mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 15:20:34 +00:00
Merge branch 'RegisterWizard' of https://github.com/abouolia/Bigcapital into RegisterWizard
This commit is contained in:
@@ -2,15 +2,15 @@
|
|||||||
|
|
||||||
export const registerWizardSteps = [
|
export const registerWizardSteps = [
|
||||||
{
|
{
|
||||||
label: 'organization_register',
|
label: 'payment_or_trial',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'payment_or_trial',
|
label: 'initializing',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'getting_started',
|
label: 'getting_started',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'initializing',
|
label: 'Congratulations',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -7,9 +7,8 @@ import { ReactQueryDevtools } from 'react-query-devtools';
|
|||||||
|
|
||||||
import PrivateRoute from 'components/PrivateRoute';
|
import PrivateRoute from 'components/PrivateRoute';
|
||||||
import Authentication from 'components/Authentication';
|
import Authentication from 'components/Authentication';
|
||||||
import Dashboard from 'components/Dashboard/Dashboard';
|
import DashboardPrivatePages from 'components/Dashboard/PrivatePages';
|
||||||
import GlobalErrors from 'containers/GlobalErrors/GlobalErrors';
|
import GlobalErrors from 'containers/GlobalErrors/GlobalErrors';
|
||||||
import RegisterWizardPage from 'containers/Authentication/Register/RegisterPage';
|
|
||||||
|
|
||||||
import messages from 'lang/en';
|
import messages from 'lang/en';
|
||||||
import 'style/App.scss';
|
import 'style/App.scss';
|
||||||
@@ -32,12 +31,8 @@ function App({ locale }) {
|
|||||||
<Authentication />
|
<Authentication />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path={'/register'}>
|
|
||||||
<RegisterWizardPage />
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path={'/'}>
|
<Route path={'/'}>
|
||||||
<PrivateRoute component={Dashboard} />
|
<PrivateRoute component={DashboardPrivatePages} />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import DashboardSplitPane from 'components/Dashboard/DashboardSplitePane';
|
|||||||
import EnsureOrganizationIsReady from './EnsureOrganizationIsReady';
|
import EnsureOrganizationIsReady from './EnsureOrganizationIsReady';
|
||||||
|
|
||||||
import withSettingsActions from 'containers/Settings/withSettingsActions';
|
import withSettingsActions from 'containers/Settings/withSettingsActions';
|
||||||
import withOrganizationsActions from 'containers/Organization/withOrganizationActions';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -22,20 +21,14 @@ import { compose } from 'utils';
|
|||||||
function Dashboard({
|
function Dashboard({
|
||||||
// #withSettings
|
// #withSettings
|
||||||
requestFetchOptions,
|
requestFetchOptions,
|
||||||
|
|
||||||
// #withOrganizations
|
|
||||||
requestOrganizationsList,
|
|
||||||
}) {
|
}) {
|
||||||
const fetchOrganizations = useQuery(
|
// const fetchOptions = useQuery(
|
||||||
['organizations'],
|
// ['options'], () => requestFetchOptions(),
|
||||||
(key) => requestOrganizationsList(),
|
// );
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<EnsureOrganizationIsReady>
|
<EnsureOrganizationIsReady>
|
||||||
<DashboardLoadingIndicator
|
<DashboardLoadingIndicator isLoading={false}>
|
||||||
isLoading={
|
|
||||||
fetchOrganizations.isLoading
|
|
||||||
}>
|
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/preferences">
|
<Route path="/preferences">
|
||||||
<DashboardSplitPane>
|
<DashboardSplitPane>
|
||||||
@@ -62,5 +55,4 @@ function Dashboard({
|
|||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withSettingsActions,
|
withSettingsActions,
|
||||||
withOrganizationsActions,
|
|
||||||
)(Dashboard);
|
)(Dashboard);
|
||||||
@@ -2,12 +2,13 @@ import React from 'react';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Choose, Icon } from 'components';
|
import { Choose, Icon } from 'components';
|
||||||
|
|
||||||
export default function Dashboard({
|
export default function DashboardLoadingIndicator({
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
|
className,
|
||||||
children,
|
children,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={classNames('dashboard')}>
|
<div className={classNames(className)}>
|
||||||
<Choose>
|
<Choose>
|
||||||
<Choose.When condition={isLoading}>
|
<Choose.When condition={isLoading}>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
|
|||||||
@@ -1,16 +1,31 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { Redirect } from 'react-router-dom';
|
import { Redirect } from 'react-router-dom';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
export default function EnsureOrganizationIsReady({
|
import withAuthentication from 'containers/Authentication/withAuthentication';
|
||||||
|
import withOrganization from 'containers/Organization/withOrganization';
|
||||||
|
|
||||||
|
|
||||||
|
function EnsureOrganizationIsReady({
|
||||||
|
// #ownProps
|
||||||
children,
|
children,
|
||||||
}) {
|
redirectTo = '/setup',
|
||||||
const isOrganizationReady = false;
|
|
||||||
|
|
||||||
return (isOrganizationReady) ? children : (
|
// #withOrganizationByOrgId
|
||||||
|
isOrganizationBuilt,
|
||||||
|
}) {
|
||||||
|
return (isOrganizationBuilt) ? children : (
|
||||||
<Redirect
|
<Redirect
|
||||||
to={{
|
to={{ pathname: redirectTo }}
|
||||||
pathname: '/register'
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAuthentication(),
|
||||||
|
connect((state, props) => ({
|
||||||
|
organizationId: props.currentOrganizationId,
|
||||||
|
})),
|
||||||
|
withOrganization(({ isOrganizationBuilt }) => ({ isOrganizationBuilt })),
|
||||||
|
)(EnsureOrganizationIsReady);
|
||||||
42
client/src/components/Dashboard/PrivatePages.js
Normal file
42
client/src/components/Dashboard/PrivatePages.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Switch, Route } from 'react-router';
|
||||||
|
import { useQuery } from 'react-query';
|
||||||
|
|
||||||
|
import Dashboard from 'components/Dashboard/Dashboard';
|
||||||
|
import SetupWizardPage from 'containers/Setup/WizardSetupPage';
|
||||||
|
import DashboardLoadingIndicator from 'components/Dashboard/DashboardLoadingIndicator';
|
||||||
|
|
||||||
|
import withOrganizationActions from 'containers/Organization/withOrganizationActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dashboard inner private pages.
|
||||||
|
*/
|
||||||
|
function DashboardPrivatePages({
|
||||||
|
|
||||||
|
// #withOrganizationActions
|
||||||
|
requestAllOrganizations,
|
||||||
|
}) {
|
||||||
|
const fetchOrganizations = useQuery(
|
||||||
|
['organizations'], () => requestAllOrganizations(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardLoadingIndicator isLoading={fetchOrganizations.isFetching}>
|
||||||
|
<Switch>
|
||||||
|
<Route path={'/setup'}>
|
||||||
|
<SetupWizardPage />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path='/'>
|
||||||
|
<Dashboard />
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
</DashboardLoadingIndicator>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withOrganizationActions,
|
||||||
|
)(DashboardPrivatePages);
|
||||||
@@ -19,18 +19,15 @@ import Icon from 'components/Icon';
|
|||||||
import { If } from 'components';
|
import { If } from 'components';
|
||||||
|
|
||||||
import withAuthenticationActions from './withAuthenticationActions';
|
import withAuthenticationActions from './withAuthenticationActions';
|
||||||
import withOrganizationsActions from 'containers/Organization/withOrganizationActions';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
|
||||||
const ERRORS_TYPES = {
|
const ERRORS_TYPES = {
|
||||||
INVALID_DETAILS: 'INVALID_DETAILS',
|
INVALID_DETAILS: 'INVALID_DETAILS',
|
||||||
USER_INACTIVE: 'USER_INACTIVE',
|
USER_INACTIVE: 'USER_INACTIVE',
|
||||||
};
|
};
|
||||||
function Login({
|
function Login({
|
||||||
requestLogin,
|
requestLogin,
|
||||||
requestOrganizationsList,
|
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -105,7 +102,7 @@ function Login({
|
|||||||
<div className={'authentication-page__label-section'}>
|
<div className={'authentication-page__label-section'}>
|
||||||
<h3><T id={'log_in'} /></h3>
|
<h3><T id={'log_in'} /></h3>
|
||||||
<T id={'need_bigcapital_account'} />
|
<T id={'need_bigcapital_account'} />
|
||||||
<Link to='/register'> <T id={'create_an_account'} /></Link>
|
<Link to='/auth/register'> <T id={'create_an_account'} /></Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className={'authentication-page__form'}>
|
<form onSubmit={handleSubmit} className={'authentication-page__form'}>
|
||||||
@@ -170,5 +167,4 @@ function Login({
|
|||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withAuthenticationActions,
|
withAuthenticationActions,
|
||||||
withOrganizationsActions,
|
|
||||||
)(Login);
|
)(Login);
|
||||||
284
client/src/containers/Authentication/Register.js
Normal file
284
client/src/containers/Authentication/Register.js
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
import React, { useMemo, useState, useCallback } from 'react';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import { Row, Col } from 'react-grid-system';
|
||||||
|
import { Link, useHistory } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
InputGroup,
|
||||||
|
Intent,
|
||||||
|
FormGroup,
|
||||||
|
Spinner,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import AppToaster from 'components/AppToaster';
|
||||||
|
import AuthInsider from 'containers/Authentication/AuthInsider';
|
||||||
|
|
||||||
|
import ErrorMessage from 'components/ErrorMessage';
|
||||||
|
import Icon from 'components/Icon';
|
||||||
|
import { If } from 'components';
|
||||||
|
import withAuthenticationActions from 'containers/Authentication/withAuthenticationActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
function RegisterUserForm({ requestRegister, requestLogin }) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const history = useHistory();
|
||||||
|
const [shown, setShown] = useState(false);
|
||||||
|
const passwordRevealer = useCallback(() => {
|
||||||
|
setShown(!shown);
|
||||||
|
}, [shown]);
|
||||||
|
|
||||||
|
const ValidationSchema = Yup.object().shape({
|
||||||
|
first_name: Yup.string()
|
||||||
|
.required()
|
||||||
|
.label(formatMessage({ id: 'first_name_' })),
|
||||||
|
last_name: Yup.string()
|
||||||
|
.required()
|
||||||
|
.label(formatMessage({ id: 'last_name_' })),
|
||||||
|
email: Yup.string()
|
||||||
|
.email()
|
||||||
|
.required()
|
||||||
|
.label(formatMessage({ id: 'email' })),
|
||||||
|
phone_number: Yup.string()
|
||||||
|
.matches()
|
||||||
|
.required()
|
||||||
|
.label(formatMessage({ id: 'phone_number_' })),
|
||||||
|
password: Yup.string()
|
||||||
|
.min(4)
|
||||||
|
.required()
|
||||||
|
.label(formatMessage({ id: 'password' })),
|
||||||
|
});
|
||||||
|
|
||||||
|
const initialValues = useMemo(
|
||||||
|
() => ({
|
||||||
|
first_name: '',
|
||||||
|
last_name: '',
|
||||||
|
email: '',
|
||||||
|
phone_number: '',
|
||||||
|
password: '',
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
errors,
|
||||||
|
touched,
|
||||||
|
handleSubmit,
|
||||||
|
getFieldProps,
|
||||||
|
isSubmitting,
|
||||||
|
} = useFormik({
|
||||||
|
enableReinitialize: true,
|
||||||
|
validationSchema: ValidationSchema,
|
||||||
|
initialValues: {
|
||||||
|
...initialValues,
|
||||||
|
country: 'libya',
|
||||||
|
},
|
||||||
|
onSubmit: (values, { setSubmitting, setErrors }) => {
|
||||||
|
requestRegister(values)
|
||||||
|
.then((response) => {
|
||||||
|
requestLogin({
|
||||||
|
crediential: values.email,
|
||||||
|
password: values.password,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
history.push('/register/subscription');
|
||||||
|
setSubmitting(false);
|
||||||
|
})
|
||||||
|
.catch((errors) => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({ id: 'something_wentwrong' }),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((errors) => {
|
||||||
|
if (errors.some((e) => e.type === 'PHONE_NUMBER_EXISTS')) {
|
||||||
|
setErrors({
|
||||||
|
phone_number: formatMessage({
|
||||||
|
id: 'the_phone_number_already_used_in_another_account',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (errors.some((e) => e.type === 'EMAIL_EXISTS')) {
|
||||||
|
setErrors({
|
||||||
|
email: formatMessage({
|
||||||
|
id: 'the_email_already_used_in_another_account',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setSubmitting(false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const passwordRevealerTmp = useMemo(
|
||||||
|
() => (
|
||||||
|
<span class="password-revealer" onClick={() => passwordRevealer()}>
|
||||||
|
<If condition={shown}>
|
||||||
|
<>
|
||||||
|
<Icon icon="eye-slash" />{' '}
|
||||||
|
<span class="text">
|
||||||
|
<T id={'hide'} />
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
</If>
|
||||||
|
<If condition={!shown}>
|
||||||
|
<>
|
||||||
|
<Icon icon="eye" />{' '}
|
||||||
|
<span class="text">
|
||||||
|
<T id={'show'} />
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
</If>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
[shown, passwordRevealer],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthInsider>
|
||||||
|
<div className={'register-form'}>
|
||||||
|
<div className={'authentication-page__label-section'}>
|
||||||
|
<h3>
|
||||||
|
<T id={'register_a_new_organization'} />
|
||||||
|
</h3>
|
||||||
|
<T id={'you_have_a_bigcapital_account'} />
|
||||||
|
<Link to="/auth/login">
|
||||||
|
{' '}
|
||||||
|
<T id={'login'} />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className={'authentication-page__form'}>
|
||||||
|
|
||||||
|
<Row className={'name-section'}>
|
||||||
|
<Col md={6}>
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'first_name'} />}
|
||||||
|
intent={
|
||||||
|
errors.first_name && touched.first_name && Intent.DANGER
|
||||||
|
}
|
||||||
|
helperText={
|
||||||
|
<ErrorMessage name={'first_name'} {...{ errors, touched }} />
|
||||||
|
}
|
||||||
|
className={'form-group--first-name'}
|
||||||
|
>
|
||||||
|
<InputGroup
|
||||||
|
intent={
|
||||||
|
errors.first_name && touched.first_name && Intent.DANGER
|
||||||
|
}
|
||||||
|
{...getFieldProps('first_name')}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col md={6}>
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'last_name'} />}
|
||||||
|
intent={errors.last_name && touched.last_name && Intent.DANGER}
|
||||||
|
helperText={
|
||||||
|
<ErrorMessage name={'last_name'} {...{ errors, touched }} />
|
||||||
|
}
|
||||||
|
className={'form-group--last-name'}
|
||||||
|
>
|
||||||
|
<InputGroup
|
||||||
|
intent={
|
||||||
|
errors.last_name && touched.last_name && Intent.DANGER
|
||||||
|
}
|
||||||
|
{...getFieldProps('last_name')}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'phone_number'} />}
|
||||||
|
intent={
|
||||||
|
errors.phone_number && touched.phone_number && Intent.DANGER
|
||||||
|
}
|
||||||
|
helperText={
|
||||||
|
<ErrorMessage name={'phone_number'} {...{ errors, touched }} />
|
||||||
|
}
|
||||||
|
className={'form-group--phone-number'}
|
||||||
|
>
|
||||||
|
<InputGroup
|
||||||
|
intent={
|
||||||
|
errors.phone_number && touched.phone_number && Intent.DANGER
|
||||||
|
}
|
||||||
|
{...getFieldProps('phone_number')}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'email'} />}
|
||||||
|
intent={errors.email && touched.email && Intent.DANGER}
|
||||||
|
helperText={
|
||||||
|
<ErrorMessage name={'email'} {...{ errors, touched }} />
|
||||||
|
}
|
||||||
|
className={'form-group--email'}
|
||||||
|
>
|
||||||
|
<InputGroup
|
||||||
|
intent={errors.email && touched.email && Intent.DANGER}
|
||||||
|
{...getFieldProps('email')}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'password'} />}
|
||||||
|
labelInfo={passwordRevealerTmp}
|
||||||
|
intent={errors.password && touched.password && Intent.DANGER}
|
||||||
|
helperText={
|
||||||
|
<ErrorMessage name={'password'} {...{ errors, touched }} />
|
||||||
|
}
|
||||||
|
className={'form-group--password has-password-revealer'}
|
||||||
|
>
|
||||||
|
<InputGroup
|
||||||
|
lang={true}
|
||||||
|
type={shown ? 'text' : 'password'}
|
||||||
|
intent={errors.password && touched.password && Intent.DANGER}
|
||||||
|
{...getFieldProps('password')}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<div className={'register-form__agreement-section'}>
|
||||||
|
<p>
|
||||||
|
<T id={'signing_in_or_creating'} /> <br />
|
||||||
|
<Link>
|
||||||
|
<T id={'terms_conditions'} />
|
||||||
|
</Link>{' '}
|
||||||
|
<T id={'and'} />
|
||||||
|
<Link>
|
||||||
|
{' '}
|
||||||
|
<T id={'privacy_statement'} />
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={'authentication-page__submit-button-wrap'}>
|
||||||
|
<Button
|
||||||
|
className={'btn-register'}
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
type="submit"
|
||||||
|
fill={true}
|
||||||
|
loading={isSubmitting}
|
||||||
|
>
|
||||||
|
<T id={'register'} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<If condition={isSubmitting}>
|
||||||
|
<div class="authentication-page__loading-overlay">
|
||||||
|
<Spinner size={50} />
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
</div>
|
||||||
|
</AuthInsider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAuthenticationActions,
|
||||||
|
)(RegisterUserForm);
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import RegisterRightSection from './RegisterRightSection';
|
|
||||||
import RegisterLeftSection from './RegisterLeftSection';
|
|
||||||
|
|
||||||
function RegisterWizardPage() {
|
|
||||||
return (
|
|
||||||
<div class="register-page">
|
|
||||||
<RegisterLeftSection />
|
|
||||||
<RegisterRightSection />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default RegisterWizardPage;
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { TransitionGroup, CSSTransition } from 'react-transition-group';
|
|
||||||
import { Wizard, Steps, Step } from 'react-albus';
|
|
||||||
import { useHistory } from "react-router-dom";
|
|
||||||
import RegisterWizardSteps from './RegisterWizardSteps';
|
|
||||||
import PrivateRoute from 'components/PrivateRoute';
|
|
||||||
|
|
||||||
import RegisterUserForm from 'containers/Authentication/Register/RegisterUserForm';
|
|
||||||
import RegisterSubscriptionForm from 'containers/Authentication/Register/RegisterSubscriptionForm';
|
|
||||||
import RegisterOrganizationForm from 'containers/Authentication/Register/RegisterOrganizationForm';
|
|
||||||
|
|
||||||
export default function RegisterRightSection () {
|
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className={'register-page__right-section'}>
|
|
||||||
<Wizard
|
|
||||||
basename={'/register'}
|
|
||||||
history={history}
|
|
||||||
render={({ step, steps }) => (
|
|
||||||
<div>
|
|
||||||
<RegisterWizardSteps currentStep={steps.indexOf(step) + 1} />
|
|
||||||
|
|
||||||
<TransitionGroup>
|
|
||||||
<CSSTransition
|
|
||||||
key={step.id}
|
|
||||||
classNames="example"
|
|
||||||
timeout={{ enter: 500, exit: 500 }}
|
|
||||||
>
|
|
||||||
<div class="register-page-form">
|
|
||||||
<Steps key={step.id} step={step}>
|
|
||||||
<Step id="user">
|
|
||||||
<RegisterUserForm />
|
|
||||||
</Step>
|
|
||||||
|
|
||||||
<Step id="subscription">
|
|
||||||
<PrivateRoute component={RegisterSubscriptionForm} />
|
|
||||||
</Step>
|
|
||||||
|
|
||||||
<Step id="organization">
|
|
||||||
<PrivateRoute component={RegisterOrganizationForm} />
|
|
||||||
</Step>
|
|
||||||
|
|
||||||
<Step id="congratulations">
|
|
||||||
<h1 className="text-align-center">Ice King</h1>
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
</div>
|
|
||||||
</CSSTransition>
|
|
||||||
</TransitionGroup>
|
|
||||||
</div>
|
|
||||||
)} />
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,281 +0,0 @@
|
|||||||
import React, { useMemo, useState, useCallback } from 'react';
|
|
||||||
import * as Yup from 'yup';
|
|
||||||
import { useFormik } from 'formik';
|
|
||||||
import { Row, Col } from 'react-grid-system';
|
|
||||||
import { Link, useHistory } from 'react-router-dom';
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
InputGroup,
|
|
||||||
Intent,
|
|
||||||
FormGroup,
|
|
||||||
Spinner,
|
|
||||||
} from '@blueprintjs/core';
|
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import AppToaster from 'components/AppToaster';
|
|
||||||
|
|
||||||
import ErrorMessage from 'components/ErrorMessage';
|
|
||||||
import Icon from 'components/Icon';
|
|
||||||
import { If } from 'components';
|
|
||||||
import withAuthenticationActions from 'containers/Authentication/withAuthenticationActions';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
|
||||||
|
|
||||||
function RegisterUserForm({ requestRegister, requestLogin }) {
|
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
const history = useHistory();
|
|
||||||
const [shown, setShown] = useState(false);
|
|
||||||
const passwordRevealer = useCallback(() => {
|
|
||||||
setShown(!shown);
|
|
||||||
}, [shown]);
|
|
||||||
|
|
||||||
const ValidationSchema = Yup.object().shape({
|
|
||||||
first_name: Yup.string()
|
|
||||||
.required()
|
|
||||||
.label(formatMessage({ id: 'first_name_' })),
|
|
||||||
last_name: Yup.string()
|
|
||||||
.required()
|
|
||||||
.label(formatMessage({ id: 'last_name_' })),
|
|
||||||
email: Yup.string()
|
|
||||||
.email()
|
|
||||||
.required()
|
|
||||||
.label(formatMessage({ id: 'email' })),
|
|
||||||
phone_number: Yup.string()
|
|
||||||
.matches()
|
|
||||||
.required()
|
|
||||||
.label(formatMessage({ id: 'phone_number_' })),
|
|
||||||
password: Yup.string()
|
|
||||||
.min(4)
|
|
||||||
.required()
|
|
||||||
.label(formatMessage({ id: 'password' })),
|
|
||||||
});
|
|
||||||
|
|
||||||
const initialValues = useMemo(
|
|
||||||
() => ({
|
|
||||||
first_name: '',
|
|
||||||
last_name: '',
|
|
||||||
email: '',
|
|
||||||
phone_number: '',
|
|
||||||
password: '',
|
|
||||||
}),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
|
||||||
errors,
|
|
||||||
touched,
|
|
||||||
handleSubmit,
|
|
||||||
getFieldProps,
|
|
||||||
isSubmitting,
|
|
||||||
} = useFormik({
|
|
||||||
enableReinitialize: true,
|
|
||||||
validationSchema: ValidationSchema,
|
|
||||||
initialValues: {
|
|
||||||
...initialValues,
|
|
||||||
country: 'libya',
|
|
||||||
},
|
|
||||||
onSubmit: (values, { setSubmitting, setErrors }) => {
|
|
||||||
requestRegister(values)
|
|
||||||
.then((response) => {
|
|
||||||
requestLogin({
|
|
||||||
crediential: values.email,
|
|
||||||
password: values.password,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
history.push('/register/subscription');
|
|
||||||
setSubmitting(false);
|
|
||||||
})
|
|
||||||
.catch((errors) => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage({
|
|
||||||
id: 'something_wentwrong',
|
|
||||||
}),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((errors) => {
|
|
||||||
if (errors.some((e) => e.type === 'PHONE_NUMBER_EXISTS')) {
|
|
||||||
setErrors({
|
|
||||||
phone_number: formatMessage({
|
|
||||||
id: 'the_phone_number_already_used_in_another_account',
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (errors.some((e) => e.type === 'EMAIL_EXISTS')) {
|
|
||||||
setErrors({
|
|
||||||
email: formatMessage({
|
|
||||||
id: 'the_email_already_used_in_another_account',
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setSubmitting(false);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const passwordRevealerTmp = useMemo(
|
|
||||||
() => (
|
|
||||||
<span class="password-revealer" onClick={() => passwordRevealer()}>
|
|
||||||
<If condition={shown}>
|
|
||||||
<>
|
|
||||||
<Icon icon="eye-slash" />{' '}
|
|
||||||
<span class="text">
|
|
||||||
<T id={'hide'} />
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
</If>
|
|
||||||
<If condition={!shown}>
|
|
||||||
<>
|
|
||||||
<Icon icon="eye" />{' '}
|
|
||||||
<span class="text">
|
|
||||||
<T id={'show'} />
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
</If>
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
[shown, passwordRevealer],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={'register-form'}>
|
|
||||||
<div className={'authentication-page__label-section'}>
|
|
||||||
<h3>
|
|
||||||
<T id={'register_a_new_organization'} />
|
|
||||||
</h3>
|
|
||||||
<T id={'you_have_a_bigcapital_account'} />
|
|
||||||
<Link to="/auth/login">
|
|
||||||
{' '}
|
|
||||||
<T id={'login'} />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className={'authentication-page__form'}>
|
|
||||||
|
|
||||||
<Row className={'name-section'}>
|
|
||||||
<Col md={6}>
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'first_name'} />}
|
|
||||||
intent={
|
|
||||||
errors.first_name && touched.first_name && Intent.DANGER
|
|
||||||
}
|
|
||||||
helperText={
|
|
||||||
<ErrorMessage name={'first_name'} {...{ errors, touched }} />
|
|
||||||
}
|
|
||||||
className={'form-group--first-name'}
|
|
||||||
>
|
|
||||||
<InputGroup
|
|
||||||
intent={
|
|
||||||
errors.first_name && touched.first_name && Intent.DANGER
|
|
||||||
}
|
|
||||||
{...getFieldProps('first_name')}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col md={6}>
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'last_name'} />}
|
|
||||||
intent={errors.last_name && touched.last_name && Intent.DANGER}
|
|
||||||
helperText={
|
|
||||||
<ErrorMessage name={'last_name'} {...{ errors, touched }} />
|
|
||||||
}
|
|
||||||
className={'form-group--last-name'}
|
|
||||||
>
|
|
||||||
<InputGroup
|
|
||||||
intent={
|
|
||||||
errors.last_name && touched.last_name && Intent.DANGER
|
|
||||||
}
|
|
||||||
{...getFieldProps('last_name')}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'phone_number'} />}
|
|
||||||
intent={
|
|
||||||
errors.phone_number && touched.phone_number && Intent.DANGER
|
|
||||||
}
|
|
||||||
helperText={
|
|
||||||
<ErrorMessage name={'phone_number'} {...{ errors, touched }} />
|
|
||||||
}
|
|
||||||
className={'form-group--phone-number'}
|
|
||||||
>
|
|
||||||
<InputGroup
|
|
||||||
intent={
|
|
||||||
errors.phone_number && touched.phone_number && Intent.DANGER
|
|
||||||
}
|
|
||||||
{...getFieldProps('phone_number')}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'email'} />}
|
|
||||||
intent={errors.email && touched.email && Intent.DANGER}
|
|
||||||
helperText={
|
|
||||||
<ErrorMessage name={'email'} {...{ errors, touched }} />
|
|
||||||
}
|
|
||||||
className={'form-group--email'}
|
|
||||||
>
|
|
||||||
<InputGroup
|
|
||||||
intent={errors.email && touched.email && Intent.DANGER}
|
|
||||||
{...getFieldProps('email')}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'password'} />}
|
|
||||||
labelInfo={passwordRevealerTmp}
|
|
||||||
intent={errors.password && touched.password && Intent.DANGER}
|
|
||||||
helperText={
|
|
||||||
<ErrorMessage name={'password'} {...{ errors, touched }} />
|
|
||||||
}
|
|
||||||
className={'form-group--password has-password-revealer'}
|
|
||||||
>
|
|
||||||
<InputGroup
|
|
||||||
lang={true}
|
|
||||||
type={shown ? 'text' : 'password'}
|
|
||||||
intent={errors.password && touched.password && Intent.DANGER}
|
|
||||||
{...getFieldProps('password')}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<div className={'register-form__agreement-section'}>
|
|
||||||
<p>
|
|
||||||
<T id={'signing_in_or_creating'} /> <br />
|
|
||||||
<Link>
|
|
||||||
<T id={'terms_conditions'} />
|
|
||||||
</Link>{' '}
|
|
||||||
<T id={'and'} />
|
|
||||||
<Link>
|
|
||||||
{' '}
|
|
||||||
<T id={'privacy_statement'} />
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={'authentication-page__submit-button-wrap'}>
|
|
||||||
<Button
|
|
||||||
className={'btn-register'}
|
|
||||||
intent={Intent.PRIMARY}
|
|
||||||
type="submit"
|
|
||||||
fill={true}
|
|
||||||
loading={isSubmitting}
|
|
||||||
>
|
|
||||||
<T id={'register'} />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<If condition={isSubmitting}>
|
|
||||||
<div class="authentication-page__loading-overlay">
|
|
||||||
<Spinner size={50} />
|
|
||||||
</div>
|
|
||||||
</If>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default compose(withAuthenticationActions)(RegisterUserForm);
|
|
||||||
@@ -6,6 +6,7 @@ export default (mapState) => {
|
|||||||
const mapped = {
|
const mapped = {
|
||||||
isAuthorized: isAuthenticated(state),
|
isAuthorized: isAuthenticated(state),
|
||||||
user: state.authentication.user,
|
user: state.authentication.user,
|
||||||
|
currentOrganizationId: state.authentication?.organizationId,
|
||||||
};
|
};
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import {
|
|
||||||
buildTenant,
|
|
||||||
seedTenant,
|
|
||||||
} from 'store/organization/organization.actions';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
requestBuildTenant: (id, token) => dispatch(buildTenant({ id, token })),
|
|
||||||
requestSeedTenant: (id, token) => dispatch(seedTenant({ id, token })),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps);
|
|
||||||
37
client/src/containers/Organization/withOrganization.js
Normal file
37
client/src/containers/Organization/withOrganization.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
getOrganizationByIdFactory,
|
||||||
|
isOrganizationReadyFactory,
|
||||||
|
isOrganizationSeededFactory,
|
||||||
|
isOrganizationBuiltFactory,
|
||||||
|
isOrganizationSeedingFactory,
|
||||||
|
isOrganizationInitializingFactory,
|
||||||
|
isOrganizationSubscribedFactory,
|
||||||
|
} from 'store/organizations/organizations.selectors';
|
||||||
|
|
||||||
|
export default (mapState) => {
|
||||||
|
const getOrganizationById = getOrganizationByIdFactory();
|
||||||
|
const isOrganizationReady = isOrganizationReadyFactory();
|
||||||
|
|
||||||
|
const isOrganizationSeeded = isOrganizationSeededFactory();
|
||||||
|
const isOrganizationBuilt = isOrganizationBuiltFactory();
|
||||||
|
|
||||||
|
const isOrganizationInitializing = isOrganizationInitializingFactory();
|
||||||
|
const isOrganizationSeeding = isOrganizationSeedingFactory();
|
||||||
|
|
||||||
|
const isOrganizationSubscribed = isOrganizationSubscribedFactory();
|
||||||
|
|
||||||
|
const mapStateToProps = (state, props) => {
|
||||||
|
const mapped = {
|
||||||
|
organization: getOrganizationById(state, props),
|
||||||
|
isOrganizationReady: isOrganizationReady(state, props),
|
||||||
|
isOrganizationSeeded: isOrganizationSeeded(state, props),
|
||||||
|
isOrganizationInitialized: isOrganizationBuilt(state, props),
|
||||||
|
isOrganizationSeeding: isOrganizationInitializing(state, props),
|
||||||
|
isOrganizationInitializing: isOrganizationSeeding(state, props),
|
||||||
|
isOrganizationSubscribed: isOrganizationSubscribed(state, props),
|
||||||
|
};
|
||||||
|
return (mapState) ? mapState(mapped, state, props) : mapped;
|
||||||
|
};
|
||||||
|
return connect(mapStateToProps);
|
||||||
|
};
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
fetchOrganizations,
|
fetchOrganizations,
|
||||||
|
buildTenant,
|
||||||
|
seedTenant,
|
||||||
} from 'store/organizations/organizations.actions';
|
} from 'store/organizations/organizations.actions';
|
||||||
|
|
||||||
export const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
requestOrganizationsList: () => dispatch(fetchOrganizations()),
|
requestOrganizationBuild: () => dispatch(buildTenant()),
|
||||||
|
requestOrganizationSeed: () => dispatch(seedTenant()),
|
||||||
|
requestAllOrganizations: () => dispatch(fetchOrganizations()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps);
|
export default connect(null, mapDispatchToProps);
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import {
|
|
||||||
getOrganizationByOrgIdFactory,
|
|
||||||
} from 'store/organizations/organizations.selector';
|
|
||||||
|
|
||||||
export default (mapState) => {
|
|
||||||
const getOrganizationByOrgId = getOrganizationByOrgIdFactory();
|
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
|
||||||
const mapped = {
|
|
||||||
organization: getOrganizationByOrgId(state, props),
|
|
||||||
};
|
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
|
||||||
};
|
|
||||||
return connect(mapStateToProps);
|
|
||||||
};
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import {
|
|
||||||
getOrganizationByTenantIdFactory,
|
|
||||||
} from 'store/organizations/organizations.selector';
|
|
||||||
|
|
||||||
export default (mapState) => {
|
|
||||||
const getOrgByTenId = getOrganizationByTenantIdFactory();
|
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
|
||||||
const mapped = {
|
|
||||||
organization: getOrgByTenId(state, props),
|
|
||||||
};
|
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
|
||||||
};
|
|
||||||
return connect(mapStateToProps);
|
|
||||||
};
|
|
||||||
27
client/src/containers/Setup/EnsureOrganizationIsNotReady.js
Normal file
27
client/src/containers/Setup/EnsureOrganizationIsNotReady.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { Redirect } from 'react-router-dom';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
import withAuthentication from 'containers/Authentication/withAuthentication';
|
||||||
|
import withOrganization from 'containers/Organization/withOrganization';
|
||||||
|
|
||||||
|
function EnsureOrganizationIsNotReady({
|
||||||
|
children,
|
||||||
|
|
||||||
|
// #withOrganization
|
||||||
|
isOrganizationReady,
|
||||||
|
}) {
|
||||||
|
return (isOrganizationReady) ? (
|
||||||
|
<Redirect to={{ pathname: '/' }} />
|
||||||
|
) : children;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAuthentication(({ currentOrganizationId }) => ({
|
||||||
|
currentOrganizationId,
|
||||||
|
})),
|
||||||
|
connect((state, props) => ({
|
||||||
|
organizationId: props.currentOrganizationId,
|
||||||
|
})),
|
||||||
|
withOrganization(({ isOrganizationReady }) => ({ isOrganizationReady })),
|
||||||
|
)(EnsureOrganizationIsNotReady);
|
||||||
30
client/src/containers/Setup/SetupInitializingForm.js
Normal file
30
client/src/containers/Setup/SetupInitializingForm.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useQuery } from 'react-query';
|
||||||
|
|
||||||
|
import withOrganizationActions from 'containers/Organization/withOrganizationActions';
|
||||||
|
import withOrganization from 'containers/Organization/withOrganization'
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup initializing step form.
|
||||||
|
*/
|
||||||
|
function SetupInitializingForm({
|
||||||
|
|
||||||
|
// #withOrganizationActions
|
||||||
|
requestOrganizationBuild,
|
||||||
|
}) {
|
||||||
|
const requestBuildOrgnization = useQuery(
|
||||||
|
['build-tenant'], () => requestOrganizationBuild(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="setup-initializing-form">
|
||||||
|
<h1>You organization is initializin...</h1>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withOrganizationActions
|
||||||
|
)(SetupInitializingForm);
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
import React, { useState, useCallback } from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
import { Icon, If } from 'components';
|
import { Icon } from 'components';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
|
||||||
import withAuthentication from 'containers/Authentication/withAuthentication';
|
|
||||||
import withAuthenticationActions from 'containers/Authentication/withAuthenticationActions';
|
import withAuthenticationActions from 'containers/Authentication/withAuthenticationActions';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
function RegisterLeftSection({
|
* Wizard setup left section.
|
||||||
|
*/
|
||||||
|
function SetupLeftSection({
|
||||||
|
// #withAuthenticationActions
|
||||||
requestLogout,
|
requestLogout,
|
||||||
isAuthorized
|
|
||||||
}) {
|
}) {
|
||||||
const [org] = useState('LibyanSpider');
|
const [org] = useState('LibyanSpider');
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ function RegisterLeftSection({
|
|||||||
}, [requestLogout]);
|
}, [requestLogout]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={'register-page__left-section'}>
|
<section className={'setup-page__left-section'}>
|
||||||
<div className={'content'}>
|
<div className={'content'}>
|
||||||
<div className={'content-logo'}>
|
<div className={'content-logo'}>
|
||||||
<Icon
|
<Icon
|
||||||
@@ -38,18 +39,15 @@ function RegisterLeftSection({
|
|||||||
<T id={'you_have_a_bigcapital_account'} />
|
<T id={'you_have_a_bigcapital_account'} />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div className={'content-org'}>
|
||||||
<If condition={!!isAuthorized}>
|
<span>
|
||||||
<div className={'content-org'}>
|
<T id={'welcome'} />
|
||||||
<span>
|
{org},
|
||||||
<T id={'welcome'} />
|
</span>
|
||||||
{org},
|
<span>
|
||||||
</span>
|
<a onClick={onClickLogout} href="#"><T id={'sign_out'} /></a>
|
||||||
<span>
|
</span>
|
||||||
<a onClick={onClickLogout} href="#"><T id={'sign_out'} /></a>
|
</div>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</If>
|
|
||||||
|
|
||||||
<div className={'content-contact'}>
|
<div className={'content-contact'}>
|
||||||
<a href={'#!'}>
|
<a href={'#!'}>
|
||||||
@@ -69,6 +67,5 @@ function RegisterLeftSection({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withAuthentication(({ isAuthorized }) => ({ isAuthorized })),
|
|
||||||
withAuthenticationActions,
|
withAuthenticationActions,
|
||||||
)(RegisterLeftSection);
|
)(SetupLeftSection);
|
||||||
@@ -20,11 +20,16 @@ import { momentFormatter, tansformDateValue } from 'utils';
|
|||||||
import AppToaster from 'components/AppToaster';
|
import AppToaster from 'components/AppToaster';
|
||||||
import { ListSelect, ErrorMessage, FieldRequiredHint } from 'components';
|
import { ListSelect, ErrorMessage, FieldRequiredHint } from 'components';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import withSettingsActions from 'containers/Settings/withSettingsActions';
|
import withSettingsActions from 'containers/Settings/withSettingsActions';
|
||||||
import withRegisterOrganizationActions from 'containers/Authentication/withRegisterOrganizationActions';
|
import withOrganizationActions from 'containers/Organization/withOrganizationActions';
|
||||||
|
|
||||||
import { compose, optionsMapToArray } from 'utils';
|
import { compose, optionsMapToArray } from 'utils';
|
||||||
|
|
||||||
function RegisterOrganizationForm({ requestSubmitOptions, requestSeedTenant }) {
|
function SetupOrganizationForm({
|
||||||
|
requestSubmitOptions,
|
||||||
|
requestSeedTenant
|
||||||
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [selected, setSelected] = useState();
|
const [selected, setSelected] = useState();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -414,5 +419,5 @@ function RegisterOrganizationForm({ requestSubmitOptions, requestSeedTenant }) {
|
|||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withSettingsActions,
|
withSettingsActions,
|
||||||
withRegisterOrganizationActions,
|
withOrganizationActions,
|
||||||
)(RegisterOrganizationForm);
|
)(SetupOrganizationForm);
|
||||||
103
client/src/containers/Setup/SetupRightSection.js
Normal file
103
client/src/containers/Setup/SetupRightSection.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
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 WizardSetupSteps from './WizardSetupSteps';
|
||||||
|
|
||||||
|
import SetupSubscriptionForm from './SetupSubscriptionForm';
|
||||||
|
import SetupOrganizationForm from './SetupOrganizationForm';
|
||||||
|
import SetupInitializingForm from './SetupInitializingForm';
|
||||||
|
|
||||||
|
import withAuthentication from 'containers/Authentication/withAuthentication';
|
||||||
|
import withOrganization from 'containers/Organization/withOrganization'
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wizard setup right section.
|
||||||
|
*/
|
||||||
|
function SetupRightSection ({
|
||||||
|
// #withAuthentication
|
||||||
|
currentOrganizationId,
|
||||||
|
|
||||||
|
// #withOrganization
|
||||||
|
isOrganizationInitialized,
|
||||||
|
isOrganizationSubscribed: hasSubscriptions,
|
||||||
|
isOrganizationSeeded
|
||||||
|
}) {
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const handleSkip = useCallback(({ step, push }) => {
|
||||||
|
const scenarios = [
|
||||||
|
{ condition: !hasSubscriptions, redirectTo: 'subscription' },
|
||||||
|
// { condition: , redirectTo: 'initializing' }
|
||||||
|
{ condition: !hasSubscriptions, redirectTo: 'organization' },
|
||||||
|
];
|
||||||
|
const scenario = scenarios.find((scenario) => scenario.condition);
|
||||||
|
|
||||||
|
if (scenario) {
|
||||||
|
push(scenario.redirectTo);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
hasSubscriptions,
|
||||||
|
isOrganizationInitialized,
|
||||||
|
isOrganizationSeeded,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={'setup-page__right-section'}>
|
||||||
|
<Wizard
|
||||||
|
onNext={handleSkip}
|
||||||
|
basename={'/setup'}
|
||||||
|
history={history}
|
||||||
|
render={({ step, steps }) => (
|
||||||
|
<div>
|
||||||
|
<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="congratulations">
|
||||||
|
<h1 className="text-align-center">Ice King</h1>
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
</div>
|
||||||
|
</CSSTransition>
|
||||||
|
</TransitionGroup>
|
||||||
|
</div>
|
||||||
|
)} />
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAuthentication(({ currentOrganizationId }) => ({ currentOrganizationId })),
|
||||||
|
connect((state, props) => ({
|
||||||
|
organizationId: props.currentOrganizationId,
|
||||||
|
})),
|
||||||
|
withOrganization(({
|
||||||
|
organization,
|
||||||
|
isOrganizationInitialized,
|
||||||
|
isOrganizationSubscribed,
|
||||||
|
isOrganizationSeeded,
|
||||||
|
}) => ({
|
||||||
|
organization,
|
||||||
|
isOrganizationInitialized,
|
||||||
|
isOrganizationSubscribed,
|
||||||
|
isOrganizationSeeded,
|
||||||
|
})),
|
||||||
|
)(SetupRightSection);
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import { Button, Intent } from '@blueprintjs/core';
|
import { Button, Intent } from '@blueprintjs/core';
|
||||||
import BillingTab from 'containers/Subscriptions/BillingTab';
|
import BillingTab from 'containers/Subscriptions/BillingTab';
|
||||||
|
|
||||||
function RegisterSubscriptionForm({}) {
|
/**
|
||||||
|
* Subscription step of wizard setup.
|
||||||
|
*/
|
||||||
|
export default function SetupSubscriptionForm({
|
||||||
|
|
||||||
|
}) {
|
||||||
const ValidationSchema = Yup.object().shape({});
|
const ValidationSchema = Yup.object().shape({});
|
||||||
|
|
||||||
const initialValues = useMemo(() => ({}), []);
|
const initialValues = useMemo(() => ({}), []);
|
||||||
@@ -36,5 +41,3 @@ function RegisterSubscriptionForm({}) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RegisterSubscriptionForm;
|
|
||||||
17
client/src/containers/Setup/WizardSetupPage.js
Normal file
17
client/src/containers/Setup/WizardSetupPage.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import EnsureOrganizationIsNotReady from './EnsureOrganizationIsNotReady';
|
||||||
|
import SetupRightSection from './SetupRightSection';
|
||||||
|
import SetupLeftSection from './SetupLeftSection';
|
||||||
|
|
||||||
|
|
||||||
|
export default function WizardSetupPage() {
|
||||||
|
return (
|
||||||
|
<EnsureOrganizationIsNotReady>
|
||||||
|
<div class="setup-page">
|
||||||
|
<SetupLeftSection />
|
||||||
|
<SetupRightSection />
|
||||||
|
</div>
|
||||||
|
</EnsureOrganizationIsNotReady>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -3,7 +3,7 @@ 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 RegisterWizardStep({
|
function WizardSetupStep({
|
||||||
label,
|
label,
|
||||||
isActive = false
|
isActive = false
|
||||||
}) {
|
}) {
|
||||||
@@ -14,15 +14,15 @@ function RegisterWizardStep({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function RegisterWizardSteps({
|
function WizardSetupSteps({
|
||||||
currentStep = 1,
|
currentStep = 1,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={'register-wizard-steps'}>
|
<div className={'setup-page-steps-container'}>
|
||||||
<div className={'wizard-container'}>
|
<div className={'setup-page-steps'}>
|
||||||
<ul className={'wizard-wrapper'}>
|
<ul>
|
||||||
{registerWizardSteps.map((step, index) => (
|
{registerWizardSteps.map((step, index) => (
|
||||||
<RegisterWizardStep
|
<WizardSetupStep
|
||||||
label={step.label}
|
label={step.label}
|
||||||
isActive={(index + 1) == currentStep}
|
isActive={(index + 1) == currentStep}
|
||||||
/>
|
/>
|
||||||
@@ -33,4 +33,4 @@ function RegisterWizardSteps({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RegisterWizardSteps;
|
export default WizardSetupSteps;
|
||||||
@@ -26,5 +26,11 @@ export default [
|
|||||||
component: LazyLoader({
|
component: LazyLoader({
|
||||||
loader: () => import('containers/Authentication/InviteAccept'),
|
loader: () => import('containers/Authentication/InviteAccept'),
|
||||||
}),
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${BASE_URL}/register`,
|
||||||
|
component: LazyLoader({
|
||||||
|
loader: () => import('containers/Authentication/Register'),
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import t from 'store/types';
|
|||||||
const initialState = {
|
const initialState = {
|
||||||
token: '',
|
token: '',
|
||||||
organization: '',
|
organization: '',
|
||||||
|
organizationId: null,
|
||||||
user: '',
|
user: '',
|
||||||
tenant: {},
|
tenant: {},
|
||||||
locale: '',
|
locale: '',
|
||||||
@@ -16,6 +17,7 @@ export default createReducer(initialState, {
|
|||||||
state.token = token;
|
state.token = token;
|
||||||
state.user = user;
|
state.user = user;
|
||||||
state.organization = tenant.organization_id;
|
state.organization = tenant.organization_id;
|
||||||
|
state.organizationId = tenant.id;
|
||||||
state.tenant = tenant;
|
state.tenant = tenant;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import ApiService from 'services/ApiService';
|
|
||||||
|
|
||||||
export const buildTenant = ({ id, token }) => {
|
|
||||||
return (dispatch) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
ApiService.post(`organization/build${token}`, id)
|
|
||||||
.then((response) => {
|
|
||||||
resolve(response);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
reject(error.response.data.errors || []);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
export const seedTenant = ({ id, token }) => {
|
|
||||||
return (dispatch) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
ApiService.post(`organization/seed/${token}`, id)
|
|
||||||
.then((response) => {
|
|
||||||
resolve(response);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
reject(error.response.data.errors || []);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,16 +1,52 @@
|
|||||||
import ApiService from 'services/ApiService';
|
import ApiService from 'services/ApiService';
|
||||||
import t from 'store/types';
|
import t from 'store/types';
|
||||||
|
|
||||||
export const fetchOrganizations = () => {
|
export const fetchOrganizations = () => (dispatch) => new Promise((resolve, reject) => {
|
||||||
return (dispatch) => new Promise((resolve, reject) => {
|
ApiService.get('organization/all').then((response) => {
|
||||||
ApiService.get('organization/all').then((response) => {
|
dispatch({
|
||||||
dispatch({
|
type: t.ORGANIZATIONS_LIST_SET,
|
||||||
type: t.ORGANIZATIONS_LIST_SET,
|
payload: {
|
||||||
payload: {
|
organizations: response.data.organizations,
|
||||||
organizations: response.data.organizations,
|
},
|
||||||
},
|
});
|
||||||
});
|
resolve(response)
|
||||||
resolve(response)
|
}).catch(error => { reject(error); });
|
||||||
}).catch(error => { reject(error); });
|
});
|
||||||
|
|
||||||
|
export const buildTenant = () => (dispatch, getState) => new Promise((resolve, reject) => {
|
||||||
|
const organizationId = getState().authentication.organizationId;
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: t.SET_ORGANIZATION_INITIALIZING,
|
||||||
|
payload: { organizationId }
|
||||||
});
|
});
|
||||||
};
|
ApiService.post(`organization/build`).then((response) => {
|
||||||
|
resolve(response);
|
||||||
|
dispatch({
|
||||||
|
type: t.SET_ORGANIZATION_INITIALIZED,
|
||||||
|
payload: { organizationId }
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error.response.data.errors || []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export const seedTenant = () => (dispatch, getState) => new Promise((resolve, reject) => {
|
||||||
|
const organizationId = getState().authentication.organizationId;
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: t.SET_ORGANIZATION_INITIALIZING,
|
||||||
|
payload: { organizationId }
|
||||||
|
});
|
||||||
|
ApiService.post(`organization/seed/`).then((response) => {
|
||||||
|
resolve(response);
|
||||||
|
dispatch({
|
||||||
|
type: t.SET_ORGANIZATION_INITIALIZED,
|
||||||
|
payload: { organizationId }
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error.response.data.errors || []);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -20,6 +20,44 @@ const reducer = createReducer(initialState, {
|
|||||||
state.data = _data;
|
state.data = _data;
|
||||||
state.byOrganizationId = _dataByOrganizationId;
|
state.byOrganizationId = _dataByOrganizationId;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[t.SET_ORGANIZATION_SEEDING]: (state, action) => {
|
||||||
|
const { organizationId } = action.payload;
|
||||||
|
|
||||||
|
state.data[organizationId] = {
|
||||||
|
...(state.data[organizationId] || {}),
|
||||||
|
is_seeding: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
[t.SET_ORGANIZATION_SEEDED]: (state, action) => {
|
||||||
|
const { organizationId } = action.payload;
|
||||||
|
|
||||||
|
state.data[organizationId] = {
|
||||||
|
...(state.data[organizationId] || {}),
|
||||||
|
is_seeding: false,
|
||||||
|
seeded_at: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
[t.SET_ORGANIZATION_INITIALIZING]: (state, action) => {
|
||||||
|
const { organizationId } = action.payload;
|
||||||
|
|
||||||
|
state.data[organizationId] = {
|
||||||
|
...(state.data[organizationId] || {}),
|
||||||
|
is_initializing: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
[t.SET_ORGANIZATION_INITIALIZED]: (state, action) => {
|
||||||
|
const { organizationId } = action.payload;
|
||||||
|
|
||||||
|
state.data[organizationId] = {
|
||||||
|
...(state.data[organizationId] || {}),
|
||||||
|
is_initializing: false,
|
||||||
|
initialized_at: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export default reducer;
|
export default reducer;
|
||||||
@@ -1,18 +1,50 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
const oragnizationByTenantIdSelector = (state, props) => state.organizations[props.tenantId];
|
const organizationSelector = (state, props) => state.organizations.data[props.organizationId];
|
||||||
const organizationByIdSelector = (state, props) => state.organizations.byOrganizationId[props.organizationId];
|
|
||||||
|
|
||||||
export const getOrganizationByOrgIdFactory = () => createSelector(
|
export const getOrganizationByIdFactory = () => createSelector(
|
||||||
organizationByIdSelector,
|
organizationSelector,
|
||||||
|
(organization) => organization
|
||||||
|
);
|
||||||
|
|
||||||
|
export const isOrganizationSeededFactory = () => createSelector(
|
||||||
|
organizationSelector,
|
||||||
(organization) => {
|
(organization) => {
|
||||||
return organization;
|
return !!organization?.seeded_at;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getOrganizationByTenantIdFactory = () => createSelector(
|
export const isOrganizationBuiltFactory = () => createSelector(
|
||||||
oragnizationByTenantIdSelector,
|
organizationSelector,
|
||||||
(organization) => {
|
(organization) => {
|
||||||
return organization;
|
return !!organization?.initialized_at;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const isOrganizationInitializingFactory = () => createSelector(
|
||||||
|
organizationSelector,
|
||||||
|
(organization) => {
|
||||||
|
return organization?.is_initializing;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const isOrganizationSeedingFactory = () => createSelector(
|
||||||
|
organizationSelector,
|
||||||
|
(organization) => {
|
||||||
|
return organization?.is_seeding;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const isOrganizationReadyFactory = () => createSelector(
|
||||||
|
organizationSelector,
|
||||||
|
(organization) => {
|
||||||
|
return organization?.is_ready;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const isOrganizationSubscribedFactory = () => createSelector(
|
||||||
|
organizationSelector,
|
||||||
|
(organization) => {
|
||||||
|
return organization?.subscriptions?.length > 0;
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
ORGANIZATIONS_LIST_SET: 'ORGANIZATIONS_LIST_SET',
|
ORGANIZATIONS_LIST_SET: 'ORGANIZATIONS_LIST_SET',
|
||||||
|
|
||||||
|
SET_ORGANIZATION_SEEDING: 'SET_ORGANIZATION_SEEDING',
|
||||||
|
SET_ORGANIZATION_SEEDED: 'SET_ORGANIZATION_SEEDED',
|
||||||
|
|
||||||
|
SET_ORGANIZATION_INITIALIZED: 'SET_ORGANIZATION_INITIALIZED',
|
||||||
|
SET_ORGANIZATION_INITIALIZING: 'SET_ORGANIZATION_INITIALIZING',
|
||||||
};
|
};
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
.register-page {
|
.register-page {
|
||||||
.bp3-input {
|
.bp3-input {
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
@@ -88,6 +89,43 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-form {
|
||||||
|
width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
// width: 690px;
|
||||||
|
// padding: 85px 60px;
|
||||||
|
// padding: 85px 105px;
|
||||||
|
|
||||||
|
// Register Form
|
||||||
|
.register-form {
|
||||||
|
padding: 85px 105px;
|
||||||
|
|
||||||
|
&__agreement-section {
|
||||||
|
margin-top: -10px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 13px;
|
||||||
|
margin-top: -10px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
line-height: 1.65;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__submit-button-wrap {
|
||||||
|
margin: 25px 0px 25px 0px;
|
||||||
|
|
||||||
|
.bp3-button {
|
||||||
|
min-height: 45px;
|
||||||
|
background-color: #0052cc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.setup-page {
|
||||||
|
|
||||||
&__right-section {
|
&__right-section {
|
||||||
padding-left: 25%;
|
padding-left: 25%;
|
||||||
}
|
}
|
||||||
@@ -145,47 +183,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&-form {
|
|
||||||
width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
|
|
||||||
// width: 690px;
|
|
||||||
// padding: 85px 60px;
|
|
||||||
// padding: 85px 105px;
|
|
||||||
|
|
||||||
// Register Form
|
|
||||||
.register-form {
|
|
||||||
padding: 85px 105px;
|
|
||||||
|
|
||||||
&__agreement-section {
|
|
||||||
margin-top: -10px;
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 13px;
|
|
||||||
margin-top: -10px;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
line-height: 1.65;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__submit-button-wrap {
|
|
||||||
margin: 25px 0px 25px 0px;
|
|
||||||
|
|
||||||
.bp3-button {
|
|
||||||
min-height: 45px;
|
|
||||||
background-color: #0052cc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Register Wizard Steps
|
|
||||||
.wizard-container {
|
|
||||||
width: 80%;
|
|
||||||
margin: 60px auto;
|
|
||||||
|
|
||||||
.wizard-wrapper li {
|
.setup-page-steps {
|
||||||
|
|
||||||
|
&-container {
|
||||||
|
width: 80%;
|
||||||
|
margin: 60px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
li{
|
||||||
position: relative;
|
position: relative;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
width: 25%;
|
width: 25%;
|
||||||
@@ -193,50 +202,53 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
color: #000;
|
color: #000;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
|
||||||
.wizard-wrapper li::before {
|
|
||||||
width: 13px;
|
|
||||||
height: 13px;
|
|
||||||
content: '';
|
|
||||||
line-height: 30px;
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
margin: 0 auto 10px auto;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #75859c;
|
|
||||||
}
|
|
||||||
.wizard-wrapper li::after {
|
|
||||||
width: 100%;
|
|
||||||
height: 2px;
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
background-color: #75859c;
|
|
||||||
top: 6px;
|
|
||||||
left: -50%;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
.wizard-wrapper li:first-child::after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wizard-wrapper > li.is-active::before {
|
&::before {
|
||||||
background-color: #75859c;
|
width: 13px;
|
||||||
}
|
height: 13px;
|
||||||
|
content: '';
|
||||||
|
line-height: 30px;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 auto 10px auto;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #75859c;
|
||||||
|
}
|
||||||
|
|
||||||
.wizard-wrapper > li.is-active ~ li::before {
|
&::after {
|
||||||
background: #ebebeb;
|
width: 100%;
|
||||||
}
|
height: 2px;
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
background-color: #75859c;
|
||||||
|
top: 6px;
|
||||||
|
left: -50%;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
.wizard-wrapper > li.is-active ~ li::after {
|
&:first-child::after {
|
||||||
background: #ebebeb;
|
display: none;
|
||||||
}
|
}
|
||||||
.wizard-wrapper > li.is-active p.wizard-info {
|
|
||||||
color: #004dd0;
|
&.is-active {
|
||||||
|
&::before {
|
||||||
|
background-color: #75859c;
|
||||||
|
}
|
||||||
|
|
||||||
|
~ li {
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
background: #ebebeb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.wizard-info {
|
||||||
|
color: #004dd0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @import './billing.scss';
|
|
||||||
|
|
||||||
//Register Subscription form
|
//Register Subscription form
|
||||||
.register-subscription-form {
|
.register-subscription-form {
|
||||||
padding-top: 50px;
|
padding-top: 50px;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import Container from 'typedi';
|
||||||
|
import TenancyService from 'services/Tenancy/TenancyService'
|
||||||
|
|
||||||
exports.up = (knex) => {
|
exports.up = (knex) => {
|
||||||
const tenancyService = Container.get(TenancyService);
|
const tenancyService = Container.get(TenancyService);
|
||||||
|
|||||||
@@ -87,9 +87,6 @@ export default class AuthenticationService implements IAuthenticationService {
|
|||||||
// Remove password property from user object.
|
// Remove password property from user object.
|
||||||
Reflect.deleteProperty(user, 'password');
|
Reflect.deleteProperty(user, 'password');
|
||||||
|
|
||||||
// Remove id property from tenant object.
|
|
||||||
Reflect.deleteProperty(tenant, 'id');
|
|
||||||
|
|
||||||
return { user, token, tenant };
|
return { user, token, tenant };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,20 @@ export default class Tenant extends BaseModel {
|
|||||||
return ['createdAt', 'updatedAt'];
|
return ['createdAt', 'updatedAt'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Virtual attributes.
|
||||||
|
*/
|
||||||
|
static get virtualAttributes() {
|
||||||
|
return ['isReady'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tenant is ready.
|
||||||
|
*/
|
||||||
|
get isReady() {
|
||||||
|
return !!(this.initializedAt && this.seededAt);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query modifiers.
|
* Query modifiers.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -77,9 +77,7 @@ export default class TenantRepository extends SystemRepository {
|
|||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId - Tenant id.
|
||||||
*/
|
*/
|
||||||
getByIdWithSubscriptions(tenantId: number) {
|
getByIdWithSubscriptions(tenantId: number) {
|
||||||
return this.cache.get(`tenant.id.${tenantId}.subscriptions`, () => {
|
return Tenant.query().findById(tenantId)
|
||||||
return Tenant.query().findById(tenantId)
|
.withGraphFetched('subscriptions.plan');
|
||||||
.withGraphFetched('subscriptions.plan');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user