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-preset-env": "6.7.0",
"postcss-safe-parser": "4.0.1", "postcss-safe-parser": "4.0.1",
"react": "^16.12.0", "react": "^16.12.0",
"react-albus": "^2.0.0",
"react-app-polyfill": "^1.0.6", "react-app-polyfill": "^1.0.6",
"react-body-classname": "^1.3.1", "react-body-classname": "^1.3.1",
"react-content-loader": "^6.0.1", "react-content-loader": "^6.0.1",

View File

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

View File

@@ -11,7 +11,7 @@ export default function BigcapitalLoading({ className }) {
return ( return (
<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={228} />
</div> </div>
</div> </div>
); );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,9 @@
import React from 'react'; 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 SetupDialogs from './SetupDialogs';
import SetupWizardContent from './SetupWizardContent'; import SetupWizardContent from './SetupWizardContent';
import withSubscriptions from 'containers/Subscriptions/withSubscriptions';
import withOrganization from 'containers/Organization/withOrganization'; import withOrganization from 'containers/Organization/withOrganization';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization'; import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
import withSetupWizard from '../../store/organizations/withSetupWizard'; import withSetupWizard from '../../store/organizations/withSetupWizard';
@@ -24,37 +20,17 @@ function SetupRightSection({
isOrganizationSetupCompleted, isOrganizationSetupCompleted,
// #withSetupWizard // #withSetupWizard
isCongratsStep, setupStepId,
isSubscriptionStep, setupStepIndex,
isInitializingStep,
isOrganizationStep,
// #withSubscriptions // #withSubscriptions
isSubscriptionActive, 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 ( return (
<section className={'setup-page__right-section'}> <section className={'setup-page__right-section'}>
<Wizard <SetupWizardContent
onNext={handleSkip} setupStepId={setupStepId}
basename={'/setup'} setupStepIndex={setupStepIndex}
history={history}
render={SetupWizardContent}
/> />
<SetupDialogs /> <SetupDialogs />
</section> </section>
@@ -84,17 +60,8 @@ export default compose(
}), }),
'main', 'main',
), ),
withSetupWizard( withSetupWizard(({ setupStepId, setupStepIndex }) => ({
({ setupStepId,
isCongratsStep, setupStepIndex,
isSubscriptionStep, })),
isInitializingStep,
isOrganizationStep,
}) => ({
isCongratsStep,
isSubscriptionStep,
isInitializingStep,
isOrganizationStep,
}),
),
)(SetupRightSection); )(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 React from 'react';
import { Formik } from 'formik'; import { Formik } from 'formik';
import { withWizard } from 'react-albus';
import 'style/pages/Setup/Subscription.scss'; import 'style/pages/Setup/Subscription.scss';
import SetupSubscriptionForm from './SetupSubscriptionForm'; import SetupSubscriptionForm from './SetupSubscriptionForm';
import { SubscriptionFormSchema } from './SubscriptionForm.schema'; import { SubscriptionFormSchema } from './SubscriptionForm.schema';
import { compose } from 'utils';
/** /**
* Subscription step of wizard setup. * Subscription step of wizard setup.
*/ */
function SetupSubscription({ export default function SetupSubscription() {
// #withWizard
wizard,
}) {
// Initial values. // Initial values.
const initialValues = { const initialValues = {
plan_slug: 'free', plan_slug: 'free',
@@ -23,6 +17,7 @@ function SetupSubscription({
license_code: '', license_code: '',
}; };
// Handle form submit.
const handleSubmit = () => {}; const handleSubmit = () => {};
return ( return (
@@ -36,7 +31,3 @@ function SetupSubscription({
</div> </div>
); );
} }
export default compose(
withWizard,
)(SetupSubscription);

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
import { useEffect } from 'react';
import { useMutation } from 'react-query'; import { useMutation } from 'react-query';
import useApiRequest from '../useRequest'; import useApiRequest from '../useRequest';
import { useAuthActions } from '../state'; import { useAuthActions } from '../state';
@@ -10,20 +9,16 @@ export const useAuthLogin = (props) => {
const { setLogin } = useAuthActions(); const { setLogin } = useAuthActions();
const apiRequest = useApiRequest(); const apiRequest = useApiRequest();
const states = useMutation( return useMutation(
(values) => apiRequest.post('auth/login', values), (values) => apiRequest.post('auth/login', values),
{ {
select: (res) => res.data, select: (res) => res.data,
onSuccess: (data) => {
setLogin(data.data);
},
...props ...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 './invite';
export * from './exchangeRates'; export * from './exchangeRates';
export * from './contacts'; 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 t from './types';
import useApiRequest from '../useRequest'; import useApiRequest from '../useRequest';
import { useRequestQuery } from '../useQueryRequest'; import { useRequestQuery } from '../useQueryRequest';
import { useEffect } from 'react';
import { useSetOrganizations, useSetSubscriptions } from '../state'; import { useSetOrganizations, useSetSubscriptions } from '../state';
import { omit } from 'lodash'; import { omit } from 'lodash';
@@ -33,29 +33,26 @@ export function useCurrentOrganization(props) {
const setOrganizations = useSetOrganizations(); const setOrganizations = useSetOrganizations();
const setSubscriptions = useSetSubscriptions(); const setSubscriptions = useSetSubscriptions();
const query = useRequestQuery( return useRequestQuery(
[t.ORGANIZATION_CURRENT], [t.ORGANIZATION_CURRENT],
{ method: 'get', url: `organization/current` }, { method: 'get', url: `organization/current` },
{ {
select: (res) => res.data.organization, select: (res) => res.data.organization,
defaultData: {}, defaultData: {},
onSuccess: (data) => {
const organization = omit(data, ['subscriptions']);
batch(() => {
// Sets subscriptions.
setSubscriptions(data.subscriptions);
// Sets organizations.
setOrganizations([organization]);
});
},
...props, ...props,
}, },
); );
useEffect(() => {
if (query.isSuccess) {
const organization = omit(query.data, ['subscriptions']);
// Sets organizations.
setOrganizations([organization]);
// Sets subscriptions.
setSubscriptions(query.data.subscriptions);
}
}, [query.data, query.isSuccess, setOrganizations, setSubscriptions]);
return query;
} }
/** /**
@@ -63,30 +60,46 @@ export function useCurrentOrganization(props) {
*/ */
export function useBuildTenant(props) { export function useBuildTenant(props) {
const apiRequest = useApiRequest(); const apiRequest = useApiRequest();
const queryClient = useQueryClient();
return useMutation( return useMutation((values) => apiRequest.post('organization/build'), {
(values) => apiRequest.post('organization/build'), onSuccess: (res, values) => {
{ queryClient.invalidateQueries(t.ORGANIZATION_CURRENT);
onSuccess: (res, values) => { queryClient.invalidateQueries(t.ORGANIZATIONS);
},
...props,
}, },
); ...props,
}; });
}
/** /**
* Seeds the current tenant * Seeds the current tenant
*/ */
export function useSeedTenant() { export function useSeedTenant() {
const apiRequest = useApiRequest(); 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( return useMutation(
(values) => apiRequest.post('organization/seed'), (values) => apiRequest.post(`setup/organization`, values),
{ {
onSuccess: (res) => { 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', ORGANIZATION_CURRENT: 'ORGANIZATION_CURRENT',
}; };
const SUBSCRIPTIONS = {
SUBSCRIPTIONS: 'SUBSCRIPTIONS',
}
export default { export default {
...ACCOUNTS, ...ACCOUNTS,
...BILLS, ...BILLS,
@@ -111,4 +115,5 @@ export default {
...USERS, ...USERS,
...SETTING, ...SETTING,
...ORGANIZATIONS, ...ORGANIZATIONS,
...SUBSCRIPTIONS
}; };

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,25 +1,27 @@
import { createReducer} from '@reduxjs/toolkit'; import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist'; import { persistReducer, purgeStoredState } from 'redux-persist';
import storage from 'redux-persist/lib/storage'; import storage from 'redux-persist/lib/storage';
import { import { createTableStateReducers } from 'store/tableState.reducer';
createTableStateReducers, import t from 'store/types';
} from 'store/tableState.reducer';
const initialState = { const initialState = {
tableState: {}, tableState: {},
}; };
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('ACCOUNTS'),
});
const STORAGE_KEY = 'bigcapital:accounts'; const STORAGE_KEY = 'bigcapital:accounts';
export default persistReducer( const CONFIG = {
{ key: STORAGE_KEY,
key: STORAGE_KEY, whitelist: ['tableState'],
whitelist: ['tableState'], storage,
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 { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist'; import { persistReducer } from 'redux-persist';
import purgeStoredState from 'redux-persist/es/purgeStoredState';
import storage from 'redux-persist/lib/storage'; import storage from 'redux-persist/lib/storage';
import t from 'store/types'; import t from 'store/types';
@@ -14,10 +15,16 @@ const initialState = {
}; };
const STORAGE_KEY = 'bigcapital:authentication'; const STORAGE_KEY = 'bigcapital:authentication';
const CONFIG = {
key: STORAGE_KEY,
blacklist: ['errors'],
storage,
};
const reducerInstance = createReducer(initialState, { const reducerInstance = createReducer(initialState, {
[t.LOGIN_SUCCESS]: (state, action) => { [t.LOGIN_SUCCESS]: (state, action) => {
const { token, user, tenant } = action.payload; const { token, user, tenant } = action.payload;
state.token = token; state.token = token;
state.user = user; state.user = user;
state.organization = tenant.organization_id; state.organization = tenant.organization_id;
@@ -32,14 +39,14 @@ const reducerInstance = createReducer(initialState, {
[t.LOGIN_CLEAR_ERRORS]: (state) => { [t.LOGIN_CLEAR_ERRORS]: (state) => {
state.errors = []; state.errors = [];
}, },
[t.RESET]: () => {
purgeStoredState(CONFIG);
}
}); });
export default persistReducer( export default persistReducer(
{ CONFIG,
key: STORAGE_KEY,
blacklist: ['errors'],
storage,
},
reducerInstance, reducerInstance,
); );

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,8 @@
import { createReducer } from '@reduxjs/toolkit'; import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist'; import { persistReducer, purgeStoredState } from 'redux-persist';
import storage from 'redux-persist/lib/storage'; 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 = { const initialState = {
tableState: { tableState: {
@@ -10,17 +11,20 @@ const initialState = {
}, },
}; };
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('MANUAL_JOURNALS'),
});
const STORAGE_KEY = 'bigcapital:manualJournals'; const STORAGE_KEY = 'bigcapital:manualJournals';
export default persistReducer( const CONFIG = {
{ key: STORAGE_KEY,
key: STORAGE_KEY, whitelist: ['tableState'],
whitelist: ['tableState'], storage,
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 = {}; const _dataByOrganizationId = {};
organizations.forEach((organization) => { organizations.forEach((organization) => {
_data[organization.id] = organization; _data[organization.id] = {
...state.data[organization.id],
...organization,
};
_dataByOrganizationId[organization.organization_id] = organization.id; _dataByOrganizationId[organization.organization_id] = organization.id;
}); });
state.data = _data; state.data = _data;

View File

@@ -6,16 +6,28 @@ export default (mapState) => {
isOrganizationSetupCompleted, isOrganizationSetupCompleted,
isOrganizationInitialized, isOrganizationInitialized,
isOrganizationSeeded, isOrganizationSeeded,
isSubscriptionActive isSubscriptionActive
} = props; } = props;
const mapped = { const condits = {
isCongratsStep: isOrganizationSetupCompleted, isCongratsStep: isOrganizationSetupCompleted,
isSubscriptionStep: !isSubscriptionActive, isSubscriptionStep: !isSubscriptionActive,
isInitializingStep: isSubscriptionActive && !isOrganizationInitialized, isInitializingStep: isSubscriptionActive && !isOrganizationInitialized,
isOrganizationStep: isOrganizationInitialized && !isOrganizationSeeded, 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 mapState ? mapState(mapped, state, props) : mapped;
}; };
return connect(mapStateToProps); return connect(mapStateToProps);

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,8 @@
import { createReducer } from '@reduxjs/toolkit'; import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist'; import { persistReducer, purgeStoredState } from 'redux-persist';
import storage from 'redux-persist/lib/storage'; import storage from 'redux-persist/lib/storage';
import { import { createTableStateReducers } from 'store/tableState.reducer';
createTableStateReducers, import t from 'store/types';
} from 'store/tableState.reducer';
const initialState = { const initialState = {
tableState: { tableState: {
@@ -11,17 +10,21 @@ const initialState = {
pageIndex: 0, pageIndex: 0,
}, },
}; };
const reducerInstance = createReducer(initialState, {
...createTableStateReducers('VENDORS'),
});
const STORAGE_KEY = 'bigcapital:vendors'; const STORAGE_KEY = 'bigcapital:vendors';
export default persistReducer( const CONFIG = {
{ key: STORAGE_KEY,
key: STORAGE_KEY, whitelist: ['tableState'],
whitelist: ['tableState'], storage,
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; color: #565e6c;
} }
.paragraph { .paragraph {
opacity: 0.75; opacity: 0.8;
} }
} }
@@ -38,6 +38,10 @@
} }
} }
label.bp3-label{
color: #313744;
}
.form-group--language { .form-group--language {
margin-left: 10px; margin-left: 10px;
} }

View File

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

View File

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

View File

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

View File

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