fix(Setup): fix organization setup.

This commit is contained in:
a.bouhuolia
2021-03-20 18:59:40 +02:00
parent e801d5d618
commit 671af0daae
46 changed files with 517 additions and 445 deletions

View File

@@ -63,7 +63,6 @@
"postcss-preset-env": "6.7.0",
"postcss-safe-parser": "4.0.1",
"react": "^16.12.0",
"react-albus": "^2.0.0",
"react-app-polyfill": "^1.0.6",
"react-body-classname": "^1.3.1",
"react-content-loader": "^6.0.1",

View File

@@ -1,6 +1,6 @@
export default [
{ name: 'English', value: 'EN' },
{ name: 'Arabic', value: 'AR' },
{ name: 'English', value: 'en' },
{ name: 'Arabic', value: 'ar' },
];

View File

@@ -11,7 +11,7 @@ export default function BigcapitalLoading({ className }) {
return (
<div className={classNames('bigcapital-loading', className)}>
<div class="center">
<Icon icon="bigcapital" height={37} width={214} />
<Icon icon="bigcapital" height={37} width={228} />
</div>
</div>
);

View File

@@ -4,18 +4,17 @@ import { Formik } from 'formik';
import { useIntl } from 'react-intl';
import * as Yup from 'yup';
import { useHistory } from 'react-router-dom';
import Toaster from 'components/AppToaster';
import 'style/pages/Setup/PaymentViaVoucherDialog.scss';
import { usePaymentByVoucher } from 'hooks/query';
import { DialogContent } from 'components';
import PaymentViaLicenseForm from './PaymentViaVoucherForm';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withBillingActions from 'containers/Subscriptions/withBillingActions';
import withSubscriptionsActions from 'containers/Subscriptions/withSubscriptionsActions';
import { compose } from 'utils';
import { Intent } from '@blueprintjs/core';
/**
* Payment via license dialog content.
@@ -26,30 +25,43 @@ function PaymentViaLicenseDialogContent({
// #withDialog
closeDialog,
// #withBillingActions
requestSubmitBilling,
// #withSubscriptionsActions
requestFetchSubscriptions,
}) {
const { formatMessage } = useIntl();
const history = useHistory();
// Payment via voucher
const {
mutateAsync: paymentViaVoucherMutate,
} = usePaymentByVoucher();
// Handle submit.
const handleSubmit = (values, { setSubmitting }) => {
const handleSubmit = (values, { setSubmitting, setErrors }) => {
setSubmitting(true);
requestSubmitBilling({ ...values, ...subscriptionForm })
.then(() => {
return requestFetchSubscriptions();
})
paymentViaVoucherMutate({ ...values })
.then(() => {
Toaster.show({
message: 'Payment has been done successfully.',
intent: Intent.SUCCESS,
});
return closeDialog('payment-via-voucher');
})
.then(() => {
history.push('initializing');
})
.catch(
({
response: {
data: { errors },
},
}) => {
if (errors.find((e) => e.type === 'LICENSE.CODE.IS.INVALID')) {
setErrors({
license_code: 'The license code is not valid, please try agin.',
});
}
},
)
.finally((errors) => {
setSubmitting(false);
});
@@ -57,17 +69,18 @@ function PaymentViaLicenseDialogContent({
// Initial values.
const initialValues = {
license_number: '',
license_code: '',
plan_slug: '',
period: '',
...subscriptionForm,
};
// Validation schema.
const validationSchema = Yup.object().shape({
license_number: Yup.string()
license_code: Yup.string()
.required()
.min(10)
.max(10)
.label(formatMessage({ id: 'license_number' })),
.label(formatMessage({ id: 'license_code' })),
});
return (
@@ -82,8 +95,4 @@ function PaymentViaLicenseDialogContent({
);
}
export default compose(
withDialogActions,
withBillingActions,
withSubscriptionsActions,
)(PaymentViaLicenseDialogContent);
export default compose(withDialogActions)(PaymentViaLicenseDialogContent);

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Button, FormGroup, InputGroup, Intent } from '@blueprintjs/core';
import { Form, FastField, ErrorMessage } from 'formik';
import { Form, FastField, ErrorMessage, useFormikContext } from 'formik';
import { FormattedMessage as T } from 'react-intl';
import { compose } from 'redux';
@@ -15,12 +15,12 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
* Payment via license form.
*/
function PaymentViaLicenseForm({
// #ownProps
isSubmitting,
// #withDialogActions
closeDialog,
}) {
// Formik context.
const { isSubmitting } = useFormikContext();
const licenseNumberRef = useAutofocus();
// Handle close button click.
@@ -33,15 +33,17 @@ function PaymentViaLicenseForm({
<div className={CLASSES.DIALOG_BODY}>
<p>Please enter your preferred payment method below.</p>
<FastField name="license_number">
<FastField name="license_code">
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'voucher_number'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="voucher_number" />}
helperText={<ErrorMessage name="license_code" />}
className={'form-group--voucher_number'}
>
<InputGroup
large={true}
intent={inputIntent({ error, touched })}
{...field}
inputRef={(ref) => (licenseNumberRef.current = ref)}
/>

View File

@@ -7,16 +7,14 @@ import withDialogRedux from 'components/DialogReduxConnect';
import { compose } from 'utils';
// Lazy loading the content.
const PaymentViaLicenseDialogContent = lazy(() => import('./PaymentViaVoucherDialogContent'));
const PaymentViaLicenseDialogContent = lazy(() =>
import('./PaymentViaVoucherDialogContent'),
);
/**
* Payment via license dialog.
*/
function PaymentViaLicenseDialog({
dialogName,
payload,
isOpen
}) {
function PaymentViaLicenseDialog({ dialogName, payload, isOpen }) {
return (
<Dialog
name={dialogName}
@@ -33,9 +31,7 @@ function PaymentViaLicenseDialog({
/>
</DialogSuspense>
</Dialog>
)
);
}
export default compose(
withDialogRedux(),
)(PaymentViaLicenseDialog);
export default compose(withDialogRedux())(PaymentViaLicenseDialog);

View File

@@ -1,17 +1,11 @@
import { connect } from 'react-redux';
import {
fetchOrganizations,
buildTenant,
seedTenant,
setOrganizationSetupCompleted,
} from 'store/organizations/organizations.actions';
const mapDispatchToProps = (dispatch) => ({
requestOrganizationBuild: () => dispatch(buildTenant()),
requestOrganizationSeed: () => dispatch(seedTenant()),
requestAllOrganizations: () => dispatch(fetchOrganizations()),
setOrganizationSetupCompleted: (congrats) => dispatch(setOrganizationSetupCompleted(congrats)),
setOrganizationSetupCompleted: (congrats) =>
dispatch(setOrganizationSetupCompleted(congrats)),
});
export default connect(null, mapDispatchToProps);

View File

@@ -1,53 +1,49 @@
import React, { useEffect } from 'react';
import { useQuery } from 'react-query';
import { withWizard } from 'react-albus'
import { ProgressBar, Intent } from '@blueprintjs/core';
import { useBuildTenant } from 'hooks/query';
import 'style/pages/Setup/Initializing.scss';
import withOrganizationActions from 'containers/Organization/withOrganizationActions';
import { compose } from 'utils';
/**
* Setup initializing step form.
*/
function SetupInitializingForm({
// #withOrganizationActions
requestOrganizationBuild,
wizard: { next },
}) {
const { isSuccess } = useQuery(
['build-tenant'], () => requestOrganizationBuild(),
);
export default function SetupInitializingForm() {
const {
mutateAsync: buildTenantMutate,
isLoading,
isError,
} = useBuildTenant();
useEffect(() => {
if (isSuccess) {
next();
}
}, [isSuccess, next]);
buildTenantMutate();
}, [buildTenantMutate]);
return (
<div class="setup-initializing-form">
<ProgressBar intent={Intent.PRIMARY} value={null} />
{isLoading && <ProgressBar intent={Intent.PRIMARY} value={null} />}
<div className={'setup-initializing-form__title'}>
<h1>
{/* You organization is initializin... */}
It's time to make your accounting really simple!
</h1>
{isLoading ? (
<>
<h1>It's time to make your accounting really simple!</h1>
<p className={'paragraph'}>
while we set up your account, please remember to verify your account by
clicking on the link we sent to yout registered email address
while we set up your account, please remember to verify your
account by clicking on the link we sent to yout registered email
address
</p>
</>
) : isError ? (
<>
<h1>Something went wrong!</h1>
<p class="paragraph">Please refresh the page</p>
</>
) : (
<>
<h1>Waiting to redirect</h1>
<p class="paragraph">Refresh the page if redirect not worked.</p>
</>
)}
</div>
</div>
);
}
export default compose(
withOrganizationActions,
withWizard,
)(SetupInitializingForm);

View File

@@ -14,7 +14,7 @@ import classNames from 'classnames';
import { TimezonePicker } from '@blueprintjs/timezone';
import { FormattedMessage as T } from 'react-intl';
import { Col, Row, ListSelect } from 'components';
import { FieldRequiredHint, Col, Row, ListSelect } from 'components';
import {
momentFormatter,
tansformDateValue,
@@ -38,9 +38,10 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
</h3>
{/* ---------- Organization name ---------- */}
<FastField name={'name'}>
<FastField name={'organization_name'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
labelInfo={<FieldRequiredHint />}
label={<T id={'legal_organization_name'} />}
className={'form-group--name'}
intent={inputIntent({ error, touched })}
@@ -55,6 +56,7 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
<FastField name={'financialDateStart'}>
{({ form: { setFieldValue }, field: { value }, meta: { error, touched } }) => (
<FormGroup
labelInfo={<FieldRequiredHint />}
label={<T id={'financial_starting_date'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="financialDateStart" />}
@@ -82,6 +84,7 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
meta: { error, touched },
}) => (
<FormGroup
labelInfo={<FieldRequiredHint />}
label={<T id={'base_currency'} />}
className={classNames(
'form-group--base-currency',
@@ -137,6 +140,7 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
selectedItemProp={'value'}
defaultText={<T id={'select_language'} />}
popoverProps={{ minimal: true }}
filterable={false}
/>
</FormGroup>
)}
@@ -147,6 +151,7 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
<FastField name={'fiscalYear'}>
{({ form: { setFieldValue }, field: { value }, meta: { error, touched } }) => (
<FormGroup
labelInfo={<FieldRequiredHint />}
label={<T id={'fiscal_year'} />}
className={classNames(
'form-group--fiscal_year',
@@ -167,6 +172,7 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
onItemSelect={(item) => {
setFieldValue('fiscalYear', item.value)
}}
filterable={false}
/>
</FormGroup>
)}
@@ -180,6 +186,7 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
meta: { error, touched },
}) => (
<FormGroup
labelInfo={<FieldRequiredHint />}
label={<T id={'time_zone'} />}
className={classNames(
'form-group--time-zone',

View File

@@ -3,49 +3,33 @@ import * as Yup from 'yup';
import { Formik } from 'formik';
import moment from 'moment';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { snakeCase } from 'lodash';
import { withWizard } from 'react-albus';
import { useQuery } from 'react-query';
import 'style/pages/Setup/Organization.scss';
import SetupOrganizationForm from './SetupOrganizationForm';
import { useOrganizationSetup } from 'hooks/query';
import withSettingsActions from 'containers/Settings/withSettingsActions';
import withSettings from 'containers/Settings/withSettings';
import withOrganizationActions from 'containers/Organization/withOrganizationActions';
import {
compose,
transformToForm,
optionsMapToArray,
transfromToSnakeCase,
} from 'utils';
/**
* Setup organization form.
*/
function SetupOrganizationPage({
// #withSettingsActions
requestSubmitOptions,
requestFetchOptions,
// #withOrganizationActions
requestOrganizationSeed,
// #withSettings
organizationSettings,
wizard,
setOrganizationSetupCompleted,
}) {
const { formatMessage } = useIntl();
const fetchSettings = useQuery(['settings'], () => requestFetchOptions({}));
const { mutateAsync: organizationSetupMutate } = useOrganizationSetup();
// Validation schema.
const validationSchema = Yup.object().shape({
name: Yup.string()
organization_name: Yup.string()
.required()
.label(formatMessage({ id: 'organization_name_' })),
financialDateStart: Yup.date()
@@ -67,35 +51,21 @@ function SetupOrganizationPage({
// Initial values.
const defaultValues = {
name: '',
organization_name: '',
financialDateStart: moment(new Date()).format('YYYY-MM-DD'),
baseCurrency: '',
language: '',
language: 'en',
fiscalYear: '',
timeZone: '',
...organizationSettings,
};
const initialValues = {
...defaultValues,
/**
* We only care about the fields in the form. Previously unfilled optional
* values such as `notes` come back from the API as null, so remove those
* as well.
*/
...transformToForm(organizationSettings, defaultValues),
};
// Handle the form submit.
const handleSubmit = (values, { setSubmitting, setErrors }) => {
const options = optionsMapToArray(values).map((option) => {
return { ...option, key: snakeCase(option.key), group: 'organization' };
});
requestSubmitOptions({ options })
.then(() => {
return requestOrganizationSeed();
})
organizationSetupMutate({ ...transfromToSnakeCase(values) })
.then(() => {
return setOrganizationSetupCompleted(true);
})
@@ -132,8 +102,4 @@ function SetupOrganizationPage({
export default compose(
withSettingsActions,
withOrganizationActions,
withWizard,
withSettings(({ organizationSettings }) => ({
organizationSettings,
})),
)(SetupOrganizationPage);

View File

@@ -1,13 +1,9 @@
import React from 'react';
import { Wizard } from 'react-albus';
import { useHistory } from 'react-router-dom';
import withSubscriptions from 'containers/Subscriptions/withSubscriptions';
import SetupDialogs from './SetupDialogs';
import SetupWizardContent from './SetupWizardContent';
import withSubscriptions from 'containers/Subscriptions/withSubscriptions';
import withOrganization from 'containers/Organization/withOrganization';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
import withSetupWizard from '../../store/organizations/withSetupWizard';
@@ -24,37 +20,17 @@ function SetupRightSection({
isOrganizationSetupCompleted,
// #withSetupWizard
isCongratsStep,
isSubscriptionStep,
isInitializingStep,
isOrganizationStep,
setupStepId,
setupStepIndex,
// #withSubscriptions
isSubscriptionActive,
}) {
const history = useHistory();
const handleSkip = ({ step, push }) => {
const scenarios = [
{ condition: isCongratsStep, redirectTo: 'congrats' },
{ condition: isSubscriptionStep, redirectTo: 'subscription' },
{ condition: isInitializingStep, redirectTo: 'initializing' },
{ condition: isOrganizationStep, redirectTo: 'organization' },
];
const scenario = scenarios.find((scenario) => scenario.condition);
if (scenario) {
push(scenario.redirectTo);
}
};
return (
<section className={'setup-page__right-section'}>
<Wizard
onNext={handleSkip}
basename={'/setup'}
history={history}
render={SetupWizardContent}
<SetupWizardContent
setupStepId={setupStepId}
setupStepIndex={setupStepIndex}
/>
<SetupDialogs />
</section>
@@ -84,17 +60,8 @@ export default compose(
}),
'main',
),
withSetupWizard(
({
isCongratsStep,
isSubscriptionStep,
isInitializingStep,
isOrganizationStep,
}) => ({
isCongratsStep,
isSubscriptionStep,
isInitializingStep,
isOrganizationStep,
}),
),
withSetupWizard(({ setupStepId, setupStepIndex }) => ({
setupStepId,
setupStepIndex,
})),
)(SetupRightSection);

View File

@@ -0,0 +1,8 @@
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,21 +1,15 @@
import React from 'react';
import { Formik } from 'formik';
import { withWizard } from 'react-albus';
import 'style/pages/Setup/Subscription.scss';
import SetupSubscriptionForm from './SetupSubscriptionForm';
import { SubscriptionFormSchema } from './SubscriptionForm.schema';
import { compose } from 'utils';
/**
* Subscription step of wizard setup.
*/
function SetupSubscription({
// #withWizard
wizard,
}) {
export default function SetupSubscription() {
// Initial values.
const initialValues = {
plan_slug: 'free',
@@ -23,6 +17,7 @@ function SetupSubscription({
license_code: '',
};
// Handle form submit.
const handleSubmit = () => {};
return (
@@ -36,7 +31,3 @@ function SetupSubscription({
</div>
);
}
export default compose(
withWizard,
)(SetupSubscription);

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { Steps, Step } from 'react-albus';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import SetupSteps from './SetupSteps';
import WizardSetupSteps from './WizardSetupSteps';
import SetupSubscription from './SetupSubscription';
@@ -12,37 +11,19 @@ import SetupCongratsPage from './SetupCongratsPage';
/**
* Setup wizard content.
*/
export default function SetupWizardContent({
step,
steps
}) {
export default function SetupWizardContent({ setupStepIndex, setupStepId }) {
return (
<div class="setup-page__content">
<WizardSetupSteps currentStep={steps.indexOf(step) + 1} />
<WizardSetupSteps currentStep={setupStepIndex} />
<TransitionGroup>
<CSSTransition key={step.id} timeout={{ enter: 500, exit: 500 }}>
<div class="setup-page-form">
<Steps key={step.id} step={step}>
<Step id="subscription">
<SetupSubscription />
</Step>
<Step id={'initializing'}>
<SetupInitializingForm />
</Step>
<Step id="organization">
<SetupOrganizationPage />
</Step>
<Step id="congrats">
<SetupCongratsPage />
</Step>
</Steps>
<SetupSteps step={{ id: setupStepId }}>
<SetupSubscription id="subscription" />
<SetupInitializingForm id={'initializing'} />
<SetupOrganizationPage id="organization" />
<SetupCongratsPage id="congrats" />
</SetupSteps>
</div>
</CSSTransition>
</TransitionGroup>
</div>
);
}

View File

@@ -1,22 +1,19 @@
import React from 'react';
import classNames from 'classnames';
import { FormattedMessage as T } from 'react-intl';
import { registerWizardSteps } from 'common/registerWizard'
import { registerWizardSteps } from 'common/registerWizard';
function WizardSetupStep({
label,
isActive = false
}) {
function WizardSetupStep({ label, isActive = false }) {
return (
<li className={classNames({ 'is-active': isActive })}>
<p className={'wizard-info'}><T id={label} /></p>
<p className={'wizard-info'}>
<T id={label} />
</p>
</li>
);
}
function WizardSetupSteps({
currentStep = 1,
}) {
export default function WizardSetupSteps({ currentStep = 1 }) {
return (
<div className={'setup-page-steps-container'}>
<div className={'setup-page-steps'}>
@@ -24,7 +21,7 @@ function WizardSetupSteps({
{registerWizardSteps.map((step, index) => (
<WizardSetupStep
label={step.label}
isActive={(index + 1) == currentStep}
isActive={index + 1 === currentStep}
/>
))}
</ul>
@@ -32,5 +29,3 @@ function WizardSetupSteps({
</div>
);
}
export default WizardSetupSteps;

View File

@@ -1,5 +1,5 @@
import { useMutation, useQueryClient } from 'react-query';
import { useQueryTenant, useRequestQuery } from '../useQueryRequest';
import { useRequestQuery } from '../useQueryRequest';
import useApiRequest from '../useRequest';
import t from './types';
@@ -24,9 +24,7 @@ export function useAccounts(query, props) {
[t.ACCOUNTS, query],
{ method: 'get', url: 'accounts', params: query },
{
select: (response) => {
return response.data.accounts;
},
select: (res) => res.data.accounts,
defaultData: [],
...props,
},

View File

@@ -1,4 +1,3 @@
import { useEffect } from 'react';
import { useMutation } from 'react-query';
import useApiRequest from '../useRequest';
import { useAuthActions } from '../state';
@@ -10,20 +9,16 @@ export const useAuthLogin = (props) => {
const { setLogin } = useAuthActions();
const apiRequest = useApiRequest();
const states = useMutation(
return useMutation(
(values) => apiRequest.post('auth/login', values),
{
select: (res) => res.data,
onSuccess: (data) => {
setLogin(data.data);
},
...props
}
);
const { isSuccess, data: response } = states;
useEffect(() => {
if (isSuccess) { setLogin(response.data); }
}, [isSuccess, response, setLogin]);
return states;
};
/**

View File

@@ -21,3 +21,5 @@ export * from './users';
export * from './invite';
export * from './exchangeRates';
export * from './contacts';
export * from './subscriptions';
export * from './organization';

View File

@@ -1,8 +1,8 @@
import { useMutation } from 'react-query';
import { useMutation, useQueryClient } from 'react-query';
import { batch } from 'react-redux';
import t from './types';
import useApiRequest from '../useRequest';
import { useRequestQuery } from '../useQueryRequest';
import { useEffect } from 'react';
import { useSetOrganizations, useSetSubscriptions } from '../state';
import { omit } from 'lodash';
@@ -33,29 +33,26 @@ export function useCurrentOrganization(props) {
const setOrganizations = useSetOrganizations();
const setSubscriptions = useSetSubscriptions();
const query = useRequestQuery(
return useRequestQuery(
[t.ORGANIZATION_CURRENT],
{ method: 'get', url: `organization/current` },
{
select: (res) => res.data.organization,
defaultData: {},
...props,
},
);
onSuccess: (data) => {
const organization = omit(data, ['subscriptions']);
useEffect(() => {
if (query.isSuccess) {
const organization = omit(query.data, ['subscriptions']);
batch(() => {
// Sets subscriptions.
setSubscriptions(data.subscriptions);
// Sets organizations.
setOrganizations([organization]);
// Sets subscriptions.
setSubscriptions(query.data.subscriptions);
}
}, [query.data, query.isSuccess, setOrganizations, setSubscriptions]);
return query;
});
},
...props,
},
);
}
/**
@@ -63,30 +60,46 @@ export function useCurrentOrganization(props) {
*/
export function useBuildTenant(props) {
const apiRequest = useApiRequest();
const queryClient = useQueryClient();
return useMutation(
(values) => apiRequest.post('organization/build'),
{
return useMutation((values) => apiRequest.post('organization/build'), {
onSuccess: (res, values) => {
queryClient.invalidateQueries(t.ORGANIZATION_CURRENT);
queryClient.invalidateQueries(t.ORGANIZATIONS);
},
...props,
},
);
};
});
}
/**
* Seeds the current tenant
*/
export function useSeedTenant() {
const apiRequest = useApiRequest();
const queryClient = useQueryClient();
return useMutation((values) => apiRequest.post('organization/seed'), {
onSuccess: (res) => {
queryClient.invalidateQueries(t.ORGANIZATION_CURRENT);
queryClient.invalidateQueries(t.ORGANIZATIONS);
},
});
}
/**
* Organization setup.
*/
export function useOrganizationSetup() {
const apiRequest = useApiRequest();
const queryClient = useQueryClient();
return useMutation(
(values) => apiRequest.post('organization/seed'),
(values) => apiRequest.post(`setup/organization`, values),
{
onSuccess: (res) => {
queryClient.invalidateQueries(t.ORGANIZATION_CURRENT);
queryClient.invalidateQueries(t.ORGANIZATIONS);
},
},
);
}
)
};

View File

@@ -0,0 +1,44 @@
import { useEffect } from "react"
import { useMutation, useQueryClient } from "react-query";
import { useRequestQuery } from "../useQueryRequest";
import useApiRequest from "../useRequest";
import { useSetSubscriptions } from '../state/subscriptions';
import T from './types';
/**
* Subscription payment via voucher.
*/
export const usePaymentByVoucher = (props) => {
const apiRequest = useApiRequest();
const queryClient = useQueryClient();
return useMutation(
(values) => apiRequest.post('subscription/license/payment', values),
{
onSuccess: () => {
queryClient.invalidateQueries(T.SUBSCRIPTIONS);
queryClient.invalidateQueries(T.ORGANIZATION_CURRENT);
queryClient.invalidateQueries(T.ORGANIZATIONS);
},
...props,
}
);
}
/**
* Fetches the organization subscriptions.
*/
export const useOrganizationSubscriptions = (props) => {
const setSubscriptions = useSetSubscriptions();
const state = useRequestQuery(
[T.SUBSCRIPTIONS],
{ method: 'get', url: 'subscriptions' },
{ ...props },
);
useEffect(() => {
if (state.isSuccess) {
setSubscriptions(state.data);
}
}, [state.isSuccess, state.data, setSubscriptions])
};

View File

@@ -94,6 +94,10 @@ const ORGANIZATIONS = {
ORGANIZATION_CURRENT: 'ORGANIZATION_CURRENT',
};
const SUBSCRIPTIONS = {
SUBSCRIPTIONS: 'SUBSCRIPTIONS',
}
export default {
...ACCOUNTS,
...BILLS,
@@ -111,4 +115,5 @@ export default {
...USERS,
...SETTING,
...ORGANIZATIONS,
...SUBSCRIPTIONS
};

View File

@@ -1,7 +1,8 @@
import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist';
import { persistReducer, purgeStoredState } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { createTableStateReducers } from 'store/tableState.reducer';
import t from 'store/types';
const initialState = {
tableState: {
@@ -12,15 +13,21 @@ const initialState = {
const STORAGE_KEY = 'bigcapital:bills';
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('BILLS'),
});
export default persistReducer(
{
const CONFIG = {
key: STORAGE_KEY,
whitelist: ['tableState'],
storage,
},
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('BILLS'),
[t.RESET]: () => {
purgeStoredState(CONFIG);
}
});
export default persistReducer(
CONFIG,
reducerInstance,
);

View File

@@ -1,9 +1,10 @@
import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist';
import { persistReducer, purgeStoredState } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import {
createTableStateReducers,
} from 'store/tableState.reducer';
import t from 'store/types';
const initialState = {
tableState: {
@@ -12,17 +13,23 @@ const initialState = {
},
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('ESTIMATES'),
});
const STORAGE_KEY = 'bigcapital:estimates';
export default persistReducer(
{
const CONFIG = {
key: STORAGE_KEY,
whitelist: ['tableState'],
storage,
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('ESTIMATES'),
[t.RESET]: () => {
purgeStoredState(CONFIG);
},
});
export default persistReducer(
CONFIG,
reducerInstance,
);

View File

@@ -1,9 +1,10 @@
import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist';
import { persistReducer, purgeStoredState } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import {
createTableStateReducers,
} from 'store/tableState.reducer';
import t from 'store/types';
const initialState = {
tableState: {
@@ -12,17 +13,23 @@ const initialState = {
},
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('INVOICES'),
});
const STORAGE_KEY = 'bigcapital:invoices';
export default persistReducer(
{
const CONFIG = {
key: STORAGE_KEY,
whitelist: ['tableState'],
storage,
},
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('INVOICES'),
[t.RESET]: () => {
purgeStoredState(CONFIG);
}
});
export default persistReducer(
CONFIG,
reducerInstance,
);

View File

@@ -1,9 +1,10 @@
import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist';
import { persistReducer, purgeStoredState } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import {
createTableStateReducers,
} from 'store/tableState.reducer';
import t from 'store/types';
const initialState = {
tableState: {
@@ -13,17 +14,20 @@ const initialState = {
},
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('PAYMENT_MADES'),
});
const STORAGE_KEY = 'bigcapital:paymentMades';
export default persistReducer(
{
const CONFIG = {
key: STORAGE_KEY,
whitelist: ['tableState'],
storage,
}
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('PAYMENT_MADES'),
[t.RESET]: () => {
purgeStoredState(CONFIG);
},
reducerInstance,
);
});
export default persistReducer(CONFIG, reducerInstance);

View File

@@ -1,9 +1,10 @@
import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist';
import { persistReducer, purgeStoredState } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import {
createTableStateReducers,
} from 'store/tableState.reducer';
import t from 'store/types';
const initialState = {
tableState: {
@@ -12,17 +13,23 @@ const initialState = {
},
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('PAYMENT_RECEIVES'),
});
const STORAGE_KEY = 'bigcapital:paymentReceives';
export default persistReducer(
{
const CONFIG = {
key: STORAGE_KEY,
whitelist: ['tableState'],
storage,
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('PAYMENT_RECEIVES'),
[t.RESET]: () => {
purgeStoredState(CONFIG);
},
});
export default persistReducer(
CONFIG,
reducerInstance,
);

View File

@@ -1,25 +1,27 @@
import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist';
import { persistReducer, purgeStoredState } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import {
createTableStateReducers,
} from 'store/tableState.reducer';
import { createTableStateReducers } from 'store/tableState.reducer';
import t from 'store/types';
const initialState = {
tableState: {},
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('ACCOUNTS'),
});
const STORAGE_KEY = 'bigcapital:accounts';
export default persistReducer(
{
const CONFIG = {
key: STORAGE_KEY,
whitelist: ['tableState'],
storage,
},
reducerInstance,
);
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('ACCOUNTS'),
[t.RESET]: () => {
purgeStoredState(CONFIG);
}
});
export default persistReducer(CONFIG, reducerInstance);

View File

@@ -1,5 +1,6 @@
import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist';
import purgeStoredState from 'redux-persist/es/purgeStoredState';
import storage from 'redux-persist/lib/storage';
import t from 'store/types';
@@ -14,10 +15,16 @@ const initialState = {
};
const STORAGE_KEY = 'bigcapital:authentication';
const CONFIG = {
key: STORAGE_KEY,
blacklist: ['errors'],
storage,
};
const reducerInstance = createReducer(initialState, {
[t.LOGIN_SUCCESS]: (state, action) => {
const { token, user, tenant } = action.payload;
state.token = token;
state.user = user;
state.organization = tenant.organization_id;
@@ -32,14 +39,14 @@ const reducerInstance = createReducer(initialState, {
[t.LOGIN_CLEAR_ERRORS]: (state) => {
state.errors = [];
},
[t.RESET]: () => {
purgeStoredState(CONFIG);
}
});
export default persistReducer(
{
key: STORAGE_KEY,
blacklist: ['errors'],
storage,
},
CONFIG,
reducerInstance,
);

View File

@@ -1,6 +1,6 @@
import t from 'store/types';
import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist';
import { persistReducer, purgeStoredState } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
const initialState = {
@@ -20,6 +20,12 @@ const initialState = {
const STORAGE_KEY = 'bigcapital:dashboard';
const CONFIG = {
key: STORAGE_KEY,
whitelist: ['sidebarExpended', 'previousSidebarExpended'],
storage,
};
const reducerInstance = createReducer(initialState, {
[t.CHANGE_DASHBOARD_PAGE_TITLE]: (state, action) => {
state.pageTitle = action.pageTitle;
@@ -115,19 +121,13 @@ const reducerInstance = createReducer(initialState, {
const { backLink } = action.payload;
state.backLink = backLink;
},
[t.RESET]: () => {
purgeStoredState(CONFIG);
},
});
export default persistReducer(
{
key: STORAGE_KEY,
whitelist: [
'sidebarExpended',
'previousSidebarExpended',
],
storage,
},
reducerInstance,
);
export default persistReducer(CONFIG, reducerInstance);
export const getDialogPayload = (state, dialogName) => {
return typeof state.dashboard.dialogs[dialogName] !== 'undefined'

View File

@@ -1,7 +1,8 @@
import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist';
import { persistReducer, purgeStoredState } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { createTableStateReducers } from 'store/tableState.reducer';
import t from 'store/types';
const initialState = {
tableState: {
@@ -10,17 +11,20 @@ const initialState = {
},
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('EXPENSES'),
});
const STORAGE_KEY = 'bigcapital:expenses';
export default persistReducer(
{
const CONFIG = {
key: STORAGE_KEY,
whitelist: ['tableState'],
storage,
},
reducerInstance,
);
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('EXPENSES'),
[t.RESET]: () => {
purgeStoredState(CONFIG);
}
});
export default persistReducer(CONFIG, reducerInstance);

View File

@@ -1,7 +1,8 @@
import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist';
import { persistReducer, purgeStoredState } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { createTableStateReducers } from 'store/tableState.reducer';
import t from 'store/types';
const initialState = {
tableState: {
@@ -12,17 +13,20 @@ const initialState = {
selectedRows: [],
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('INVENTORY_ADJUSTMENTS'),
});
const STORAGE_KEY = 'bigcapital:inventoryAdjustments';
export default persistReducer(
{
const CONFIG = {
key: STORAGE_KEY,
whitelist: ['tableState'],
storage,
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('INVENTORY_ADJUSTMENTS'),
[t.RESET]: () => {
purgeStoredState(CONFIG);
},
reducerInstance,
);
});
export default persistReducer(CONFIG, reducerInstance);

View File

@@ -1,27 +1,33 @@
import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist';
import { persistReducer, purgeStoredState } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import {
createTableStateReducers,
} from 'store/tableState.reducer';
import t from 'store/types';
// Initial state.
const initialState = {
tableState: {},
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('ITEMS_CATEGORIES'),
});
const STORAGE_KEY = 'bigcapital:itemCategories';
export default persistReducer(
{
const CONFIG = {
key: STORAGE_KEY,
whitelist: ['tableState'],
storage,
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('ITEMS_CATEGORIES'),
[t.RESET]: () => {
purgeStoredState(CONFIG);
},
});
export default persistReducer(
CONFIG,
reducerInstance,
);

View File

@@ -1,7 +1,8 @@
import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist';
import { persistReducer, purgeStoredState } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { createTableStateReducers } from 'store/tableState.reducer';
import t from 'store/types';
const initialState = {
tableState: {
@@ -12,17 +13,23 @@ const initialState = {
selectedRows: [],
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('ITEMS'),
});
const STORAGE_KEY = 'bigcapital:items';
export default persistReducer(
{
const CONFIG = {
key: STORAGE_KEY,
whitelist: ['tableState'],
storage,
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('ITEMS'),
[t.RESET]: () => {
purgeStoredState(CONFIG);
},
});
export default persistReducer(
CONFIG,
reducerInstance,
);

View File

@@ -1,7 +1,8 @@
import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist';
import { persistReducer, purgeStoredState } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { createTableStateReducers } from 'store/tableState.reducer';
import t from 'store/types';
const initialState = {
tableState: {
@@ -10,17 +11,20 @@ const initialState = {
},
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('MANUAL_JOURNALS'),
});
const STORAGE_KEY = 'bigcapital:manualJournals';
export default persistReducer(
{
const CONFIG = {
key: STORAGE_KEY,
whitelist: ['tableState'],
storage,
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('MANUAL_JOURNALS'),
[t.RESET]: () => {
purgeStoredState(CONFIG);
},
reducerInstance,
);
});
export default persistReducer(CONFIG, reducerInstance);

View File

@@ -14,7 +14,10 @@ const reducer = createReducer(initialState, {
const _dataByOrganizationId = {};
organizations.forEach((organization) => {
_data[organization.id] = organization;
_data[organization.id] = {
...state.data[organization.id],
...organization,
};
_dataByOrganizationId[organization.organization_id] = organization.id;
});
state.data = _data;

View File

@@ -6,16 +6,28 @@ export default (mapState) => {
isOrganizationSetupCompleted,
isOrganizationInitialized,
isOrganizationSeeded,
isSubscriptionActive
} = props;
const mapped = {
const condits = {
isCongratsStep: isOrganizationSetupCompleted,
isSubscriptionStep: !isSubscriptionActive,
isInitializingStep: isSubscriptionActive && !isOrganizationInitialized,
isOrganizationStep: isOrganizationInitialized && !isOrganizationSeeded,
};
const scenarios = [
{ condition: condits.isCongratsStep, step: 'congrats' },
{ condition: condits.isSubscriptionStep, step: 'subscription' },
{ condition: condits.isInitializingStep, step: 'initializing' },
{ condition: condits.isOrganizationStep, step: 'organization' },
];
const setupStep = scenarios.find((scenario) => scenario.condition);
const mapped = {
...condits,
setupStepId: setupStep?.step,
setupStepIndex: scenarios.indexOf(setupStep) ,
};
return mapState ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);

View File

@@ -1,9 +1,10 @@
import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist';
import { persistReducer, purgeStoredState } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import {
createTableStateReducers,
} from 'store/tableState.reducer';
import t from 'store/types';
const initialState = {
tableState: {
@@ -12,17 +13,23 @@ const initialState = {
},
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('RECEIPTS'),
});
const STORAGE_KEY = 'bigcapital:receipts';
export default persistReducer(
{
const CONFIG = {
key: STORAGE_KEY,
whitelist: ['tableState'],
storage,
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('RECEIPTS'),
[t.RESET]: () => {
purgeStoredState(CONFIG);
},
});
export default persistReducer(
CONFIG,
reducerInstance,
);

View File

@@ -1,11 +1,11 @@
import { camelCase } from 'lodash';
import { createReducer } from '@reduxjs/toolkit';
import t from 'store/types';
import { optionsArrayToMap } from 'utils';
const initialState = {
data: {
organization: {
name: 'Bigcapital, Limited Liabilities',
name: 'Bigcapital, LLC',
},
manualJournals: {},
bills: {},

View File

@@ -22,3 +22,4 @@ export const setSubscriptions = (subscriptions) => {
},
}
};

View File

@@ -1,9 +1,8 @@
import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist';
import { persistReducer, purgeStoredState } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import {
createTableStateReducers,
} from 'store/tableState.reducer';
import { createTableStateReducers } from 'store/tableState.reducer';
import t from 'store/types';
const initialState = {
tableState: {
@@ -11,17 +10,21 @@ const initialState = {
pageIndex: 0,
},
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('VENDORS'),
});
const STORAGE_KEY = 'bigcapital:vendors';
export default persistReducer(
{
const CONFIG = {
key: STORAGE_KEY,
whitelist: ['tableState'],
storage,
},
reducerInstance,
);
};
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('VENDORS'),
[t.RESET]: () => {
purgeStoredState(CONFIG);
}
});
export default persistReducer(CONFIG, reducerInstance);

View File

@@ -13,7 +13,7 @@
color: #565e6c;
}
.paragraph {
opacity: 0.75;
opacity: 0.8;
}
}
@@ -38,6 +38,10 @@
}
}
label.bp3-label{
color: #313744;
}
.form-group--language {
margin-left: 10px;
}

View File

@@ -30,7 +30,7 @@
font-weight: 600;
}
&__period{
color: #666;
color: #2f3863;
font-size: 14px;
font-weight: 500;

View File

@@ -60,7 +60,7 @@
}
&__period {
font-weight: 400;
color: #666;
color: #2f3863;
&::before {
content: '/';

View File

@@ -8,8 +8,10 @@ import {
NoPaymentModelWithPricedPlan,
PaymentAmountInvalidWithPlan,
PaymentInputInvalid,
VoucherCodeRequired
} from 'exceptions';
import { ILicensePaymentModel } from 'interfaces';
import instance from 'tsyringe/dist/typings/dependency-container';
@Service()
export default class PaymentViaLicenseController extends PaymentMethodController {
@@ -67,6 +69,11 @@ export default class PaymentViaLicenseController extends PaymentMethodController
} catch (exception) {
const errorReasons = [];
if (exception instanceof VoucherCodeRequired) {
errorReasons.push({
type: 'VOUCHER_CODE_REQUIRED', code: 100,
});
}
if (exception instanceof NoPaymentModelWithPricedPlan) {
errorReasons.push({
type: 'NO_PAYMENT_WITH_PRICED_PLAN',

View File

@@ -56,6 +56,7 @@ export default () => {
app.use('/organization', Container.get(Organization).router());
app.use('/ping', Container.get(Ping).router());
app.use('/setup', Container.get(Setup).router());
// - Dashboard routes.
// ---------------------------
const dashboard = Router();