mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 12:50:38 +00:00
feat: ensure to access dashboard user's subscription is active.
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Formik } from 'formik';
|
||||
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
import * as Yup from 'yup';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import Toaster from 'components/AppToaster';
|
||||
|
||||
import 'style/pages/Setup/PaymentViaVoucherDialog.scss';
|
||||
@@ -14,7 +15,6 @@ import PaymentViaLicenseForm from './PaymentViaVoucherForm';
|
||||
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import { compose } from 'utils';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
|
||||
/**
|
||||
* Payment via license dialog content.
|
||||
@@ -35,10 +35,11 @@ function PaymentViaLicenseDialogContent({
|
||||
const handleSubmit = (values, { setSubmitting, setErrors }) => {
|
||||
setSubmitting(true);
|
||||
|
||||
// Payment via voucher mutate.
|
||||
paymentViaVoucherMutate({ ...values })
|
||||
.then(() => {
|
||||
Toaster.show({
|
||||
message: intl.get('payment_has_been_done_successfully'),
|
||||
message: intl.get('payment_via_voucher.success_message'),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
return closeDialog('payment-via-voucher');
|
||||
@@ -54,7 +55,7 @@ function PaymentViaLicenseDialogContent({
|
||||
}) => {
|
||||
if (errors.find((e) => e.type === 'LICENSE.CODE.IS.INVALID')) {
|
||||
setErrors({
|
||||
license_code: 'The license code is not valid, please try agin.',
|
||||
license_code: 'payment_via_voucher.license_code_not_valid',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -31,7 +31,7 @@ function PaymentViaLicenseForm({
|
||||
<Form>
|
||||
<div className={CLASSES.DIALOG_BODY}>
|
||||
<p>
|
||||
<T id={'Pleasse enter your voucher number that you received from reseller.'} />
|
||||
<T id={'payment_via_voucher.dialog.description'} />
|
||||
</p>
|
||||
|
||||
<FastField name="license_code">
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import AppToaster from 'components/AppToaster';
|
||||
|
||||
import withGlobalErrors from './withGlobalErrors';
|
||||
@@ -17,29 +19,31 @@ function GlobalErrors({
|
||||
globalErrorsSet,
|
||||
}) {
|
||||
if (globalErrors.something_wrong) {
|
||||
toastKeySessionExpired = AppToaster.show({
|
||||
message: intl.get('ops_something_went_wrong'),
|
||||
intent: Intent.DANGER,
|
||||
onDismiss: () => {
|
||||
globalErrorsSet({ something_wrong: false });
|
||||
}
|
||||
}, toastKeySessionExpired);
|
||||
toastKeySessionExpired = AppToaster.show(
|
||||
{
|
||||
message: intl.get('ops_something_went_wrong'),
|
||||
intent: Intent.DANGER,
|
||||
onDismiss: () => {
|
||||
globalErrorsSet({ something_wrong: false });
|
||||
},
|
||||
},
|
||||
toastKeySessionExpired,
|
||||
);
|
||||
}
|
||||
|
||||
if (globalErrors.session_expired) {
|
||||
toastKeySomethingWrong = AppToaster.show({
|
||||
message: intl.get('session_expired'),
|
||||
intent: Intent.DANGER,
|
||||
onDismiss: () => {
|
||||
globalErrorsSet({ session_expired: false });
|
||||
}
|
||||
}, toastKeySomethingWrong);
|
||||
toastKeySomethingWrong = AppToaster.show(
|
||||
{
|
||||
message: intl.get('session_expired'),
|
||||
intent: Intent.DANGER,
|
||||
onDismiss: () => {
|
||||
globalErrorsSet({ session_expired: false });
|
||||
},
|
||||
},
|
||||
toastKeySomethingWrong,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withGlobalErrors,
|
||||
withGlobalErrorsActions,
|
||||
)(GlobalErrors);
|
||||
export default compose(withGlobalErrors, withGlobalErrorsActions)(GlobalErrors);
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { Button, Intent } from '@blueprintjs/core';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import WorkflowIcon from './WorkflowIcon';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import withOrganizationActions from 'containers/Organization/withOrganizationActions';
|
||||
import { compose } from 'utils';
|
||||
|
||||
import 'style/pages/Setup/Congrats.scss';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Setup congrats page.
|
||||
@@ -28,15 +30,15 @@ function SetupCongratsPage({ setOrganizationSetupCompleted }) {
|
||||
|
||||
<div class="setup-congrats__text">
|
||||
<h1>
|
||||
<T id={'congrats_you_are_ready_to_go'} />
|
||||
<T id={'setup.congrats.title'} />
|
||||
</h1>
|
||||
|
||||
<p class="paragraph">
|
||||
<T id={'it_is_a_long_established_fact_that_a_reader'} />
|
||||
<T id={'setup.congrats.description'} />
|
||||
</p>
|
||||
|
||||
<Button intent={Intent.PRIMARY} type="submit" onClick={handleBtnClick}>
|
||||
<T id={'go_to_dashboard'} />
|
||||
<T id={'setup.congrats.go_to_dashboard'} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function SetupInitializingForm() {
|
||||
isError,
|
||||
} = useBuildTenant();
|
||||
|
||||
useEffect(() => {
|
||||
React.useEffect(() => {
|
||||
buildTenantMutate();
|
||||
}, [buildTenantMutate]);
|
||||
|
||||
@@ -27,32 +27,32 @@ export default function SetupInitializingForm() {
|
||||
{isLoading ? (
|
||||
<>
|
||||
<h1>
|
||||
<T id={'it_s_time_to_make_your_accounting_really_simple'} />
|
||||
<T id={'setup.initializing.title'} />
|
||||
</h1>
|
||||
<p className={'paragraph'}>
|
||||
<T
|
||||
id={
|
||||
'while_we_set_up_your_account_please_remember_to_verify_your_account'
|
||||
}
|
||||
/>
|
||||
<T id={'setup.initializing.description'} />
|
||||
</p>
|
||||
</>
|
||||
) : isError ? (
|
||||
<>
|
||||
<h1>
|
||||
<T id={'something_went_wrong'} />
|
||||
<T id={'setup.initializing.something_went_wrong'} />
|
||||
</h1>
|
||||
<p class="paragraph">
|
||||
<T id={'please_refresh_the_page'} />
|
||||
<T id={'setup.initializing.please_refresh_the_page'} />
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h1>
|
||||
<T id={'waiting_to_redirect'} />
|
||||
<T id={'setup.initializing.waiting_to_redirect'} />
|
||||
</h1>
|
||||
<p class="paragraph">
|
||||
<T id={'refresh_the_page_if_redirect_not_worked'} />
|
||||
<T
|
||||
id={
|
||||
'setup.initializing.refresh_the_page_if_redirect_not_worked'
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -3,11 +3,14 @@ import { Icon, For } from 'components';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import { getFooterLinks } from 'config/footerLinks';
|
||||
import { useAuthActions, useAuthOrganizationId } from 'hooks/state';
|
||||
import { useAuthActions } from 'hooks/state';
|
||||
|
||||
/**
|
||||
* Footer item link.
|
||||
*/
|
||||
function FooterLinkItem({ title, link }) {
|
||||
return (
|
||||
<div class="">
|
||||
<div class="content__links-item">
|
||||
<a href={link} target="_blank">
|
||||
{title}
|
||||
</a>
|
||||
@@ -16,20 +19,65 @@ function FooterLinkItem({ title, link }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wizard setup left section.
|
||||
* Setup left section footer.
|
||||
*/
|
||||
export default function SetupLeftSection() {
|
||||
const { setLogout } = useAuthActions();
|
||||
const organizationId = useAuthOrganizationId();
|
||||
|
||||
function SetupLeftSectionFooter() {
|
||||
// Retrieve the footer links.
|
||||
const footerLinks = getFooterLinks();
|
||||
|
||||
return (
|
||||
<div className={'content__footer'}>
|
||||
<div className={'content__contact-info'}>
|
||||
<p>
|
||||
<T id={'setup.left_side.footer_help'} />{' '}
|
||||
<span>{'+21892-738-1987'}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={'content__links'}>
|
||||
<For render={FooterLinkItem} of={footerLinks} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup left section header.
|
||||
*/
|
||||
function SetupLeftSectionHeader() {
|
||||
const { setLogout } = useAuthActions();
|
||||
|
||||
// Handle logout link click.
|
||||
const onClickLogout = () => {
|
||||
setLogout();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={'content__header'}>
|
||||
<h1 className={'content__title'}>
|
||||
<T id={'setup.left_side.title'} />
|
||||
</h1>
|
||||
|
||||
<p className={'content__text'}>
|
||||
<T id={'setup.left_side.description'} />
|
||||
</p>
|
||||
<div class="content__divider"></div>
|
||||
|
||||
<div className={'content__organization'}>
|
||||
<span class="signout">
|
||||
<a onClick={onClickLogout} href="#">
|
||||
<T id={'sign_out'} />
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wizard setup left section.
|
||||
*/
|
||||
export default function SetupLeftSection() {
|
||||
return (
|
||||
<section className={'setup-page__left-section'}>
|
||||
<div className={'content'}>
|
||||
@@ -41,40 +89,8 @@ export default function SetupLeftSection() {
|
||||
width={190}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h1 className={'content__title'}>
|
||||
<T id={'register_a_new_organization_now'} />
|
||||
</h1>
|
||||
|
||||
<p className={'content__text'}>
|
||||
<T id={'you_have_a_bigcapital_account'} />
|
||||
</p>
|
||||
<span class="content__divider"></span>
|
||||
|
||||
<div className={'content__organization'}>
|
||||
<span class="organization-id">
|
||||
<T id={'organization_id'} />:{' '}
|
||||
<span class="id">{organizationId}</span>,
|
||||
</span>
|
||||
<br />
|
||||
<span class="signout">
|
||||
<a onClick={onClickLogout} href="#">
|
||||
<T id={'sign_out'} />
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className={'content__footer'}>
|
||||
<div className={'content__contact-info'}>
|
||||
<p>
|
||||
<T id={'we_re_here_to_help'} /> <span>{'+21892-738-1987'}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={'content__links'}>
|
||||
<For render={FooterLinkItem} of={footerLinks} />
|
||||
</div>
|
||||
</div>
|
||||
<SetupLeftSectionHeader />
|
||||
<SetupLeftSectionFooter />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
15
client/src/containers/Setup/SetupOrganization.schema.js
Normal file
15
client/src/containers/Setup/SetupOrganization.schema.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as Yup from 'yup';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
// Retrieve the setup organization form validation.
|
||||
export const getSetupOrganizationValidation = () =>
|
||||
Yup.object().shape({
|
||||
organization_name: Yup.string()
|
||||
.required()
|
||||
.label(intl.get('organization_name_')),
|
||||
financialDateStart: Yup.date().required().label(intl.get('date_start_')),
|
||||
baseCurrency: Yup.string().required().label(intl.get('base_currency_')),
|
||||
language: Yup.string().required().label(intl.get('language')),
|
||||
fiscalYear: Yup.string().required().label(intl.get('fiscal_year_')),
|
||||
timeZone: Yup.string().required().label(intl.get('time_zone_')),
|
||||
});
|
||||
@@ -14,20 +14,18 @@ import classNames from 'classnames';
|
||||
import { TimezonePicker } from '@blueprintjs/timezone';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import { FieldRequiredHint, Col, Row, ListSelect } from 'components';
|
||||
import { Col, Row, ListSelect } from 'components';
|
||||
import {
|
||||
momentFormatter,
|
||||
tansformDateValue,
|
||||
inputIntent,
|
||||
handleDateChange
|
||||
handleDateChange,
|
||||
} from 'utils';
|
||||
|
||||
import { getFiscalYear } from 'common/fiscalYearOptions';
|
||||
import { getLanguages } from 'common/languagesOptions';
|
||||
import { getCurrencies } from 'common/currencies';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Setup organization form.
|
||||
*/
|
||||
@@ -46,22 +44,24 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
|
||||
<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 })}
|
||||
helperText={<ErrorMessage name={'organization_name'} />}
|
||||
>
|
||||
<InputGroup {...field} />
|
||||
<InputGroup {...field} intent={inputIntent({ error, touched })} />
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
|
||||
{/* ---------- Financial starting date ---------- */}
|
||||
<FastField name={'financialDateStart'}>
|
||||
{({ form: { setFieldValue }, field: { value }, meta: { error, touched } }) => (
|
||||
{({
|
||||
form: { setFieldValue },
|
||||
field: { value },
|
||||
meta: { error, touched },
|
||||
}) => (
|
||||
<FormGroup
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
label={<T id={'financial_starting_date'} />}
|
||||
intent={inputIntent({ error, touched })}
|
||||
helperText={<ErrorMessage name="financialDateStart" />}
|
||||
@@ -74,6 +74,7 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
|
||||
onChange={handleDateChange((formattedDate) => {
|
||||
setFieldValue('financialDateStart', formattedDate);
|
||||
})}
|
||||
intent={inputIntent({ error, touched })}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
@@ -89,7 +90,6 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
|
||||
meta: { error, touched },
|
||||
}) => (
|
||||
<FormGroup
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
label={<T id={'base_currency'} />}
|
||||
className={classNames(
|
||||
'form-group--base-currency',
|
||||
@@ -101,7 +101,9 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
|
||||
>
|
||||
<ListSelect
|
||||
items={Currencies}
|
||||
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
|
||||
noResults={
|
||||
<MenuItem disabled={true} text={<T id={'no_results'} />} />
|
||||
}
|
||||
popoverProps={{ minimal: true }}
|
||||
onItemSelect={(item) => {
|
||||
setFieldValue('baseCurrency', item.code);
|
||||
@@ -110,6 +112,7 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
|
||||
textProp={'name'}
|
||||
defaultText={<T id={'select_base_currency'} />}
|
||||
selectedItem={value}
|
||||
intent={inputIntent({ error, touched })}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
@@ -136,7 +139,9 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
|
||||
>
|
||||
<ListSelect
|
||||
items={Languages}
|
||||
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
|
||||
noResults={
|
||||
<MenuItem disabled={true} text={<T id={'no_results'} />} />
|
||||
}
|
||||
onItemSelect={(item) => {
|
||||
setFieldValue('language', item.value);
|
||||
}}
|
||||
@@ -146,6 +151,7 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
|
||||
defaultText={<T id={'select_language'} />}
|
||||
popoverProps={{ minimal: true }}
|
||||
filterable={false}
|
||||
intent={inputIntent({ error, touched })}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
@@ -154,9 +160,12 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
|
||||
</Row>
|
||||
{/* --------- Fiscal Year ----------- */}
|
||||
<FastField name={'fiscalYear'}>
|
||||
{({ form: { setFieldValue }, field: { value }, meta: { error, touched } }) => (
|
||||
{({
|
||||
form: { setFieldValue },
|
||||
field: { value },
|
||||
meta: { error, touched },
|
||||
}) => (
|
||||
<FormGroup
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
label={<T id={'fiscal_year'} />}
|
||||
className={classNames(
|
||||
'form-group--fiscal_year',
|
||||
@@ -168,14 +177,16 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
|
||||
>
|
||||
<ListSelect
|
||||
items={FiscalYear}
|
||||
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
|
||||
noResults={
|
||||
<MenuItem disabled={true} text={<T id={'no_results'} />} />
|
||||
}
|
||||
selectedItem={value}
|
||||
selectedItemProp={'value'}
|
||||
textProp={'name'}
|
||||
defaultText={<T id={'select_fiscal_year'} />}
|
||||
popoverProps={{ minimal: true }}
|
||||
onItemSelect={(item) => {
|
||||
setFieldValue('fiscalYear', item.value)
|
||||
setFieldValue('fiscalYear', item.value);
|
||||
}}
|
||||
filterable={false}
|
||||
/>
|
||||
@@ -191,7 +202,6 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
|
||||
meta: { error, touched },
|
||||
}) => (
|
||||
<FormGroup
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
label={<T id={'time_zone'} />}
|
||||
className={classNames(
|
||||
'form-group--time-zone',
|
||||
@@ -216,20 +226,11 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
|
||||
</FastField>
|
||||
|
||||
<p className={'register-org-note'}>
|
||||
<T
|
||||
id={
|
||||
'note_you_can_change_your_preferences_later_in_dashboard_if_needed'
|
||||
}
|
||||
/>
|
||||
<T id={'setup.organization.note_you_can_change_your_preferences'} />
|
||||
</p>
|
||||
|
||||
<div className={'register-org-button'}>
|
||||
<Button
|
||||
intent={Intent.PRIMARY}
|
||||
disabled={isSubmitting}
|
||||
loading={isSubmitting}
|
||||
type="submit"
|
||||
>
|
||||
<Button intent={Intent.PRIMARY} loading={isSubmitting} type="submit">
|
||||
<T id={'save_continue'} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import { Formik } from 'formik';
|
||||
import moment from 'moment';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
|
||||
import 'style/pages/Setup/Organization.scss';
|
||||
|
||||
@@ -13,53 +12,30 @@ import { useOrganizationSetup } from 'hooks/query';
|
||||
import withSettingsActions from 'containers/Settings/withSettingsActions';
|
||||
import withOrganizationActions from 'containers/Organization/withOrganizationActions';
|
||||
|
||||
import {
|
||||
compose,
|
||||
transfromToSnakeCase,
|
||||
} from 'utils';
|
||||
import { compose, transfromToSnakeCase } from 'utils';
|
||||
import { getSetupOrganizationValidation } from './SetupOrganization.schema';
|
||||
|
||||
|
||||
// Initial values.
|
||||
const defaultValues = {
|
||||
organization_name: '',
|
||||
financialDateStart: moment(new Date()).format('YYYY-MM-DD'),
|
||||
baseCurrency: '',
|
||||
language: 'en',
|
||||
fiscalYear: '',
|
||||
timeZone: '',
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup organization form.
|
||||
*/
|
||||
function SetupOrganizationPage({
|
||||
wizard,
|
||||
setOrganizationSetupCompleted,
|
||||
}) {
|
||||
|
||||
function SetupOrganizationPage({ wizard, setOrganizationSetupCompleted }) {
|
||||
const { mutateAsync: organizationSetupMutate } = useOrganizationSetup();
|
||||
|
||||
// Validation schema.
|
||||
const validationSchema = Yup.object().shape({
|
||||
organization_name: Yup.string()
|
||||
.required()
|
||||
.label(intl.get('organization_name_')),
|
||||
financialDateStart: Yup.date()
|
||||
.required()
|
||||
.label(intl.get('date_start_')),
|
||||
baseCurrency: Yup.string()
|
||||
.required()
|
||||
.label(intl.get('base_currency_')),
|
||||
language: Yup.string()
|
||||
.required()
|
||||
.label(intl.get('language')),
|
||||
fiscalYear: Yup.string()
|
||||
.required()
|
||||
.label(intl.get('fiscal_year_')),
|
||||
timeZone: Yup.string()
|
||||
.required()
|
||||
.label(intl.get('time_zone_')),
|
||||
});
|
||||
|
||||
// Initial values.
|
||||
const defaultValues = {
|
||||
organization_name: '',
|
||||
financialDateStart: moment(new Date()).format('YYYY-MM-DD'),
|
||||
baseCurrency: '',
|
||||
language: 'en',
|
||||
fiscalYear: '',
|
||||
timeZone: '',
|
||||
};
|
||||
const validationSchema = getSetupOrganizationValidation();
|
||||
|
||||
// Initialize values.
|
||||
const initialValues = {
|
||||
...defaultValues,
|
||||
};
|
||||
@@ -83,10 +59,10 @@ function SetupOrganizationPage({
|
||||
<div className={'setup-organization'}>
|
||||
<div className={'setup-organization__title-wrap'}>
|
||||
<h1>
|
||||
<T id={'let_s_get_started'} />
|
||||
<T id={'setup.organization.title'} />
|
||||
</h1>
|
||||
<p class="paragraph">
|
||||
<T id={'tell_the_system_a_little_bit_about_your_organization'} />
|
||||
<T id={'setup.organization.description'} />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as R from 'ramda';
|
||||
|
||||
import 'style/pages/Setup/Subscription.scss';
|
||||
|
||||
import SetupSubscriptionForm from './SetupSubscriptionForm';
|
||||
import SetupSubscriptionForm from './SetupSubscription/SetupSubscriptionForm';
|
||||
import { getSubscriptionFormSchema } from './SubscriptionForm.schema';
|
||||
import withSubscriptionPlansActions from '../Subscriptions/withSubscriptionPlansActions';
|
||||
|
||||
@@ -13,13 +13,11 @@ import withSubscriptionPlansActions from '../Subscriptions/withSubscriptionPlans
|
||||
*/
|
||||
function SetupSubscription({
|
||||
// #withSubscriptionPlansActions
|
||||
initSubscriptionPlans
|
||||
initSubscriptionPlans,
|
||||
}) {
|
||||
React.useEffect(() => {
|
||||
initSubscriptionPlans();
|
||||
}, [
|
||||
initSubscriptionPlans
|
||||
]);
|
||||
}, [initSubscriptionPlans]);
|
||||
|
||||
// Initial values.
|
||||
const initialValues = {
|
||||
@@ -30,10 +28,14 @@ function SetupSubscription({
|
||||
// Handle form submit.
|
||||
const handleSubmit = () => {};
|
||||
|
||||
const SubscriptionFormSchema = getSubscriptionFormSchema();
|
||||
// Retrieve momerized subscription form schema.
|
||||
const SubscriptionFormSchema = React.useMemo(
|
||||
() => getSubscriptionFormSchema(),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={'setup-subscription-form'}>
|
||||
<div className={'setup-subscription-form'}>
|
||||
<Formik
|
||||
validationSchema={SubscriptionFormSchema}
|
||||
initialValues={initialValues}
|
||||
@@ -44,6 +46,4 @@ function SetupSubscription({
|
||||
);
|
||||
}
|
||||
|
||||
export default R.compose(
|
||||
withSubscriptionPlansActions,
|
||||
)(SetupSubscription);
|
||||
export default R.compose(withSubscriptionPlansActions)(SetupSubscription);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
import SubscriptionPlansSection from './SubscriptionPlansSection';
|
||||
import SubscriptionPeriodsSection from './SubscriptionPeriodsSection';
|
||||
import SubscriptionPaymentMethodsSection from './SubscriptionPaymentsMethodsSection';
|
||||
|
||||
|
||||
export default function SetupSubscriptionForm() {
|
||||
return (
|
||||
<div class="billing-plans">
|
||||
<SubscriptionPlansSection />
|
||||
<SubscriptionPeriodsSection />
|
||||
<SubscriptionPaymentMethodsSection />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { T } from 'components';
|
||||
|
||||
import { PaymentMethodTabs } from '../../Subscriptions/SubscriptionTabs';
|
||||
|
||||
export default ({ formik, title, description }) => {
|
||||
return (
|
||||
<section class="billing-plans__section">
|
||||
<h1 className="title">
|
||||
<T id={'setup.plans.payment_methods.title'} />
|
||||
</h1>
|
||||
<p className="paragraph">
|
||||
<T id={'setup.plans.payment_methods.description'} />
|
||||
</p>
|
||||
|
||||
<PaymentMethodTabs formik={formik} />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import { Field } from 'formik';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { T, SubscriptionPeriods } from 'components';
|
||||
|
||||
import withPlan from '../../Subscriptions/withPlan';
|
||||
|
||||
const SubscriptionPeriodsEnhanced = R.compose(
|
||||
withPlan(({ plan }) => ({ plan })),
|
||||
)(({ plan, ...restProps }) => {
|
||||
return <SubscriptionPeriods periods={plan.periods} {...restProps} />;
|
||||
});
|
||||
|
||||
/**
|
||||
* Billing periods.
|
||||
*/
|
||||
export default function SubscriptionPeriodsSection() {
|
||||
return (
|
||||
<section class="billing-plans__section">
|
||||
<h1 class="title">
|
||||
<T id={'setup.plans.select_period.title'} />
|
||||
</h1>
|
||||
<div class="description">
|
||||
<p className="paragraph">
|
||||
<T id={'setup.plans.select_period.description'} />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Field name={'period'}>
|
||||
{({ form: { setFieldValue, values }, field: { value } }) => (
|
||||
<SubscriptionPeriodsEnhanced
|
||||
planSlug={values.plan_slug}
|
||||
selectedPeriod={value}
|
||||
onPeriodSelect={(period) => {
|
||||
setFieldValue('period', period);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import { Field } from 'formik';
|
||||
|
||||
import { T, SubscriptionPlans } from 'components';
|
||||
|
||||
import { compose } from 'utils';
|
||||
import withPlans from '../../Subscriptions/withPlans';
|
||||
|
||||
/**
|
||||
* Billing plans.
|
||||
*/
|
||||
function SubscriptionPlansSection({ plans }) {
|
||||
return (
|
||||
<section class="billing-plans__section">
|
||||
<h1 class="title">
|
||||
<T id={'setup.plans.select_plan.title'} />
|
||||
</h1>
|
||||
<div class="description">
|
||||
<p className="paragraph">
|
||||
<T id={'setup.plans.select_plan.description'} />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Field name={'plan_slug'}>
|
||||
{({ form: { setFieldValue }, field: { value } }) => (
|
||||
<SubscriptionPlans
|
||||
value={value}
|
||||
plans={plans}
|
||||
onSelect={(value) => {
|
||||
setFieldValue('plan_slug', value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withPlans(({ plans }) => ({ plans })))(
|
||||
SubscriptionPlansSection,
|
||||
);
|
||||
@@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Form } from 'formik';
|
||||
|
||||
import BillingPlansForm from 'containers/Subscriptions/BillingPlansForm';
|
||||
|
||||
/**
|
||||
* Subscription step of wizard setup.
|
||||
*/
|
||||
export default function SetupSubscriptionForm() {
|
||||
return (
|
||||
<Form>
|
||||
<BillingPlansForm />
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@@ -5,9 +5,7 @@ import { getSetupWizardSteps } from 'common/registerWizard';
|
||||
function WizardSetupStep({ label, isActive = false }) {
|
||||
return (
|
||||
<li className={classNames({ 'is-active': isActive })}>
|
||||
<p className={'wizard-info'}>
|
||||
{ label }
|
||||
</p>
|
||||
<p className={'wizard-info'}>{label}</p>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import { Formik, Form } from 'formik';
|
||||
import intl from 'react-intl-universal';
|
||||
import { If, Alert, T } from 'components';
|
||||
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
|
||||
import 'style/pages/Billing/BillingPage.scss';
|
||||
@@ -10,8 +11,11 @@ import { MasterBillingTabs } from './SubscriptionTabs';
|
||||
|
||||
import withBillingActions from './withBillingActions';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withSubscriptionPlansActions from './withSubscriptionPlansActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
import { getBillingFormValidationSchema } from './utils';
|
||||
import withSubscriptions from './withSubscriptions';
|
||||
|
||||
/**
|
||||
* Billing form.
|
||||
@@ -20,26 +24,30 @@ function BillingForm({
|
||||
// #withDashboardActions
|
||||
changePageTitle,
|
||||
|
||||
//#withBillingActions
|
||||
// #withBillingActions
|
||||
requestSubmitBilling,
|
||||
|
||||
initSubscriptionPlans,
|
||||
|
||||
// #withSubscriptions
|
||||
isSubscriptionInactive,
|
||||
}) {
|
||||
useEffect(() => {
|
||||
changePageTitle(intl.get('billing'));
|
||||
}, [changePageTitle]);
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
plan_slug: Yup.string()
|
||||
.required(),
|
||||
period: Yup.string().required(),
|
||||
license_code: Yup.string().trim(),
|
||||
});
|
||||
React.useEffect(() => {
|
||||
initSubscriptionPlans();
|
||||
}, [initSubscriptionPlans]);
|
||||
|
||||
// Initial values.
|
||||
const initialValues = {
|
||||
plan_slug: 'free',
|
||||
period: 'month',
|
||||
license_code: '',
|
||||
};
|
||||
|
||||
// Handle form submitting.
|
||||
const handleSubmit = (values, { setSubmitting }) => {
|
||||
requestSubmitBilling(values)
|
||||
.then((response) => {
|
||||
@@ -53,20 +61,34 @@ function BillingForm({
|
||||
return (
|
||||
<DashboardInsider name={'billing-page'}>
|
||||
<div className={'billing-page'}>
|
||||
<If condition={isSubscriptionInactive}>
|
||||
<Alert
|
||||
intent={'danger'}
|
||||
title={<T id={'billing.suspend_message.title'} />}
|
||||
description={<T id={'billing.suspend_message.description'} />}
|
||||
/>
|
||||
</If>
|
||||
|
||||
<Formik
|
||||
validationSchema={validationSchema}
|
||||
validationSchema={getBillingFormValidationSchema()}
|
||||
onSubmit={handleSubmit}
|
||||
initialValues={initialValues}
|
||||
>
|
||||
{({ isSubmitting, handleSubmit }) => (
|
||||
<Form>
|
||||
<MasterBillingTabs />
|
||||
</Form>
|
||||
)}
|
||||
<Form>
|
||||
<MasterBillingTabs />
|
||||
</Form>
|
||||
</Formik>
|
||||
</div>
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDashboardActions, withBillingActions)(BillingForm);
|
||||
export default compose(
|
||||
withDashboardActions,
|
||||
withBillingActions,
|
||||
withSubscriptionPlansActions,
|
||||
withSubscriptions(
|
||||
({ isSubscriptionInactive }) => ({ isSubscriptionInactive }),
|
||||
'main',
|
||||
),
|
||||
)(BillingForm);
|
||||
|
||||
@@ -15,12 +15,14 @@ import { saveInvoke } from 'utils';
|
||||
function BillingPeriod({
|
||||
// #ownProps
|
||||
label,
|
||||
currencyCode,
|
||||
value,
|
||||
selectedOption,
|
||||
onSelected,
|
||||
period,
|
||||
|
||||
// #withPlan
|
||||
price,
|
||||
currencyCode,
|
||||
}) {
|
||||
const handlePeriodClick = () => {
|
||||
saveInvoke(onSelected, value);
|
||||
|
||||
@@ -1,42 +1,46 @@
|
||||
import React from 'react';
|
||||
import { Field } from 'formik';
|
||||
import BillingPeriod from './BillingPeriod';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import withPlans from './withPlans';
|
||||
import { T, SubscriptionPeriods } from 'components';
|
||||
|
||||
import { compose } from 'utils';
|
||||
import withPlan from './withPlan';
|
||||
|
||||
/**
|
||||
* Sunscription periods enhanced.
|
||||
*/
|
||||
const SubscriptionPeriodsEnhanced = R.compose(
|
||||
withPlan(({ plan }) => ({ plan })),
|
||||
)(({ plan, ...restProps }) => {
|
||||
return <SubscriptionPeriods periods={plan.periods} {...restProps} />;
|
||||
});
|
||||
|
||||
/**
|
||||
* Billing periods.
|
||||
*/
|
||||
function BillingPeriods({ title, description, plansPeriods }) {
|
||||
export default function BillingPeriods() {
|
||||
return (
|
||||
<section class="billing-plans__section">
|
||||
<h1 class="title">{title}</h1>
|
||||
<h1 class="title">
|
||||
<T id={'setup.plans.select_period.title'} />
|
||||
</h1>
|
||||
<div class="description">
|
||||
<p className="paragraph">{description}</p>
|
||||
<p className="paragraph">
|
||||
<T id={'setup.plans.select_period.description'} />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Field name={'period'}>
|
||||
{({ form: { setFieldValue, values } }) => (
|
||||
<div className={'plan-periods'}>
|
||||
{plansPeriods.map((period) => (
|
||||
<BillingPeriod
|
||||
planSlug={values.plan_slug}
|
||||
period={period.slug}
|
||||
label={period.label}
|
||||
value={period.slug}
|
||||
onSelected={(value) => setFieldValue('period', value)}
|
||||
selectedOption={values.period}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{({ field: { value }, form: { values, setFieldValue } }) => (
|
||||
<SubscriptionPeriodsEnhanced
|
||||
selectedPeriod={value}
|
||||
planSlug={values.plan_slug}
|
||||
onPeriodSelect={(period) => {
|
||||
setFieldValue('period', period);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withPlans(({ plansPeriods }) => ({ plansPeriods })))(
|
||||
BillingPeriods,
|
||||
);
|
||||
|
||||
@@ -3,8 +3,6 @@ import classNames from 'classnames';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import 'style/pages/Subscription/PlanRadio.scss';
|
||||
|
||||
import { saveInvoke } from 'utils';
|
||||
|
||||
/**
|
||||
@@ -32,9 +30,7 @@ export default function BillingPlan({
|
||||
onClick={handlePlanClick}
|
||||
>
|
||||
<div className={'plan-radio__header'}>
|
||||
<div className={'plan-radio__name'}>
|
||||
{intl.get('plan_radio_name', { name: name })}
|
||||
</div>
|
||||
<div className={'plan-radio__name'}>{name}</div>
|
||||
</div>
|
||||
|
||||
<div className={'plan-radio__description'}>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import 'style/pages/Subscription/BillingPlans.scss';
|
||||
|
||||
import BillingPlansInput from 'containers/Subscriptions/BillingPlansInput';
|
||||
import BillingPeriodsInput from 'containers/Subscriptions/BillingPeriodsInput';
|
||||
import BillingPaymentMethod from 'containers/Subscriptions/BillingPaymentMethod';
|
||||
import BillingPlansInput from './BillingPlansInput';
|
||||
import BillingPeriodsInput from './BillingPeriodsInput';
|
||||
import BillingPaymentMethod from './BillingPaymentMethod';
|
||||
|
||||
/**
|
||||
* Billing plans form.
|
||||
@@ -14,18 +12,9 @@ import BillingPaymentMethod from 'containers/Subscriptions/BillingPaymentMethod'
|
||||
export default function BillingPlansForm() {
|
||||
return (
|
||||
<div class="billing-plans">
|
||||
<BillingPlansInput
|
||||
title={intl.get('select_a_plan')}
|
||||
description={<T id={'please_enter_your_preferred_payment_method'} />}
|
||||
/>
|
||||
<BillingPeriodsInput
|
||||
title={intl.get('choose_your_billing')}
|
||||
description={<T id={'please_enter_your_preferred_payment_method'} />}
|
||||
/>
|
||||
<BillingPaymentMethod
|
||||
title={intl.get('payment_methods')}
|
||||
description={<T id={'please_enter_your_preferred_payment_method'} />}
|
||||
/>
|
||||
<BillingPlansInput />
|
||||
<BillingPeriodsInput />
|
||||
<BillingPaymentMethod />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { FastField, Field } from 'formik';
|
||||
import BillingPlan from './BillingPlan';
|
||||
import { Field } from 'formik';
|
||||
import { T, SubscriptionPlans } from 'components';
|
||||
|
||||
import withPlans from './withPlans';
|
||||
import { compose } from 'utils';
|
||||
@@ -11,27 +11,24 @@ import { compose } from 'utils';
|
||||
function BillingPlans({ plans, title, description, selectedOption }) {
|
||||
return (
|
||||
<section class="billing-plans__section">
|
||||
<h1 class="title">{title}</h1>
|
||||
<h1 class="title">
|
||||
<T id={'setup.plans.select_plan.title'} />
|
||||
</h1>
|
||||
<div class="description">
|
||||
<p className="paragraph">{description}</p>
|
||||
<p className="paragraph">
|
||||
<T id={'setup.plans.select_plan.description'} />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Field name={'plan_slug'}>
|
||||
{({ form: { setFieldValue }, field: { value } }) => (
|
||||
<div className={'plan-radios'}>
|
||||
{plans.map((plan) => (
|
||||
<BillingPlan
|
||||
name={plan.name}
|
||||
description={plan.description}
|
||||
slug={plan.slug}
|
||||
price={plan.price.month}
|
||||
currencyCode={plan.currencyCode}
|
||||
value={plan.slug}
|
||||
onSelected={(value) => setFieldValue('plan_slug', value)}
|
||||
selectedOption={value}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<SubscriptionPlans
|
||||
plans={plans}
|
||||
value={value}
|
||||
onSelect={(value) => {
|
||||
setFieldValue('plan_slug', value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</section>
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import React from 'react';
|
||||
import BillingPlansForm from 'containers/Subscriptions/BillingPlansForm';
|
||||
import BillingPlansForm from './BillingPlansForm';
|
||||
|
||||
export default function BillingTab() {
|
||||
return (
|
||||
<div>
|
||||
<BillingPlansForm />
|
||||
</div>
|
||||
);
|
||||
return (<BillingPlansForm />);
|
||||
}
|
||||
@@ -16,11 +16,7 @@ export const MasterBillingTabs = ({ formik }) => {
|
||||
id={'billing'}
|
||||
panel={<BillingTab formik={formik} />}
|
||||
/>
|
||||
<Tab
|
||||
title={intl.get('usage')}
|
||||
id={'usage'}
|
||||
disabled={true}
|
||||
/>
|
||||
<Tab title={intl.get('usage')} id={'usage'} disabled={true} />
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
@@ -43,11 +39,7 @@ export const PaymentMethodTabs = ({ formik }) => {
|
||||
id={'credit_card'}
|
||||
disabled={true}
|
||||
/>
|
||||
<Tab
|
||||
title={intl.get('paypal')}
|
||||
id={'paypal'}
|
||||
disabled={true}
|
||||
/>
|
||||
<Tab title={intl.get('paypal')} id={'paypal'} disabled={true} />
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
import { T } from 'components';
|
||||
import { PaymentMethodTabs } from './SubscriptionTabs';
|
||||
|
||||
export default ({ formik, title, description }) => {
|
||||
return (
|
||||
<section class="billing-plans__section">
|
||||
<h1 className="title">{ title }</h1>
|
||||
<p className="paragraph">{ description }</p>
|
||||
<h1 className="title"><T id={'setup.plans.payment_methods.title'} /></h1>
|
||||
<p className="paragraph"><T id={'setup.plans.payment_methods.description' } /></p>
|
||||
|
||||
<PaymentMethodTabs formik={formik} />
|
||||
</section>
|
||||
|
||||
8
client/src/containers/Subscriptions/utils.js
Normal file
8
client/src/containers/Subscriptions/utils.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import * as Yup from 'yup';
|
||||
|
||||
export const getBillingFormValidationSchema = () =>
|
||||
Yup.object().shape({
|
||||
plan_slug: Yup.string().required(),
|
||||
period: Yup.string().required(),
|
||||
license_code: Yup.string().trim(),
|
||||
});
|
||||
@@ -1,17 +1,14 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
getPlansSelector,
|
||||
getPlansPeriodsSelector,
|
||||
} from 'store/plans/plans.selectors';
|
||||
|
||||
export default (mapState) => {
|
||||
const mapStateToProps = (state, props) => {
|
||||
const getPlans = getPlansSelector();
|
||||
const getPlansPeriods = getPlansPeriodsSelector();
|
||||
|
||||
const mapped = {
|
||||
plans: getPlans(state, props),
|
||||
plansPeriods: getPlansPeriods(state, props),
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
|
||||
19
client/src/containers/Subscriptions/withSubscriptionss.js
Normal file
19
client/src/containers/Subscriptions/withSubscriptionss.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
isSubscriptionsInactiveFactory,
|
||||
isSubscriptionsActiveFactory
|
||||
} from 'store/subscription/subscription.selectors';
|
||||
|
||||
export default (mapState) => {
|
||||
const isSubscriptionsInactive = isSubscriptionsInactiveFactory();
|
||||
const isSubscriptionsActive = isSubscriptionsActiveFactory();
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const mapped = {
|
||||
isSubscriptionsInactive: isSubscriptionsInactive(state, props),
|
||||
isSubscriptionsActive: isSubscriptionsActive(state, props),
|
||||
};
|
||||
return (mapState) ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
return connect(mapStateToProps);
|
||||
};
|
||||
Reference in New Issue
Block a user