mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
feat: retrieve organization subscriptions list api.
feat: subscriptions reducers.
This commit is contained in:
@@ -13,7 +13,6 @@ export default function DashboardLoadingIndicator({
|
|||||||
<div className={classNames('bigcapital-loading', className)}>
|
<div className={classNames('bigcapital-loading', className)}>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<Icon icon="bigcapital" height={37} width={214} />
|
<Icon icon="bigcapital" height={37} width={214} />
|
||||||
<span class="text">Please wait while resources loading...</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ function EnsureOrganizationIsReady({
|
|||||||
redirectTo = '/setup',
|
redirectTo = '/setup',
|
||||||
|
|
||||||
// #withOrganizationByOrgId
|
// #withOrganizationByOrgId
|
||||||
isOrganizationBuilt,
|
isOrganizationInitialized,
|
||||||
}) {
|
}) {
|
||||||
return (isOrganizationBuilt) ? children : (
|
return (isOrganizationInitialized) ? children : (
|
||||||
<Redirect
|
<Redirect
|
||||||
to={{ pathname: redirectTo }}
|
to={{ pathname: redirectTo }}
|
||||||
/>
|
/>
|
||||||
@@ -27,5 +27,5 @@ export default compose(
|
|||||||
connect((state, props) => ({
|
connect((state, props) => ({
|
||||||
organizationId: props.currentOrganizationId,
|
organizationId: props.currentOrganizationId,
|
||||||
})),
|
})),
|
||||||
withOrganization(({ isOrganizationBuilt }) => ({ isOrganizationBuilt })),
|
withOrganization(({ isOrganizationInitialized }) => ({ isOrganizationInitialized })),
|
||||||
)(EnsureOrganizationIsReady);
|
)(EnsureOrganizationIsReady);
|
||||||
@@ -7,6 +7,7 @@ import SetupWizardPage from 'containers/Setup/WizardSetupPage';
|
|||||||
import DashboardLoadingIndicator from 'components/Dashboard/DashboardLoadingIndicator';
|
import DashboardLoadingIndicator from 'components/Dashboard/DashboardLoadingIndicator';
|
||||||
|
|
||||||
import withOrganizationActions from 'containers/Organization/withOrganizationActions';
|
import withOrganizationActions from 'containers/Organization/withOrganizationActions';
|
||||||
|
import withSubscriptionsActions from 'containers/Subscriptions/withSubscriptionsActions';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -17,13 +18,26 @@ function DashboardPrivatePages({
|
|||||||
|
|
||||||
// #withOrganizationActions
|
// #withOrganizationActions
|
||||||
requestAllOrganizations,
|
requestAllOrganizations,
|
||||||
|
|
||||||
|
// #withSubscriptionsActions
|
||||||
|
requestFetchSubscriptions,
|
||||||
}) {
|
}) {
|
||||||
|
// Fetch all user's organizatins.
|
||||||
const fetchOrganizations = useQuery(
|
const fetchOrganizations = useQuery(
|
||||||
['organizations'], () => requestAllOrganizations(),
|
['organizations'], () => requestAllOrganizations(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Fetchs organization subscriptions.
|
||||||
|
const fetchSuscriptions = useQuery(
|
||||||
|
['susbcriptions'], () => requestFetchSubscriptions(),
|
||||||
|
{ enabled: fetchOrganizations.data },
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardLoadingIndicator isLoading={fetchOrganizations.isFetching}>
|
<DashboardLoadingIndicator isLoading={
|
||||||
|
fetchOrganizations.isFetching ||
|
||||||
|
fetchSuscriptions.isFetching
|
||||||
|
}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path={'/setup'}>
|
<Route path={'/setup'}>
|
||||||
<SetupWizardPage />
|
<SetupWizardPage />
|
||||||
@@ -39,4 +53,5 @@ function DashboardPrivatePages({
|
|||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withOrganizationActions,
|
withOrganizationActions,
|
||||||
|
withSubscriptionsActions,
|
||||||
)(DashboardPrivatePages);
|
)(DashboardPrivatePages);
|
||||||
@@ -43,14 +43,18 @@ function SetupLeftSection({
|
|||||||
<p className={'content__text'}>
|
<p className={'content__text'}>
|
||||||
<T id={'you_have_a_bigcapital_account'} />
|
<T id={'you_have_a_bigcapital_account'} />
|
||||||
</p>
|
</p>
|
||||||
|
<span class="content__divider"></span>
|
||||||
|
|
||||||
<div className={'content__organization'}>
|
<div className={'content__organization'}>
|
||||||
<span class="organization-id">Your oragnization ID: <span class="id">{ currentOrganizationId }</span>,</span><br />
|
<span class="organization-id">
|
||||||
<span class="signout"><a onClick={onClickLogout} href="#"><T id={'sign_out'} /></a></span>
|
Your oragnization ID: <span class="id">{ currentOrganizationId }</span>,
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
<span class="signout">
|
||||||
|
<a onClick={onClickLogout} href="#"><T id={'sign_out'} /></a>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="content__divider"></span>
|
|
||||||
|
|
||||||
<div className={'content__footer'}>
|
<div className={'content__footer'}>
|
||||||
<div className={'content__contact-info'}>
|
<div className={'content__contact-info'}>
|
||||||
<p><T id={'we_re_here_to_help'} /> {'+21892-791-8381'}</p>
|
<p><T id={'we_re_here_to_help'} /> {'+21892-791-8381'}</p>
|
||||||
|
|||||||
@@ -16,16 +16,20 @@ import classNames from 'classnames';
|
|||||||
import { TimezonePicker } from '@blueprintjs/timezone';
|
import { TimezonePicker } from '@blueprintjs/timezone';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { DateInput } from '@blueprintjs/datetime';
|
import { DateInput } from '@blueprintjs/datetime';
|
||||||
|
import { withWizard } from 'react-albus';
|
||||||
import { momentFormatter, tansformDateValue } from 'utils';
|
import { momentFormatter, tansformDateValue } from 'utils';
|
||||||
import { ListSelect, ErrorMessage, FieldRequiredHint } from 'components';
|
import { ListSelect, ErrorMessage, FieldRequiredHint } from 'components';
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
|
|
||||||
import withSettingsActions from 'containers/Settings/withSettingsActions';
|
import withSettingsActions from 'containers/Settings/withSettingsActions';
|
||||||
import withOrganizationActions from 'containers/Organization/withOrganizationActions';
|
import withOrganizationActions from 'containers/Organization/withOrganizationActions';
|
||||||
|
|
||||||
import { compose, optionsMapToArray } from 'utils';
|
import { compose, optionsMapToArray } from 'utils';
|
||||||
|
|
||||||
function SetupOrganizationForm({ requestSubmitOptions, requestSeedTenant }) {
|
function SetupOrganizationForm({
|
||||||
|
requestSubmitOptions,
|
||||||
|
requestOrganizationSeed,
|
||||||
|
wizard,
|
||||||
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [selected, setSelected] = useState();
|
const [selected, setSelected] = useState();
|
||||||
|
|
||||||
@@ -130,7 +134,7 @@ function SetupOrganizationForm({ requestSubmitOptions, requestSeedTenant }) {
|
|||||||
name: Yup.string()
|
name: Yup.string()
|
||||||
.required()
|
.required()
|
||||||
.label(formatMessage({ id: 'organization_name_' })),
|
.label(formatMessage({ id: 'organization_name_' })),
|
||||||
date_start: Yup.date()
|
financial_date_start: Yup.date()
|
||||||
.required()
|
.required()
|
||||||
.label(formatMessage({ id: 'date_start_' })),
|
.label(formatMessage({ id: 'date_start_' })),
|
||||||
base_currency: Yup.string()
|
base_currency: Yup.string()
|
||||||
@@ -148,7 +152,7 @@ function SetupOrganizationForm({ requestSubmitOptions, requestSeedTenant }) {
|
|||||||
const initialValues = useMemo(
|
const initialValues = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
name: '',
|
name: '',
|
||||||
date_start: moment(new Date()).format('YYYY-MM-DD'),
|
financial_date_start: moment(new Date()).format('YYYY-MM-DD'),
|
||||||
base_currency: '',
|
base_currency: '',
|
||||||
language: '',
|
language: '',
|
||||||
fiscal_year: '',
|
fiscal_year: '',
|
||||||
@@ -176,7 +180,11 @@ function SetupOrganizationForm({ requestSubmitOptions, requestSeedTenant }) {
|
|||||||
return { key: option.key, ...option, group: 'organization' };
|
return { key: option.key, ...option, group: 'organization' };
|
||||||
});
|
});
|
||||||
requestSubmitOptions({ options })
|
requestSubmitOptions({ options })
|
||||||
|
.then(() => {
|
||||||
|
return requestOrganizationSeed();
|
||||||
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
wizard.next();
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
})
|
})
|
||||||
.catch((erros) => {
|
.catch((erros) => {
|
||||||
@@ -220,29 +228,29 @@ function SetupOrganizationForm({ requestSubmitOptions, requestSeedTenant }) {
|
|||||||
const handleDateChange = useCallback(
|
const handleDateChange = useCallback(
|
||||||
(date) => {
|
(date) => {
|
||||||
const formatted = moment(date).format('YYYY-MM-DD');
|
const formatted = moment(date).format('YYYY-MM-DD');
|
||||||
setFieldValue('date_start', formatted);
|
setFieldValue('financial_date_start', formatted);
|
||||||
},
|
},
|
||||||
[setFieldValue],
|
[setFieldValue],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'register-organizaton-form'}>
|
<div className={'setup-organization'}>
|
||||||
<div className={'register-org-title'}>
|
<div className={'setup-organization__title-wrap'}>
|
||||||
<h2>
|
<h1>
|
||||||
<T id={'let_s_get_started'} />
|
<T id={'let_s_get_started'} />
|
||||||
</h2>
|
</h1>
|
||||||
<p>
|
<p class="paragraph">
|
||||||
<T id={'tell_the_system_a_little_bit_about_your_organization'} />
|
<T id={'tell_the_system_a_little_bit_about_your_organization'} />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onClick={handleSubmit}>
|
<form class="setup-organization__form" onClick={handleSubmit}>
|
||||||
<h3>
|
<h3>
|
||||||
<T id={'organization_details'} />
|
<T id={'organization_details'} />
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'name'} />}
|
label={<T id={'legal_organization_name'} />}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
className={'form-group--name'}
|
className={'form-group--name'}
|
||||||
intent={errors.name && touched.name && Intent.DANGER}
|
intent={errors.name && touched.name && Intent.DANGER}
|
||||||
@@ -258,15 +266,15 @@ function SetupOrganizationForm({ requestSubmitOptions, requestSeedTenant }) {
|
|||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'financial_starting_date'} />}
|
label={<T id={'financial_starting_date'} />}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
intent={errors.date_start && touched.date_start && Intent.DANGER}
|
intent={errors.financial_date_start && touched.financial_date_start && Intent.DANGER}
|
||||||
helperText={
|
helperText={
|
||||||
<ErrorMessage name="date_start" {...{ errors, touched }} />
|
<ErrorMessage name="financial_date_start" {...{ errors, touched }} />
|
||||||
}
|
}
|
||||||
className={classNames('form-group--select-list', Classes.FILL)}
|
className={classNames('form-group--select-list', Classes.FILL)}
|
||||||
>
|
>
|
||||||
<DateInput
|
<DateInput
|
||||||
{...momentFormatter('MMMM Do YYYY')}
|
{...momentFormatter('MMMM Do YYYY')}
|
||||||
value={tansformDateValue(values.date_start)}
|
value={tansformDateValue(values.financial_date_start)}
|
||||||
onChange={handleDateChange}
|
onChange={handleDateChange}
|
||||||
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
||||||
/>
|
/>
|
||||||
@@ -410,4 +418,5 @@ function SetupOrganizationForm({ requestSubmitOptions, requestSeedTenant }) {
|
|||||||
export default compose(
|
export default compose(
|
||||||
withSettingsActions,
|
withSettingsActions,
|
||||||
withOrganizationActions,
|
withOrganizationActions,
|
||||||
|
withWizard,
|
||||||
)(SetupOrganizationForm);
|
)(SetupOrganizationForm);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useHistory } from "react-router-dom";
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import WizardSetupSteps from './WizardSetupSteps';
|
import WizardSetupSteps from './WizardSetupSteps';
|
||||||
|
import withSubscriptions from 'containers/Subscriptions/withSubscriptions';
|
||||||
|
|
||||||
import SetupSubscriptionForm from './SetupSubscriptionForm';
|
import SetupSubscriptionForm from './SetupSubscriptionForm';
|
||||||
import SetupOrganizationForm from './SetupOrganizationForm';
|
import SetupOrganizationForm from './SetupOrganizationForm';
|
||||||
@@ -23,16 +24,18 @@ function SetupRightSection ({
|
|||||||
|
|
||||||
// #withOrganization
|
// #withOrganization
|
||||||
isOrganizationInitialized,
|
isOrganizationInitialized,
|
||||||
isOrganizationSubscribed: hasSubscriptions,
|
isOrganizationSeeded,
|
||||||
isOrganizationSeeded
|
|
||||||
|
// #withSubscriptions
|
||||||
|
isSubscriptionActive
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const handleSkip = useCallback(({ step, push }) => {
|
const handleSkip = useCallback(({ step, push }) => {
|
||||||
const scenarios = [
|
const scenarios = [
|
||||||
{ condition: !hasSubscriptions, redirectTo: 'subscription' },
|
{ condition: !isSubscriptionActive, redirectTo: 'subscription' },
|
||||||
{ condition: hasSubscriptions && !isOrganizationInitialized, redirectTo: 'initializing' },
|
{ condition: isSubscriptionActive && !isOrganizationInitialized, redirectTo: 'initializing' },
|
||||||
{ condition: hasSubscriptions && !isOrganizationSeeded, redirectTo: 'organization' },
|
{ condition: isSubscriptionActive && !isOrganizationSeeded, redirectTo: 'organization' },
|
||||||
];
|
];
|
||||||
const scenario = scenarios.find((scenario) => scenario.condition);
|
const scenario = scenarios.find((scenario) => scenario.condition);
|
||||||
|
|
||||||
@@ -40,7 +43,7 @@ function SetupRightSection ({
|
|||||||
push(scenario.redirectTo);
|
push(scenario.redirectTo);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
hasSubscriptions,
|
isSubscriptionActive,
|
||||||
isOrganizationInitialized,
|
isOrganizationInitialized,
|
||||||
isOrganizationSeeded,
|
isOrganizationSeeded,
|
||||||
]);
|
]);
|
||||||
@@ -92,12 +95,15 @@ export default compose(
|
|||||||
withOrganization(({
|
withOrganization(({
|
||||||
organization,
|
organization,
|
||||||
isOrganizationInitialized,
|
isOrganizationInitialized,
|
||||||
isOrganizationSubscribed,
|
|
||||||
isOrganizationSeeded,
|
isOrganizationSeeded,
|
||||||
}) => ({
|
}) => ({
|
||||||
organization,
|
organization,
|
||||||
isOrganizationInitialized,
|
isOrganizationInitialized,
|
||||||
isOrganizationSubscribed,
|
|
||||||
isOrganizationSeeded,
|
isOrganizationSeeded,
|
||||||
})),
|
})),
|
||||||
|
withSubscriptions(({
|
||||||
|
isSubscriptionActive,
|
||||||
|
}) => ({
|
||||||
|
isSubscriptionActive
|
||||||
|
}), 'main'),
|
||||||
)(SetupRightSection);
|
)(SetupRightSection);
|
||||||
@@ -3,10 +3,11 @@ import * as Yup from 'yup';
|
|||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { Button, Intent } from '@blueprintjs/core';
|
import { Button, Intent } from '@blueprintjs/core';
|
||||||
|
import { withWizard } from 'react-albus';
|
||||||
|
import withSubscriptionsActions from 'containers/Subscriptions/withSubscriptionsActions';
|
||||||
import BillingPlans from 'containers/Subscriptions/billingPlans';
|
import BillingPlans from 'containers/Subscriptions/billingPlans';
|
||||||
import BillingPeriods from 'containers/Subscriptions/billingPeriods';
|
import BillingPeriods from 'containers/Subscriptions/billingPeriods';
|
||||||
import { BillingPaymentmethod } from 'containers/Subscriptions/billingPaymentmethod';
|
import { BillingPaymentmethod } from 'containers/Subscriptions/billingPaymentmethod';
|
||||||
|
|
||||||
import withBillingActions from 'containers/Subscriptions/withBillingActions';
|
import withBillingActions from 'containers/Subscriptions/withBillingActions';
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -14,18 +15,23 @@ import { compose } from 'utils';
|
|||||||
* Subscription step of wizard setup.
|
* Subscription step of wizard setup.
|
||||||
*/
|
*/
|
||||||
function SetupSubscriptionForm({
|
function SetupSubscriptionForm({
|
||||||
//#withBillingActions
|
// #withBillingActions
|
||||||
requestSubmitBilling,
|
requestSubmitBilling,
|
||||||
|
|
||||||
|
// #withWizard
|
||||||
|
wizard,
|
||||||
|
|
||||||
|
// #withSubscriptionsActions
|
||||||
|
requestFetchSubscriptions
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
const validationSchema = Yup.object().shape({
|
const validationSchema = Yup.object().shape({
|
||||||
plan_slug: Yup.string()
|
plan_slug: Yup.string()
|
||||||
.required()
|
.required()
|
||||||
.label(formatMessage({ id: 'plan_slug' })),
|
.label(formatMessage({ id: 'plan_slug' })),
|
||||||
license_code: Yup.string()
|
license_code: Yup.string()
|
||||||
.min(7)
|
.min(10)
|
||||||
.max(7)
|
.max(10)
|
||||||
.required()
|
.required()
|
||||||
.label(formatMessage({ id: 'license_code_' }))
|
.label(formatMessage({ id: 'license_code_' }))
|
||||||
.trim(),
|
.trim(),
|
||||||
@@ -48,6 +54,10 @@ function SetupSubscriptionForm({
|
|||||||
onSubmit: (values, { setSubmitting, setErrors }) => {
|
onSubmit: (values, { setSubmitting, setErrors }) => {
|
||||||
requestSubmitBilling(values)
|
requestSubmitBilling(values)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
return requestFetchSubscriptions();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
wizard.next();
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
})
|
})
|
||||||
.catch((errors) => {
|
.catch((errors) => {
|
||||||
@@ -76,4 +86,8 @@ function SetupSubscriptionForm({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withBillingActions)(SetupSubscriptionForm);
|
export default compose(
|
||||||
|
withBillingActions,
|
||||||
|
withWizard,
|
||||||
|
withSubscriptionsActions,
|
||||||
|
)(SetupSubscriptionForm);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ function BillingPeriods({ formik, title, selected = 1 }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section class="billing-section">
|
<section class="billing-section">
|
||||||
<h1 className={'bg-title'}>
|
<h1>
|
||||||
<T id={title} />
|
<T id={title} />
|
||||||
</h1>
|
</h1>
|
||||||
<p className='paragraph'>
|
<p className='paragraph'>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ function BillingPlans({ formik, title, selected = 1 }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section class="billing-section">
|
<section class="billing-section">
|
||||||
<h1 className={'bg-title'}>
|
<h1>
|
||||||
<T id={title} />
|
<T id={title} />
|
||||||
</h1>
|
</h1>
|
||||||
<p className='paragraph'>
|
<p className='paragraph'>
|
||||||
|
|||||||
22
client/src/containers/Subscriptions/withSubscriptions.js
Normal file
22
client/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);
|
||||||
|
};
|
||||||
@@ -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);
|
||||||
@@ -765,4 +765,5 @@ export default {
|
|||||||
something_wentwrong: 'Something went wrong.',
|
something_wentwrong: 'Something went wrong.',
|
||||||
new_password: 'New password',
|
new_password: 'New password',
|
||||||
license_code_: 'License code',
|
license_code_: 'License code',
|
||||||
|
legal_organization_name: 'Legal Organization Name'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,10 +26,12 @@ import vendors from './vendors/vendors.reducer';
|
|||||||
import paymentReceives from './PaymentReceive/paymentReceive.reducer';
|
import paymentReceives from './PaymentReceive/paymentReceive.reducer';
|
||||||
import paymentMades from './PaymentMades/paymentMade.reducer';
|
import paymentMades from './PaymentMades/paymentMade.reducer';
|
||||||
import organizations from './organizations/organizations.reducers';
|
import organizations from './organizations/organizations.reducers';
|
||||||
|
import subscriptions from './subscription/subscription.reducer';
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
authentication,
|
authentication,
|
||||||
organizations,
|
organizations,
|
||||||
|
subscriptions,
|
||||||
dashboard,
|
dashboard,
|
||||||
users,
|
users,
|
||||||
accounts,
|
accounts,
|
||||||
@@ -47,12 +49,11 @@ export default combineReducers({
|
|||||||
exchangeRates,
|
exchangeRates,
|
||||||
globalErrors,
|
globalErrors,
|
||||||
customers,
|
customers,
|
||||||
|
|
||||||
salesEstimates,
|
salesEstimates,
|
||||||
salesInvoices,
|
salesInvoices,
|
||||||
salesReceipts,
|
salesReceipts,
|
||||||
bills,
|
bills,
|
||||||
vendors,
|
vendors,
|
||||||
paymentReceives,
|
paymentReceives,
|
||||||
paymentMades
|
paymentMades,
|
||||||
});
|
});
|
||||||
|
|||||||
14
client/src/store/subscription/subscription.actions.js
Normal file
14
client/src/store/subscription/subscription.actions.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import ApiService from 'services/ApiService';
|
||||||
|
import t from 'store/types';
|
||||||
|
|
||||||
|
export const fetchSubscriptions = () => (dispatch) => new Promise((resolve, reject) => {
|
||||||
|
ApiService.get('subscription').then((response) => {
|
||||||
|
dispatch({
|
||||||
|
type: t.SET_PLAN_SUBSCRIPTIONS_LIST,
|
||||||
|
payload: {
|
||||||
|
subscriptions: response.data.subscriptions,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
resolve(response);
|
||||||
|
}).catch((error) => { reject(error); })
|
||||||
|
});
|
||||||
19
client/src/store/subscription/subscription.reducer.js
Normal file
19
client/src/store/subscription/subscription.reducer.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { createReducer } from '@reduxjs/toolkit';
|
||||||
|
import t from 'store/types';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
data: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createReducer(initialState, {
|
||||||
|
|
||||||
|
[t.SET_PLAN_SUBSCRIPTIONS_LIST]: (state, action) => {
|
||||||
|
const { subscriptions } = action.payload;
|
||||||
|
const _data = {};
|
||||||
|
|
||||||
|
subscriptions.forEach((subscription) => {
|
||||||
|
_data[subscription.id] = subscription;
|
||||||
|
});
|
||||||
|
state.data = _data;
|
||||||
|
},
|
||||||
|
});
|
||||||
23
client/src/store/subscription/subscription.selectors.js
Normal file
23
client/src/store/subscription/subscription.selectors.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
const subscriptionSelector = (slug) => (state, props) => {
|
||||||
|
const subscriptions = Object.values(state.subscriptions.data);
|
||||||
|
return subscriptions.find((subscription) => subscription.slug === slug);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isSubscriptionOnTrialFactory = (slug) => createSelector(
|
||||||
|
subscriptionSelector(slug),
|
||||||
|
(subscription) => !!subscription?.on_trial,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const isSubscriptionActiveFactory = (slug) => createSelector(
|
||||||
|
subscriptionSelector(slug),
|
||||||
|
(subscription) => {
|
||||||
|
return !!subscription?.active;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const isSubscriptionInactiveFactory = (slug) => createSelector(
|
||||||
|
subscriptionSelector(slug),
|
||||||
|
(subscription) => !!subscription?.inactive,
|
||||||
|
);
|
||||||
4
client/src/store/subscription/subscription.types.js
Normal file
4
client/src/store/subscription/subscription.types.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
export default {
|
||||||
|
SET_PLAN_SUBSCRIPTIONS_LIST: 'SET_PLAN_SUBSCRIPTIONS_LIST',
|
||||||
|
};
|
||||||
@@ -25,6 +25,7 @@ import vendors from './vendors/vendors.types';
|
|||||||
import paymentReceives from './PaymentReceive/paymentReceive.type';
|
import paymentReceives from './PaymentReceive/paymentReceive.type';
|
||||||
import paymentMades from './PaymentMades/paymentMade.type';
|
import paymentMades from './PaymentMades/paymentMade.type';
|
||||||
import organizations from './organizations/organizations.types';
|
import organizations from './organizations/organizations.types';
|
||||||
|
import subscription from './subscription/subscription.types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
...authentication,
|
...authentication,
|
||||||
@@ -54,4 +55,5 @@ export default {
|
|||||||
...paymentReceives,
|
...paymentReceives,
|
||||||
...paymentMades,
|
...paymentMades,
|
||||||
...organizations,
|
...organizations,
|
||||||
|
...subscription,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,13 +20,17 @@
|
|||||||
|
|
||||||
h1{
|
h1{
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
h1,
|
||||||
|
h3{
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #6d6d6d;
|
color: #6b7382;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__content{
|
&__content{
|
||||||
width: 70%;
|
width: 70%;
|
||||||
|
padding-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__left-section {
|
&__left-section {
|
||||||
@@ -68,7 +72,7 @@
|
|||||||
&__text {
|
&__text {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
margin-bottom: 18px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__organization {
|
&__organization {
|
||||||
@@ -87,7 +91,7 @@
|
|||||||
height: 3px;
|
height: 3px;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
background: rgba(255, 255, 255, 0.15);
|
background: rgba(255, 255, 255, 0.15);
|
||||||
margin: 20px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__footer{
|
&__footer{
|
||||||
@@ -190,7 +194,54 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Register Subscription form
|
.setup-organization {
|
||||||
.register-subscription-form {
|
width: 580px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 45px 0 20px;
|
||||||
|
|
||||||
|
&__title-wrap{
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #565e6c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__form{
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-form-group {
|
||||||
|
.bp3-input-group {
|
||||||
|
.bp3-input {
|
||||||
|
height: 38px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-button:not([class*='bp3-intent-']):not(.bp3-minimal) {
|
||||||
|
width: 100%;
|
||||||
|
height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-org-note{
|
||||||
|
font-size: 13px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid #e1e1e1;
|
||||||
|
margin-bottom: 1.75rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-org-button {
|
||||||
|
.bp3-button {
|
||||||
|
background-color: #0052cc;
|
||||||
|
min-width: 175px;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,17 @@
|
|||||||
import { Router } from 'express'
|
import { Router, Request, Response, NextFunction } from 'express'
|
||||||
import { Container, Service } from 'typedi';
|
import { Container, Service, Inject } from 'typedi';
|
||||||
import JWTAuth from 'api/middleware/jwtAuth';
|
import JWTAuth from 'api/middleware/jwtAuth';
|
||||||
import TenancyMiddleware from 'api/middleware/TenancyMiddleware';
|
import TenancyMiddleware from 'api/middleware/TenancyMiddleware';
|
||||||
import AttachCurrentTenantUser from 'api/middleware/AttachCurrentTenantUser';
|
import AttachCurrentTenantUser from 'api/middleware/AttachCurrentTenantUser';
|
||||||
import PaymentViaLicenseController from 'api/controllers/Subscription/PaymentViaLicense';
|
import PaymentViaLicenseController from 'api/controllers/Subscription/PaymentViaLicense';
|
||||||
|
import SubscriptionService from 'services/Subscription/SubscriptionService';
|
||||||
|
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class SubscriptionController {
|
export default class SubscriptionController {
|
||||||
|
@Inject()
|
||||||
|
subscriptionService: SubscriptionService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
*/
|
*/
|
||||||
@@ -19,6 +24,26 @@ export default class SubscriptionController {
|
|||||||
|
|
||||||
router.use('/license', Container.get(PaymentViaLicenseController).router());
|
router.use('/license', Container.get(PaymentViaLicenseController).router());
|
||||||
|
|
||||||
|
router.get('/',
|
||||||
|
asyncMiddleware(this.getSubscriptions.bind(this))
|
||||||
|
);
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all subscriptions of the authenticated user's tenant.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
async getSubscriptions(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const subscriptions = await this.subscriptionService.getSubscriptions(tenantId);
|
||||||
|
return res.status(200).send({ subscriptions });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ export default {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
// config: true,
|
// config: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'financial_date_start',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'language',
|
key: 'language',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ export default class OrganizationService {
|
|||||||
this.logger.info('[organization] trying to list all organizations.', { user });
|
this.logger.info('[organization] trying to list all organizations.', { user });
|
||||||
|
|
||||||
const { tenantRepository } = this.sysRepositories;
|
const { tenantRepository } = this.sysRepositories;
|
||||||
const tenant = await tenantRepository.getByIdWithSubscriptions(user.tenantId);
|
const tenant = await tenantRepository.getById(user.tenantId);
|
||||||
|
|
||||||
return [tenant];
|
return [tenant];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { Plan, Tenant } from 'system/models';
|
import { Plan, PlanSubscription } from 'system/models';
|
||||||
import Subscription from 'services/Subscription/Subscription';
|
import Subscription from 'services/Subscription/Subscription';
|
||||||
import LicensePaymentMethod from 'services/Payment/LicensePaymentMethod';
|
import LicensePaymentMethod from 'services/Payment/LicensePaymentMethod';
|
||||||
import PaymentContext from 'services/Payment';
|
import PaymentContext from 'services/Payment';
|
||||||
@@ -29,7 +29,7 @@ export default class SubscriptionService {
|
|||||||
* @param {string} licenseCode
|
* @param {string} licenseCode
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
async subscriptionViaLicense(
|
public async subscriptionViaLicense(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
planSlug: string,
|
planSlug: string,
|
||||||
paymentModel?: ILicensePaymentModel,
|
paymentModel?: ILicensePaymentModel,
|
||||||
@@ -53,4 +53,15 @@ export default class SubscriptionService {
|
|||||||
tenantId, paymentModel
|
tenantId, paymentModel
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all subscription of the given tenant.
|
||||||
|
* @param {number} tenantId
|
||||||
|
*/
|
||||||
|
public async getSubscriptions(tenantId: number) {
|
||||||
|
this.logger.info('[subscription] trying to get tenant subscriptions.', { tenantId });
|
||||||
|
const subscriptions = await PlanSubscription.query().where('tenant_id', tenantId);
|
||||||
|
|
||||||
|
return subscriptions;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user