feat: billing page in dashboard and setup.

This commit is contained in:
a.bouhuolia
2021-01-31 13:16:01 +02:00
parent 732c3bbfd7
commit e093be0663
60 changed files with 1505 additions and 1073 deletions

View File

@@ -18,7 +18,7 @@ function SetupCongratsPage({
const handleBtnClick = useCallback(() => {
setOrganizationSetupCompleted(false);
history.push('/');
history.push('/homepage');
}, [
setOrganizationSetupCompleted,
history,

View File

@@ -0,0 +1,13 @@
import React from 'react';
import PaymentViaVoucherDialog from 'containers/Dialogs/PaymentViaVoucherDialog';
/**
* Setup dialogs.
*/
export default function SetupDialogs() {
return (
<div class="setup-dialogs">
<PaymentViaVoucherDialog dialogName={'payment-via-voucher'} />
</div>
)
}

View File

@@ -32,6 +32,7 @@ function SetupInitializingForm({
return (
<div class="setup-initializing-form">
<ProgressBar intent={Intent.PRIMARY} value={null} />
<div className={'setup-initializing-form__title'}>
<h1>
{/* You organization is initializin... */}

View File

@@ -1,9 +1,12 @@
import React, { useCallback } from 'react';
import { Icon, For } from 'components';
import { FormattedMessage as T } from 'react-intl';
import withAuthenticationActions from 'containers/Authentication/withAuthenticationActions';
import withAuthentication from 'containers/Authentication/withAuthentication';
import footerLinks from 'config/footerLinks';
import { compose } from 'utils';

View File

@@ -1,7 +1,5 @@
import React, { useMemo, useState, useCallback } from 'react';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import { Row, Col } from 'react-grid-system';
import React from 'react';
import { FastField, Form, ErrorMessage } from 'formik';
import {
Button,
Intent,
@@ -11,429 +9,218 @@ import {
Classes,
Position,
} from '@blueprintjs/core';
import moment from 'moment';
import { DateInput } from '@blueprintjs/datetime';
import classNames from 'classnames';
import { TimezonePicker } from '@blueprintjs/timezone';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { DateInput } from '@blueprintjs/datetime';
import { withWizard } from 'react-albus';
import { FormattedMessage as T } from 'react-intl';
import 'style/pages/Setup/Organization.scss';
import { Col, Row, ListSelect } from 'components';
import {
momentFormatter,
tansformDateValue,
inputIntent,
handleDateChange
} from 'utils';
import { momentFormatter, tansformDateValue } from 'utils';
import { ListSelect, ErrorMessage, FieldRequiredHint } from 'components';
import fiscalYearOptions from 'common/fiscalYearOptions';
import languages from 'common/languagesOptions';
import currencies from 'common/currencies';
import withSettingsActions from 'containers/Settings/withSettingsActions';
import withOrganizationActions from 'containers/Organization/withOrganizationActions';
import { compose, optionsMapToArray } from 'utils';
function SetupOrganizationForm({
requestSubmitOptions,
requestOrganizationSeed,
wizard,
setOrganizationSetupCompleted
}) {
const { formatMessage } = useIntl();
const [selected, setSelected] = useState();
const baseCurrency = [
{ id: 0, name: 'LYD - Libyan Dinar', value: 'LYD' },
{ id: 1, name: 'USD - American Dollar', value: 'USD' },
];
const languages = [
{ id: 0, name: 'English', value: 'en' },
{ id: 1, name: 'Arabic', value: 'ar' },
];
const fiscalYear = [
{
id: 0,
name: `${formatMessage({ id: 'january' })} - ${formatMessage({
id: 'december',
})}`,
value: 'january',
},
{
id: 1,
name: `${formatMessage({ id: 'february' })} - ${formatMessage({
id: 'january',
})}`,
value: 'february',
},
{
id: 2,
name: `${formatMessage({ id: 'march' })} - ${formatMessage({
id: 'february',
})}`,
value: 'March',
},
{
id: 3,
name: `${formatMessage({ id: 'april' })} - ${formatMessage({
id: 'march',
})}`,
value: 'april',
},
{
id: 4,
name: `${formatMessage({ id: 'may' })} - ${formatMessage({
id: 'april',
})}`,
value: 'may',
},
{
id: 5,
name: `${formatMessage({ id: 'june' })} - ${formatMessage({
id: 'may',
})}`,
value: 'june',
},
{
id: 6,
name: `${formatMessage({ id: 'july' })} - ${formatMessage({
id: 'june',
})}`,
value: 'july',
},
{
id: 7,
name: `${formatMessage({ id: 'august' })} - ${formatMessage({
id: 'july',
})}`,
value: 'August',
},
{
id: 8,
name: `${formatMessage({ id: 'september' })} - ${formatMessage({
id: 'august',
})}`,
value: 'september',
},
{
id: 9,
name: `${formatMessage({ id: 'october' })} - ${formatMessage({
id: 'november',
})}`,
value: 'october',
},
{
id: 10,
name: `${formatMessage({ id: 'november' })} - ${formatMessage({
id: 'october',
})}`,
value: 'november',
},
{
id: 11,
name: `${formatMessage({ id: 'december' })} - ${formatMessage({
id: 'november',
})}`,
value: 'december',
},
];
const ValidationSchema = Yup.object().shape({
name: Yup.string()
.required()
.label(formatMessage({ id: 'organization_name_' })),
financial_date_start: Yup.date()
.required()
.label(formatMessage({ id: 'date_start_' })),
base_currency: Yup.string()
.required()
.label(formatMessage({ id: 'base_currency_' })),
language: Yup.string()
.required()
.label(formatMessage({ id: 'language' })),
fiscal_year: Yup.string()
.required()
.label(formatMessage({ id: 'fiscal_year_' })),
time_zone: Yup.string()
.required()
.label(formatMessage({ id: 'time_zone_' })),
});
const initialValues = useMemo(
() => ({
name: '',
financial_date_start: moment(new Date()).format('YYYY-MM-DD'),
base_currency: '',
language: '',
fiscal_year: '',
time_zone: '',
}),
[],
);
const {
values,
errors,
touched,
handleSubmit,
setFieldValue,
getFieldProps,
isSubmitting,
} = useFormik({
enableReinitialize: true,
validationSchema: ValidationSchema,
initialValues: {
...initialValues,
},
onSubmit: (values, { setSubmitting, setErrors }) => {
const options = optionsMapToArray(values).map((option) => {
return { key: option.key, ...option, group: 'organization' };
});
requestSubmitOptions({ options })
.then(() => {
return requestOrganizationSeed();
})
.then(() => {
return setOrganizationSetupCompleted(true);
})
.then((response) => {
setSubmitting(false);
wizard.next();
})
.catch((erros) => {
setSubmitting(false);
});
},
});
const onItemsSelect = (filedName) => {
return (filed) => {
setSelected({
...selected,
[filedName]: filed,
});
setFieldValue(filedName, filed.value);
};
};
const filterItems = (query, item, _index, exactMatch) => {
const normalizedTitle = item.name.toLowerCase();
const normalizedQuery = query.toLowerCase();
if (exactMatch) {
return normalizedTitle === normalizedQuery;
} else {
return normalizedTitle.indexOf(normalizedQuery) >= 0;
}
};
const onItemRenderer = (item, { handleClick }) => (
<MenuItem key={item.id} text={item.name} onClick={handleClick} />
);
const handleTimeZoneChange = useCallback(
(time_zone) => {
setFieldValue('time_zone', time_zone);
},
[setFieldValue],
);
const handleDateChange = useCallback(
(date) => {
const formatted = moment(date).format('YYYY-MM-DD');
setFieldValue('financial_date_start', formatted);
},
[setFieldValue],
);
/**
* Setup organization form.
*/
export default function SetupOrganizationForm({ isSubmitting, values }) {
return (
<div className={'setup-organization'}>
<div className={'setup-organization__title-wrap'}>
<h1>
<T id={'let_s_get_started'} />
</h1>
<p class="paragraph">
<T id={'tell_the_system_a_little_bit_about_your_organization'} />
</p>
</div>
<Form>
<h3>
<T id={'organization_details'} />
</h3>
<form class="setup-organization__form" onSubmit={handleSubmit}>
<h3>
<T id={'organization_details'} />
</h3>
<FormGroup
label={<T id={'legal_organization_name'} />}
labelInfo={<FieldRequiredHint />}
className={'form-group--name'}
intent={errors.name && touched.name && Intent.DANGER}
helperText={<ErrorMessage {...{ errors, touched }} name={'name'} />}
>
<InputGroup
intent={errors.name && touched.name && Intent.DANGER}
{...getFieldProps('name')}
/>
</FormGroup>
{/* financial starting date */}
<FormGroup
label={<T id={'financial_starting_date'} />}
labelInfo={<FieldRequiredHint />}
intent={
errors.financial_date_start &&
touched.financial_date_start &&
Intent.DANGER
}
helperText={
<ErrorMessage
name="financial_date_start"
{...{ errors, touched }}
/>
}
className={classNames('form-group--select-list', Classes.FILL)}
>
<DateInput
{...momentFormatter('MMMM Do YYYY')}
value={tansformDateValue(values.financial_date_start)}
onChange={handleDateChange}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
/>
</FormGroup>
<Row>
{/* base currency */}
<Col width={300}>
<FormGroup
label={<T id={'base_currency'} />}
labelInfo={<FieldRequiredHint />}
className={classNames(
'form-group--base-currency',
'form-group--select-list',
Classes.LOADING,
Classes.FILL,
)}
intent={
errors.base_currency && touched.base_currency && Intent.DANGER
}
helperText={
<ErrorMessage name={'base_currency'} {...{ errors, touched }} />
}
>
<ListSelect
items={baseCurrency}
noResults={<MenuItem disabled={true} text="No results." />}
itemRenderer={onItemRenderer}
popoverProps={{ minimal: true }}
onItemSelect={onItemsSelect('base_currency')}
itemPredicate={filterItems}
selectedItem={values.base_currency}
selectedItemProp={'value'}
defaultText={<T id={'select_base_currency'} />}
textProp={'name'}
/>
</FormGroup>
</Col>
{/* language */}
<Col width={300}>
<FormGroup
label={<T id={'language'} />}
labelInfo={<FieldRequiredHint />}
className={classNames(
'form-group--language',
'form-group--select-list',
Classes.FILL,
)}
intent={errors.language && touched.language && Intent.DANGER}
helperText={
<ErrorMessage name={'language'} {...{ errors, touched }} />
}
>
<ListSelect
items={languages}
noResults={<MenuItem disabled={true} text="No results." />}
itemRenderer={onItemRenderer}
popoverProps={{ minimal: true }}
onItemSelect={onItemsSelect('language')}
itemPredicate={filterItems}
selectedItem={values.language}
selectedItemProp={'value'}
defaultText={<T id={'select_language'} />}
textProp={'name'}
/>
</FormGroup>
</Col>
</Row>
{/* fiscal Year */}
<FormGroup
label={<T id={'fiscal_year'} />}
labelInfo={<FieldRequiredHint />}
className={classNames(
'form-group--fiscal_year',
'form-group--select-list',
Classes.FILL,
)}
intent={errors.fiscal_year && touched.fiscal_year && Intent.DANGER}
helperText={
<ErrorMessage name={'fiscal_year'} {...{ errors, touched }} />
}
>
<ListSelect
items={fiscalYear}
noResults={<MenuItem disabled={true} text="No results." />}
itemRenderer={onItemRenderer}
popoverProps={{ minimal: true }}
onItemSelect={onItemsSelect('fiscal_year')}
itemPredicate={filterItems}
selectedItem={values.fiscal_year}
selectedItemProp={'value'}
defaultText={<T id={'select_fiscal_year'} />}
textProp={'name'}
/>
</FormGroup>
{/* Time zone */}
<FormGroup
label={<T id={'time_zone'} />}
labelInfo={<FieldRequiredHint />}
className={classNames(
'form-group--time-zone',
'form-group--select-list',
Classes.FILL,
)}
intent={errors.time_zone && touched.time_zone && Intent.DANGER}
helperText={
<ErrorMessage {...{ errors, touched }} name={'time_zone'} />
}
>
<TimezonePicker
value={values.time_zone}
onChange={handleTimeZoneChange}
valueDisplayFormat="composite"
showLocalTimezone={true}
placeholder={<T id={'select_time_zone'} />}
/>
</FormGroup>
<p className={'register-org-note'}>
<T
id={
'note_you_can_change_your_preferences_later_in_dashboard_if_needed'
}
/>
</p>
<div className={'register-org-button'}>
<Button
intent={Intent.PRIMARY}
disabled={isSubmitting}
loading={isSubmitting}
type="submit"
{/* ---------- Organization name ---------- */}
<FastField name={'name'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'legal_organization_name'} />}
className={'form-group--name'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'organization_name'} />}
>
<T id={'save_continue'} />
</Button>
</div>
</form>
</div>
<InputGroup {...field} />
</FormGroup>
)}
</FastField>
{/* ---------- Financial starting date ---------- */}
<FastField name={'financialDateStart'}>
{({ form: { setFieldValue }, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'financial_starting_date'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="financialDateStart" />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<DateInput
{...momentFormatter('YYYY MMMM DD')}
value={tansformDateValue(value)}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
onChange={handleDateChange((formattedDate) => {
setFieldValue('financialDateStart', formattedDate);
})}
/>
</FormGroup>
)}
</FastField>
<Row>
<Col xs={6}>
{/* ---------- Base currency ---------- */}
<FastField name={'baseCurrency'}>
{({
form: { setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'base_currency'} />}
className={classNames(
'form-group--base-currency',
'form-group--select-list',
Classes.FILL,
)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'baseCurrency'} />}
>
<ListSelect
items={currencies}
noResults={<MenuItem disabled={true} text="No results." />}
popoverProps={{ minimal: true }}
onItemSelect={(item) => {
setFieldValue('baseCurrency', item.code);
}}
selectedItemProp={'code'}
textProp={'label'}
defaultText={<T id={'select_base_currency'} />}
selectedItem={value}
/>
</FormGroup>
)}
</FastField>
</Col>
{/* ---------- Language ---------- */}
<Col xs={6}>
<FastField name={'language'}>
{({
form: { setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'language'} />}
className={classNames(
'form-group--language',
'form-group--select-list',
Classes.FILL,
)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'language'} />}
>
<ListSelect
items={languages}
noResults={<MenuItem disabled={true} text="No results." />}
onItemSelect={(item) => {
setFieldValue('language', item.value);
}}
selectedItem={value}
textProp={'name'}
selectedItemProp={'value'}
defaultText={<T id={'select_language'} />}
popoverProps={{ minimal: true }}
/>
</FormGroup>
)}
</FastField>
</Col>
</Row>
{/* --------- Fiscal Year ----------- */}
<FastField name={'fiscalYear'}>
{({ form: { setFieldValue }, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'fiscal_year'} />}
className={classNames(
'form-group--fiscal_year',
'form-group--select-list',
Classes.FILL,
)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'fiscalYear'} />}
>
<ListSelect
items={fiscalYearOptions}
noResults={<MenuItem disabled={true} text="No results." />}
selectedItem={value}
selectedItemProp={'value'}
textProp={'name'}
defaultText={<T id={'select_fiscal_year'} />}
popoverProps={{ minimal: true }}
onItemSelect={(item) => {
setFieldValue('fiscalYear', item.value)
}}
/>
</FormGroup>
)}
</FastField>
{/* ---------- Time zone ---------- */}
<FastField name={'timeZone'}>
{({
form: { setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'time_zone'} />}
className={classNames(
'form-group--time-zone',
'form-group--select-list',
Classes.FILL,
)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'timeZone'} />}
>
<TimezonePicker
value={value}
onChange={(item) => {
setFieldValue('timeZone', item);
}}
valueDisplayFormat="composite"
showLocalTimezone={true}
placeholder={<T id={'select_time_zone'} />}
popoverProps={{ minimal: true }}
/>
</FormGroup>
)}
</FastField>
<p className={'register-org-note'}>
<T
id={
'note_you_can_change_your_preferences_later_in_dashboard_if_needed'
}
/>
</p>
<div className={'register-org-button'}>
<Button
intent={Intent.PRIMARY}
disabled={isSubmitting}
loading={isSubmitting}
type="submit"
>
<T id={'save_continue'} />
</Button>
</div>
</Form>
);
}
export default compose(
withSettingsActions,
withOrganizationActions,
withWizard,
)(SetupOrganizationForm);

View File

@@ -0,0 +1,139 @@
import React from 'react';
import * as Yup from 'yup';
import { Formik } from 'formik';
import moment from 'moment';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { snakeCase } from 'lodash';
import { withWizard } from 'react-albus';
import { useQuery } from 'react-query';
import 'style/pages/Setup/Organization.scss';
import SetupOrganizationForm from './SetupOrganizationForm';
import withSettingsActions from 'containers/Settings/withSettingsActions';
import withSettings from 'containers/Settings/withSettings';
import withOrganizationActions from 'containers/Organization/withOrganizationActions';
import {
compose,
transformToForm,
optionsMapToArray,
} from 'utils';
/**
* Setup organization form.
*/
function SetupOrganizationPage({
// #withSettingsActions
requestSubmitOptions,
requestFetchOptions,
// #withOrganizationActions
requestOrganizationSeed,
// #withSettings
organizationSettings,
wizard,
setOrganizationSetupCompleted,
}) {
const { formatMessage } = useIntl();
const fetchSettings = useQuery(['settings'], () => requestFetchOptions({}));
// Validation schema.
const validationSchema = Yup.object().shape({
name: Yup.string()
.required()
.label(formatMessage({ id: 'organization_name_' })),
financialDateStart: Yup.date()
.required()
.label(formatMessage({ id: 'date_start_' })),
baseCurrency: Yup.string()
.required()
.label(formatMessage({ id: 'base_currency_' })),
language: Yup.string()
.required()
.label(formatMessage({ id: 'language' })),
fiscalYear: Yup.string()
.required()
.label(formatMessage({ id: 'fiscal_year_' })),
timeZone: Yup.string()
.required()
.label(formatMessage({ id: 'time_zone_' })),
});
// Initial values.
const defaultValues = {
name: '',
financialDateStart: moment(new Date()).format('YYYY-MM-DD'),
baseCurrency: '',
language: '',
fiscalYear: '',
timeZone: '',
...organizationSettings,
};
const initialValues = {
...defaultValues,
/**
* We only care about the fields in the form. Previously unfilled optional
* values such as `notes` come back from the API as null, so remove those
* as well.
*/
...transformToForm(organizationSettings, defaultValues),
};
// Handle the form submit.
const handleSubmit = (values, { setSubmitting, setErrors }) => {
const options = optionsMapToArray(values).map((option) => {
return { ...option, key: snakeCase(option.key), group: 'organization' };
});
requestSubmitOptions({ options })
.then(() => {
return requestOrganizationSeed();
})
.then(() => {
return setOrganizationSetupCompleted(true);
})
.then((response) => {
setSubmitting(false);
wizard.next();
})
.catch((erros) => {
setSubmitting(false);
});
};
return (
<div className={'setup-organization'}>
<div className={'setup-organization__title-wrap'}>
<h1>
<T id={'let_s_get_started'} />
</h1>
<p class="paragraph">
<T id={'tell_the_system_a_little_bit_about_your_organization'} />
</p>
</div>
<Formik
validationSchema={validationSchema}
initialValues={initialValues}
component={SetupOrganizationForm}
onSubmit={handleSubmit}
/>
</div>
);
}
export default compose(
withSettingsActions,
withOrganizationActions,
withWizard,
withSettings(({ organizationSettings }) => ({
organizationSettings,
})),
)(SetupOrganizationPage);

View File

@@ -1,56 +1,52 @@
import React, { useCallback } from 'react';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import { Wizard, Steps, Step } from 'react-albus';
import { useHistory } from "react-router-dom";
import { connect } from 'react-redux';
import React from 'react';
import { Wizard } from 'react-albus';
import { useHistory } from 'react-router-dom';
import WizardSetupSteps from './WizardSetupSteps';
import withSubscriptions from 'containers/Subscriptions/withSubscriptions';
import SetupSubscriptionForm from './SetupSubscriptionForm';
import SetupOrganizationForm from './SetupOrganizationForm';
import SetupInitializingForm from './SetupInitializingForm';
import SetupCongratsPage from './SetupCongratsPage';
import SetupDialogs from './SetupDialogs';
import SetupWizardContent from './SetupWizardContent';
import withOrganization from 'containers/Organization/withOrganization';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
import withSetupWizard from '../../store/organizations/withSetupWizard';
import withAuthentication from 'containers/Authentication/withAuthentication';
import withOrganization from 'containers/Organization/withOrganization'
import { compose } from 'utils';
/**
* Wizard setup right section.
*/
function SetupRightSection ({
// #withAuthentication
currentOrganizationId,
function SetupRightSection({
// #withOrganization
isOrganizationInitialized,
isOrganizationSeeded,
isOrganizationSetupCompleted,
// #withSetupWizard
isCongratsStep,
isSubscriptionStep,
isInitializingStep,
isOrganizationStep,
// #withSubscriptions
isSubscriptionActive
isSubscriptionActive,
}) {
const history = useHistory();
const handleSkip = useCallback(({ step, push }) => {
const handleSkip = ({ step, push }) => {
const scenarios = [
{ condition: isOrganizationSetupCompleted, redirectTo: 'congrats' },
{ condition: !isSubscriptionActive, redirectTo: 'subscription' },
{ condition: isSubscriptionActive && !isOrganizationInitialized, redirectTo: 'initializing' },
{ condition: isSubscriptionActive && !isOrganizationSeeded, redirectTo: 'organization' },
{ 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);
}
}, [
isSubscriptionActive,
isOrganizationInitialized,
isOrganizationSeeded,
isOrganizationSetupCompleted
]);
};
return (
<section className={'setup-page__right-section'}>
@@ -58,58 +54,47 @@ function SetupRightSection ({
onNext={handleSkip}
basename={'/setup'}
history={history}
render={({ step, steps }) => (
<div class="setup-page__content">
<WizardSetupSteps currentStep={steps.indexOf(step) + 1} />
<TransitionGroup>
<CSSTransition key={step.id} timeout={{ enter: 500, exit: 500 }}>
<div class="register-page-form">
<Steps key={step.id} step={step}>
<Step id="subscription">
<SetupSubscriptionForm />
</Step>
<Step id={'initializing'}>
<SetupInitializingForm />
</Step>
<Step id="organization">
<SetupOrganizationForm />
</Step>
<Step id="congrats">
<SetupCongratsPage />
</Step>
</Steps>
</div>
</CSSTransition>
</TransitionGroup>
</div>
)} />
render={SetupWizardContent}
/>
<SetupDialogs />
</section>
)
);
}
export default compose(
withAuthentication(({ currentOrganizationId }) => ({ currentOrganizationId })),
connect((state, props) => ({
organizationId: props.currentOrganizationId,
withCurrentOrganization(({ organizationTenantId }) => ({
organizationId: organizationTenantId,
})),
withOrganization(({
organization,
isOrganizationInitialized,
isOrganizationSeeded,
isOrganizationSetupCompleted
}) => ({
organization,
isOrganizationInitialized,
isOrganizationSeeded,
isOrganizationSetupCompleted
})),
withSubscriptions(({
isSubscriptionActive,
}) => ({
isSubscriptionActive
}), 'main'),
)(SetupRightSection);
withOrganization(
({
organization,
isOrganizationInitialized,
isOrganizationSeeded,
isOrganizationSetupCompleted,
}) => ({
organization,
isOrganizationInitialized,
isOrganizationSeeded,
isOrganizationSetupCompleted,
}),
),
withSubscriptions(
({ isSubscriptionActive }) => ({
isSubscriptionActive,
}),
'main',
),
withSetupWizard(
({
isCongratsStep,
isSubscriptionStep,
isInitializingStep,
isOrganizationStep,
}) => ({
isCongratsStep,
isSubscriptionStep,
isInitializingStep,
isOrganizationStep,
}),
),
)(SetupRightSection);

View File

@@ -0,0 +1,42 @@
import React from 'react';
import { Formik } from 'formik';
import { withWizard } from 'react-albus';
import 'style/pages/Setup/Subscription.scss';
import SetupSubscriptionForm from './SetupSubscriptionForm';
import { SubscriptionFormSchema } from './SubscriptionForm.schema';
import { compose } from 'utils';
/**
* Subscription step of wizard setup.
*/
function SetupSubscription({
// #withWizard
wizard,
}) {
// Initial values.
const initialValues = {
plan_slug: 'free',
period: 'month',
license_code: '',
};
const handleSubmit = () => {};
return (
<div className={'setup-subscription-form'}>
<Formik
validationSchema={SubscriptionFormSchema}
initialValues={initialValues}
component={SetupSubscriptionForm}
onSubmit={handleSubmit}
/>
</div>
);
}
export default compose(
withWizard,
)(SetupSubscription);

View File

@@ -1,98 +1,15 @@
import React, { useMemo } from 'react';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { Button, Intent } from '@blueprintjs/core';
import { withWizard } from 'react-albus';
import React from 'react';
import { Form } from 'formik';
import 'style/pages/Setup/Billing.scss';
import BillingPlans from 'containers/Subscriptions/billingPlans';
import BillingPeriods from 'containers/Subscriptions/billingPeriods';
import { BillingPaymentmethod } from 'containers/Subscriptions/billingPaymentmethod';
import withSubscriptionsActions from 'containers/Subscriptions/withSubscriptionsActions';
import withBillingActions from 'containers/Subscriptions/withBillingActions';
import { compose } from 'utils';
import BillingPlansForm from 'containers/Subscriptions/BillingPlansForm';
/**
* Subscription step of wizard setup.
*/
function SetupSubscriptionForm({
// #withBillingActions
requestSubmitBilling,
// #withWizard
wizard,
// #withSubscriptionsActions
requestFetchSubscriptions
}) {
const { formatMessage } = useIntl();
const validationSchema = Yup.object().shape({
plan_slug: Yup.string()
.required()
.label(formatMessage({ id: 'plan_slug' })),
license_code: Yup.string()
.min(10)
.max(10)
.required()
.label(formatMessage({ id: 'license_code_' }))
.trim(),
});
const initialValues = useMemo(
() => ({
plan_slug: '',
license_code: '',
}),
[],
);
const formik = useFormik({
enableReinitialize: true,
validationSchema: validationSchema,
initialValues: {
...initialValues,
},
onSubmit: (values, { setSubmitting, setErrors }) => {
requestSubmitBilling(values)
.then((response) => {
return requestFetchSubscriptions();
})
.then(() => {
wizard.next();
setSubmitting(false);
})
.catch((errors) => {
setSubmitting(false);
});
},
});
export default function SetupSubscriptionForm() {
return (
<div className={'register-subscription-form'}>
<form onSubmit={formik.handleSubmit} className={'billing-form'}>
<BillingPlans title={'a_select_a_plan'} formik={formik} />
<BillingPeriods title={'b_choose_your_billing'} formik={formik} />
<BillingPaymentmethod title={'c_payment_methods'} formik={formik} />
<div className={'subscribe-button'}>
<Button
intent={Intent.PRIMARY}
type="submit"
loading={formik.isSubmitting}
>
<T id={'subscribe'} />
</Button>
</div>
</form>
</div>
<Form>
<BillingPlansForm />
</Form>
);
}
export default compose(
withBillingActions,
withWizard,
withSubscriptionsActions,
)(SetupSubscriptionForm);

View File

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

View File

@@ -0,0 +1,9 @@
import * as Yup from 'yup';
import { formatMessage } from 'services/intl';
export const SubscriptionFormSchema = Yup.object().shape({
plan_slug: Yup.string()
.required()
.label(formatMessage({ id: 'plan_slug' })),
period: Yup.string().required(),
});