mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 20:30:33 +00:00
chrone: sperate client and server to different repos.
This commit is contained in:
94
src/containers/Subscriptions/BillingForm.js
Normal file
94
src/containers/Subscriptions/BillingForm.js
Normal file
@@ -0,0 +1,94 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Formik, Form } from 'formik';
|
||||
import intl from 'react-intl-universal';
|
||||
import { If, Alert, T } from 'components';
|
||||
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
|
||||
import 'style/pages/Billing/BillingPage.scss';
|
||||
|
||||
import { MasterBillingTabs } from './SubscriptionTabs';
|
||||
|
||||
import withBillingActions from './withBillingActions';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withSubscriptionPlansActions from './withSubscriptionPlansActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
import { getBillingFormValidationSchema } from './utils';
|
||||
import withSubscriptions from './withSubscriptions';
|
||||
|
||||
/**
|
||||
* Billing form.
|
||||
*/
|
||||
function BillingForm({
|
||||
// #withDashboardActions
|
||||
changePageTitle,
|
||||
|
||||
// #withBillingActions
|
||||
requestSubmitBilling,
|
||||
|
||||
initSubscriptionPlans,
|
||||
|
||||
// #withSubscriptions
|
||||
isSubscriptionInactive,
|
||||
}) {
|
||||
useEffect(() => {
|
||||
changePageTitle(intl.get('billing'));
|
||||
}, [changePageTitle]);
|
||||
|
||||
React.useEffect(() => {
|
||||
initSubscriptionPlans();
|
||||
}, [initSubscriptionPlans]);
|
||||
|
||||
// Initial values.
|
||||
const initialValues = {
|
||||
plan_slug: 'essentials',
|
||||
period: 'month',
|
||||
license_code: '',
|
||||
};
|
||||
|
||||
// Handle form submitting.
|
||||
const handleSubmit = (values, { setSubmitting }) => {
|
||||
requestSubmitBilling(values)
|
||||
.then((response) => {
|
||||
setSubmitting(false);
|
||||
})
|
||||
.catch((errors) => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardInsider name={'billing-page'}>
|
||||
<div className={'billing-page'}>
|
||||
<If condition={isSubscriptionInactive}>
|
||||
<Alert
|
||||
intent={'danger'}
|
||||
title={<T id={'billing.suspend_message.title'} />}
|
||||
description={<T id={'billing.suspend_message.description'} />}
|
||||
/>
|
||||
</If>
|
||||
|
||||
<Formik
|
||||
validationSchema={getBillingFormValidationSchema()}
|
||||
onSubmit={handleSubmit}
|
||||
initialValues={initialValues}
|
||||
>
|
||||
<Form>
|
||||
<MasterBillingTabs />
|
||||
</Form>
|
||||
</Formik>
|
||||
</div>
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDashboardActions,
|
||||
withBillingActions,
|
||||
withSubscriptionPlansActions,
|
||||
withSubscriptions(
|
||||
({ isSubscriptionInactive }) => ({ isSubscriptionInactive }),
|
||||
'main',
|
||||
),
|
||||
)(BillingForm);
|
||||
58
src/containers/Subscriptions/BillingPeriod.js
Normal file
58
src/containers/Subscriptions/BillingPeriod.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { compose } from 'redux';
|
||||
|
||||
import 'style/pages/Subscription/PlanPeriodRadio.scss';
|
||||
|
||||
import withPlan from 'containers/Subscriptions/withPlan';
|
||||
|
||||
import { saveInvoke } from 'utils';
|
||||
|
||||
/**
|
||||
* Billing period.
|
||||
*/
|
||||
function BillingPeriod({
|
||||
// #ownProps
|
||||
label,
|
||||
value,
|
||||
selectedOption,
|
||||
onSelected,
|
||||
period,
|
||||
|
||||
// #withPlan
|
||||
price,
|
||||
currencyCode,
|
||||
}) {
|
||||
const handlePeriodClick = () => {
|
||||
saveInvoke(onSelected, value);
|
||||
};
|
||||
return (
|
||||
<div
|
||||
id={`plan-period-${period}`}
|
||||
className={classNames(
|
||||
{
|
||||
'is-selected': value === selectedOption,
|
||||
},
|
||||
'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>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withPlan(({ plan }, state, { period }) => ({
|
||||
price: get(plan, `price.${period}`),
|
||||
currencyCode: get(plan, 'currencyCode'),
|
||||
})),
|
||||
)(BillingPeriod);
|
||||
48
src/containers/Subscriptions/BillingPeriodsInput.js
Normal file
48
src/containers/Subscriptions/BillingPeriodsInput.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { Field } from 'formik';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { T, SubscriptionPeriods } from 'components';
|
||||
|
||||
import withPlan from './withPlan';
|
||||
|
||||
/**
|
||||
* Sunscription periods enhanced.
|
||||
*/
|
||||
const SubscriptionPeriodsEnhanced = R.compose(
|
||||
withPlan(({ plan }) => ({ plan })),
|
||||
)(({ plan, ...restProps }) => {
|
||||
if (!plan) return null;
|
||||
|
||||
return <SubscriptionPeriods periods={plan.periods} {...restProps} />;
|
||||
});
|
||||
|
||||
/**
|
||||
* Billing periods.
|
||||
*/
|
||||
export default function BillingPeriods() {
|
||||
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'}>
|
||||
{({ field: { value }, form: { values, setFieldValue } }) => (
|
||||
<SubscriptionPeriodsEnhanced
|
||||
selectedPeriod={value}
|
||||
planSlug={values.plan_slug}
|
||||
onPeriodSelect={(period) => {
|
||||
setFieldValue('period', period);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
54
src/containers/Subscriptions/BillingPlan.js
Normal file
54
src/containers/Subscriptions/BillingPlan.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import { saveInvoke } from 'utils';
|
||||
|
||||
/**
|
||||
* Billing plan.
|
||||
*/
|
||||
export default function BillingPlan({
|
||||
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>
|
||||
);
|
||||
}
|
||||
20
src/containers/Subscriptions/BillingPlansForm.js
Normal file
20
src/containers/Subscriptions/BillingPlansForm.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
import 'style/pages/Subscription/BillingPlans.scss';
|
||||
|
||||
import BillingPlansInput from './BillingPlansInput';
|
||||
import BillingPeriodsInput from './BillingPeriodsInput';
|
||||
// import BillingPaymentMethod from './BillingPaymentMethod';
|
||||
|
||||
/**
|
||||
* Billing plans form.
|
||||
*/
|
||||
export default function BillingPlansForm() {
|
||||
return (
|
||||
<div class="billing-plans">
|
||||
<BillingPlansInput />
|
||||
<BillingPeriodsInput />
|
||||
{/* <BillingPaymentMethod /> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
37
src/containers/Subscriptions/BillingPlansInput.js
Normal file
37
src/containers/Subscriptions/BillingPlansInput.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { Field } from 'formik';
|
||||
import { T, SubscriptionPlans } from 'components';
|
||||
|
||||
import withPlans from './withPlans';
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Billing plans.
|
||||
*/
|
||||
function BillingPlans({ plans, title, description, selectedOption }) {
|
||||
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>
|
||||
|
||||
<Field name={'plan_slug'}>
|
||||
{({ form: { setFieldValue }, field: { value } }) => (
|
||||
<SubscriptionPlans
|
||||
plans={plans}
|
||||
value={value}
|
||||
onSelect={(value) => {
|
||||
setFieldValue('plan_slug', value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
export default compose(withPlans(({ plans }) => ({ plans })))(BillingPlans);
|
||||
6
src/containers/Subscriptions/BillingTab.js
Normal file
6
src/containers/Subscriptions/BillingTab.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import React from 'react';
|
||||
import BillingPlansForm from './BillingPlansForm';
|
||||
|
||||
export default function BillingTab() {
|
||||
return (<BillingPlansForm />);
|
||||
}
|
||||
42
src/containers/Subscriptions/LicenseTab.js
Normal file
42
src/containers/Subscriptions/LicenseTab.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import { Intent, Button } from '@blueprintjs/core';
|
||||
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import { compose } from 'redux';
|
||||
import { useFormikContext } from 'formik';
|
||||
|
||||
/**
|
||||
* Payment via license code tab.
|
||||
*/
|
||||
function LicenseTab({ openDialog }) {
|
||||
const { submitForm, values } = useFormikContext();
|
||||
|
||||
const handleSubmitBtnClick = () => {
|
||||
submitForm().then(() => {
|
||||
openDialog('payment-via-voucher', { ...values });
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={'license-container'}>
|
||||
<h3>
|
||||
<T id={'voucher'} />
|
||||
</h3>
|
||||
<p className="paragraph">
|
||||
<T id={'cards_will_be_charged'} />
|
||||
</p>
|
||||
|
||||
<Button
|
||||
onClick={handleSubmitBtnClick}
|
||||
intent={Intent.PRIMARY}
|
||||
large={true}
|
||||
>
|
||||
<T id={'submit_voucher'} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDialogActions)(LicenseTab);
|
||||
46
src/containers/Subscriptions/SubscriptionTabs.js
Normal file
46
src/containers/Subscriptions/SubscriptionTabs.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
import { Tabs, Tab } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
import BillingTab from './BillingTab';
|
||||
import LicenseTab from './LicenseTab';
|
||||
|
||||
/**
|
||||
* Master billing tabs.
|
||||
*/
|
||||
export const MasterBillingTabs = ({ formik }) => {
|
||||
return (
|
||||
<div>
|
||||
<Tabs animate={true} large={true}>
|
||||
<Tab
|
||||
title={intl.get('billing')}
|
||||
id={'billing'}
|
||||
panel={<BillingTab formik={formik} />}
|
||||
/>
|
||||
<Tab title={intl.get('usage')} id={'usage'} disabled={true} />
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Payment methods tabs.
|
||||
*/
|
||||
export const PaymentMethodTabs = ({ formik }) => {
|
||||
return (
|
||||
<div>
|
||||
<Tabs animate={true} large={true}>
|
||||
<Tab
|
||||
title={intl.get('voucher')}
|
||||
id={'voucher'}
|
||||
panel={<LicenseTab formik={formik} />}
|
||||
/>
|
||||
<Tab
|
||||
title={intl.get('credit_card')}
|
||||
id={'credit_card'}
|
||||
disabled={true}
|
||||
/>
|
||||
<Tab title={intl.get('paypal')} id={'paypal'} disabled={true} />
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
15
src/containers/Subscriptions/billingPaymentmethod.js
Normal file
15
src/containers/Subscriptions/billingPaymentmethod.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
|
||||
import { T } from 'components';
|
||||
import { PaymentMethodTabs } from './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>
|
||||
);
|
||||
};
|
||||
8
src/containers/Subscriptions/utils.js
Normal file
8
src/containers/Subscriptions/utils.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import * as Yup from 'yup';
|
||||
|
||||
export const getBillingFormValidationSchema = () =>
|
||||
Yup.object().shape({
|
||||
plan_slug: Yup.string().required(),
|
||||
period: Yup.string().required(),
|
||||
license_code: Yup.string().trim(),
|
||||
});
|
||||
8
src/containers/Subscriptions/withBillingActions.js
Normal file
8
src/containers/Subscriptions/withBillingActions.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { submitBilling } from 'store/billing/Billing.action';
|
||||
|
||||
export const mapDispatchToProps = (dispatch) => ({
|
||||
requestSubmitBilling: (form) => dispatch(submitBilling({ form })),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps);
|
||||
17
src/containers/Subscriptions/withPlan.js
Normal file
17
src/containers/Subscriptions/withPlan.js
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { getPlanSelector } from 'store/plans/plans.selectors';
|
||||
|
||||
export default (mapState) => {
|
||||
const mapStateToProps = (state, props) => {
|
||||
const getPlan = getPlanSelector();
|
||||
|
||||
const mapped = {
|
||||
plan: getPlan(state, props),
|
||||
};
|
||||
return (mapState) ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
return connect(mapStateToProps);
|
||||
};
|
||||
16
src/containers/Subscriptions/withPlans.js
Normal file
16
src/containers/Subscriptions/withPlans.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
getPlansSelector,
|
||||
} from 'store/plans/plans.selectors';
|
||||
|
||||
export default (mapState) => {
|
||||
const mapStateToProps = (state, props) => {
|
||||
const getPlans = getPlansSelector();
|
||||
|
||||
const mapped = {
|
||||
plans: getPlans(state, props),
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
return connect(mapStateToProps);
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { initSubscriptionPlans } from 'store/plans/plans.actions';
|
||||
|
||||
export const mapDispatchToProps = (dispatch) => ({
|
||||
initSubscriptionPlans: () => dispatch(initSubscriptionPlans()),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps);
|
||||
22
src/containers/Subscriptions/withSubscriptions.js
Normal file
22
src/containers/Subscriptions/withSubscriptions.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
isSubscriptionOnTrialFactory,
|
||||
isSubscriptionInactiveFactory,
|
||||
isSubscriptionActiveFactory,
|
||||
} from 'store/subscription/subscription.selectors';
|
||||
|
||||
export default (mapState, slug) => {
|
||||
const isSubscriptionOnTrial = isSubscriptionOnTrialFactory(slug);
|
||||
const isSubscriptionInactive = isSubscriptionInactiveFactory(slug);
|
||||
const isSubscriptionActive = isSubscriptionActiveFactory(slug);
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const mapped = {
|
||||
isSubscriptionOnTrial: isSubscriptionOnTrial(state, props),
|
||||
isSubscriptionInactive: isSubscriptionInactive(state, props),
|
||||
isSubscriptionActive: isSubscriptionActive(state, props),
|
||||
};
|
||||
return (mapState) ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
return connect(mapStateToProps);
|
||||
};
|
||||
10
src/containers/Subscriptions/withSubscriptionsActions.js
Normal file
10
src/containers/Subscriptions/withSubscriptionsActions.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
fetchSubscriptions,
|
||||
} from 'store/subscription/subscription.actions'
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
requestFetchSubscriptions: () => dispatch(fetchSubscriptions()),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps);
|
||||
19
src/containers/Subscriptions/withSubscriptionss.js
Normal file
19
src/containers/Subscriptions/withSubscriptionss.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
isSubscriptionsInactiveFactory,
|
||||
isSubscriptionsActiveFactory
|
||||
} from 'store/subscription/subscription.selectors';
|
||||
|
||||
export default (mapState) => {
|
||||
const isSubscriptionsInactive = isSubscriptionsInactiveFactory();
|
||||
const isSubscriptionsActive = isSubscriptionsActiveFactory();
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const mapped = {
|
||||
isSubscriptionsInactive: isSubscriptionsInactive(state, props),
|
||||
isSubscriptionsActive: isSubscriptionsActive(state, props),
|
||||
};
|
||||
return (mapState) ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
return connect(mapStateToProps);
|
||||
};
|
||||
Reference in New Issue
Block a user