mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 15:20:34 +00:00
feat: register pages routes guards.
feat: retrieve all organizations details to authenticated user. feat: redux organization reducers and actions.
This commit is contained in:
@@ -64,6 +64,7 @@
|
|||||||
"postcss-preset-env": "6.7.0",
|
"postcss-preset-env": "6.7.0",
|
||||||
"postcss-safe-parser": "4.0.1",
|
"postcss-safe-parser": "4.0.1",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
|
"react-albus": "^2.0.0",
|
||||||
"react-app-polyfill": "^1.0.6",
|
"react-app-polyfill": "^1.0.6",
|
||||||
"react-body-classname": "^1.3.1",
|
"react-body-classname": "^1.3.1",
|
||||||
"react-dev-utils": "^10.2.0",
|
"react-dev-utils": "^10.2.0",
|
||||||
@@ -83,6 +84,7 @@
|
|||||||
"react-split-pane": "^0.1.91",
|
"react-split-pane": "^0.1.91",
|
||||||
"react-table": "^7.0.0",
|
"react-table": "^7.0.0",
|
||||||
"react-table-sticky": "^1.1.2",
|
"react-table-sticky": "^1.1.2",
|
||||||
|
"react-transition-group": "^4.4.1",
|
||||||
"react-use": "^13.26.1",
|
"react-use": "^13.26.1",
|
||||||
"react-window": "^1.8.5",
|
"react-window": "^1.8.5",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function App({ locale }) {
|
|||||||
const queryConfig = {
|
const queryConfig = {
|
||||||
queries: {
|
queries: {
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<IntlProvider locale={locale} messages={messages}>
|
<IntlProvider locale={locale} messages={messages}>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Switch, Route } from 'react-router';
|
import { Switch, Route } from 'react-router';
|
||||||
import classNames from 'classnames';
|
import { useQuery } from 'react-query';
|
||||||
|
|
||||||
|
import DashboardLoadingIndicator from './DashboardLoadingIndicator';
|
||||||
|
|
||||||
import Sidebar from 'components/Sidebar/Sidebar';
|
import Sidebar from 'components/Sidebar/Sidebar';
|
||||||
import DashboardContent from 'components/Dashboard/DashboardContent';
|
import DashboardContent from 'components/Dashboard/DashboardContent';
|
||||||
@@ -9,29 +11,56 @@ import PreferencesContent from 'components/Preferences/PreferencesContent';
|
|||||||
import PreferencesSidebar from 'components/Preferences/PreferencesSidebar';
|
import PreferencesSidebar from 'components/Preferences/PreferencesSidebar';
|
||||||
import Search from 'containers/GeneralSearch/Search';
|
import Search from 'containers/GeneralSearch/Search';
|
||||||
import DashboardSplitPane from 'components/Dashboard/DashboardSplitePane';
|
import DashboardSplitPane from 'components/Dashboard/DashboardSplitePane';
|
||||||
|
import EnsureOrganizationIsReady from './EnsureOrganizationIsReady';
|
||||||
|
|
||||||
export default function Dashboard() {
|
import withSettingsActions from 'containers/Settings/withSettingsActions';
|
||||||
|
import withOrganizationsActions from 'containers/Organization/withOrganizationActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
|
||||||
|
function Dashboard({
|
||||||
|
// #withSettings
|
||||||
|
requestFetchOptions,
|
||||||
|
|
||||||
|
// #withOrganizations
|
||||||
|
requestOrganizationsList,
|
||||||
|
}) {
|
||||||
|
const fetchOrganizations = useQuery(
|
||||||
|
['organizations'],
|
||||||
|
(key) => requestOrganizationsList(),
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<div className={classNames('dashboard')}>
|
<EnsureOrganizationIsReady>
|
||||||
<Switch>
|
<DashboardLoadingIndicator
|
||||||
<Route path="/preferences">
|
isLoading={
|
||||||
<DashboardSplitPane>
|
fetchOrganizations.isLoading
|
||||||
<Sidebar />
|
}>
|
||||||
<PreferencesSidebar />
|
<Switch>
|
||||||
</DashboardSplitPane>
|
<Route path="/preferences">
|
||||||
<PreferencesContent />
|
<DashboardSplitPane>
|
||||||
</Route>
|
<Sidebar />
|
||||||
|
<PreferencesSidebar />
|
||||||
|
</DashboardSplitPane>
|
||||||
|
<PreferencesContent />
|
||||||
|
</Route>
|
||||||
|
|
||||||
<Route path="/">
|
<Route path="/">
|
||||||
<DashboardSplitPane>
|
<DashboardSplitPane>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<DashboardContent />
|
<DashboardContent />
|
||||||
</DashboardSplitPane>
|
</DashboardSplitPane>
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|
||||||
<Search />
|
<Search />
|
||||||
<DialogsContainer />
|
<DialogsContainer />
|
||||||
</div>
|
</DashboardLoadingIndicator>
|
||||||
|
</EnsureOrganizationIsReady>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withSettingsActions,
|
||||||
|
withOrganizationsActions,
|
||||||
|
)(Dashboard);
|
||||||
25
client/src/components/Dashboard/DashboardLoadingIndicator.js
Normal file
25
client/src/components/Dashboard/DashboardLoadingIndicator.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Choose, Icon } from 'components';
|
||||||
|
|
||||||
|
export default function Dashboard({
|
||||||
|
isLoading = false,
|
||||||
|
children,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className={classNames('dashboard')}>
|
||||||
|
<Choose>
|
||||||
|
<Choose.When condition={isLoading}>
|
||||||
|
<div class="center">
|
||||||
|
<Icon icon="bigcapital" height={37} width={214} />
|
||||||
|
<span>Please wait while resources loading...</span>
|
||||||
|
</div>
|
||||||
|
</Choose.When>
|
||||||
|
|
||||||
|
<Choose.Otherwise>
|
||||||
|
{ children }
|
||||||
|
</Choose.Otherwise>
|
||||||
|
</Choose>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
16
client/src/components/Dashboard/EnsureOrganizationIsReady.js
Normal file
16
client/src/components/Dashboard/EnsureOrganizationIsReady.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
|
export default function EnsureOrganizationIsReady({
|
||||||
|
children,
|
||||||
|
}) {
|
||||||
|
const isOrganizationReady = false;
|
||||||
|
|
||||||
|
return (isOrganizationReady) ? children : (
|
||||||
|
<Redirect
|
||||||
|
to={{
|
||||||
|
pathname: '/register'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ 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';
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ const ERRORS_TYPES = {
|
|||||||
};
|
};
|
||||||
function Login({
|
function Login({
|
||||||
requestLogin,
|
requestLogin,
|
||||||
|
requestOrganizationsList,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -168,4 +170,5 @@ function Login({
|
|||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withAuthenticationActions,
|
withAuthenticationActions,
|
||||||
|
withOrganizationsActions,
|
||||||
)(Login);
|
)(Login);
|
||||||
@@ -1,12 +1,23 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
import { Icon } from 'components';
|
import { Icon, If } from 'components';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
|
||||||
export default function RegisterLeftSection({
|
import withAuthentication from 'containers/Authentication/withAuthentication';
|
||||||
|
import withAuthenticationActions from 'containers/Authentication/withAuthenticationActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
|
||||||
|
function RegisterLeftSection({
|
||||||
|
requestLogout,
|
||||||
|
isAuthorized
|
||||||
}) {
|
}) {
|
||||||
const [org] = useState('LibyanSpider');
|
const [org] = useState('LibyanSpider');
|
||||||
|
|
||||||
|
const onClickLogout = useCallback(() => {
|
||||||
|
requestLogout();
|
||||||
|
}, [requestLogout]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={'register-page__left-section'}>
|
<section className={'register-page__left-section'}>
|
||||||
<div className={'content'}>
|
<div className={'content'}>
|
||||||
@@ -27,17 +38,18 @@ export default function RegisterLeftSection({
|
|||||||
<T id={'you_have_a_bigcapital_account'} />
|
<T id={'you_have_a_bigcapital_account'} />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className={'content-org'}>
|
|
||||||
<span>
|
<If condition={!!isAuthorized}>
|
||||||
<T id={'welcome'} />
|
<div className={'content-org'}>
|
||||||
{org},
|
<span>
|
||||||
</span>
|
<T id={'welcome'} />
|
||||||
<span>
|
{org},
|
||||||
<a href={'#!'}>
|
</span>
|
||||||
<T id={'sign_out'} />
|
<span>
|
||||||
</a>
|
<a onClick={onClickLogout} href="#"><T id={'sign_out'} /></a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</If>
|
||||||
|
|
||||||
<div className={'content-contact'}>
|
<div className={'content-contact'}>
|
||||||
<a href={'#!'}>
|
<a href={'#!'}>
|
||||||
@@ -54,4 +66,9 @@ export default function RegisterLeftSection({
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAuthentication(({ isAuthorized }) => ({ isAuthorized })),
|
||||||
|
withAuthenticationActions,
|
||||||
|
)(RegisterLeftSection);
|
||||||
|
|||||||
@@ -1,26 +1,55 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Route, Switch } from 'react-router-dom';
|
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 RegisterWizardSteps from './RegisterWizardSteps';
|
||||||
import registerRoutes from 'routes/register';
|
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 () {
|
export default function RegisterRightSection () {
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={'register-page__right-section'}>
|
<section className={'register-page__right-section'}>
|
||||||
<RegisterWizardSteps />
|
<Wizard
|
||||||
|
basename={'/register'}
|
||||||
|
history={history}
|
||||||
|
render={({ step, steps }) => (
|
||||||
|
<div>
|
||||||
|
<RegisterWizardSteps currentStep={steps.indexOf(step) + 1} />
|
||||||
|
|
||||||
<div class="register-page-form">
|
<TransitionGroup>
|
||||||
<Switch>
|
<CSSTransition
|
||||||
{ registerRoutes.map((route, index) => (
|
key={step.id}
|
||||||
<Route
|
classNames="example"
|
||||||
exact={route.exact}
|
timeout={{ enter: 500, exit: 500 }}
|
||||||
key={index}
|
>
|
||||||
path={`${route.path}`}
|
<div class="register-page-form">
|
||||||
component={route.component}
|
<Steps key={step.id} step={step}>
|
||||||
/>
|
<Step id="user">
|
||||||
)) }
|
<RegisterUserForm />
|
||||||
</Switch>
|
</Step>
|
||||||
</div>
|
|
||||||
|
<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>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -77,17 +77,12 @@ function RegisterUserForm({ requestRegister, requestLogin }) {
|
|||||||
onSubmit: (values, { setSubmitting, setErrors }) => {
|
onSubmit: (values, { setSubmitting, setErrors }) => {
|
||||||
requestRegister(values)
|
requestRegister(values)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
// AppToaster.show({
|
|
||||||
// message: formatMessage({
|
|
||||||
// id: 'welcome_organization_account_has_been_created',
|
|
||||||
// }),
|
|
||||||
// intent: Intent.SUCCESS,
|
|
||||||
// });
|
|
||||||
requestLogin({
|
requestLogin({
|
||||||
crediential: values.email,
|
crediential: values.email,
|
||||||
password: values.password,
|
password: values.password,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
history.push('/register/subscription');
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
})
|
})
|
||||||
.catch((errors) => {
|
.catch((errors) => {
|
||||||
@@ -98,7 +93,6 @@ function RegisterUserForm({ requestRegister, requestLogin }) {
|
|||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// history.push('/auth/login');
|
|
||||||
})
|
})
|
||||||
.catch((errors) => {
|
.catch((errors) => {
|
||||||
if (errors.some((e) => e.type === 'PHONE_NUMBER_EXISTS')) {
|
if (errors.some((e) => e.type === 'PHONE_NUMBER_EXISTS')) {
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
fetchOrganizations,
|
||||||
|
} from 'store/organizations/organizations.actions';
|
||||||
|
|
||||||
|
export const mapDispatchToProps = (dispatch) => ({
|
||||||
|
requestOrganizationsList: () => dispatch(fetchOrganizations()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps);
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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);
|
||||||
|
};
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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);
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ const initialState = {
|
|||||||
token: '',
|
token: '',
|
||||||
organization: '',
|
organization: '',
|
||||||
user: '',
|
user: '',
|
||||||
|
tenant: {},
|
||||||
locale: '',
|
locale: '',
|
||||||
errors: [],
|
errors: [],
|
||||||
};
|
};
|
||||||
@@ -15,6 +16,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.tenant = tenant;
|
||||||
},
|
},
|
||||||
|
|
||||||
[t.LOGIN_FAILURE]: (state, action) => {
|
[t.LOGIN_FAILURE]: (state, action) => {
|
||||||
@@ -36,3 +38,9 @@ export const isAuthenticated = (state) => !!state.authentication.token;
|
|||||||
export const hasErrorType = (state, errorType) => {
|
export const hasErrorType = (state, errorType) => {
|
||||||
return state.authentication.errors.find((e) => e.type === errorType);
|
return state.authentication.errors.find((e) => e.type === errorType);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isTenantSeeded = (state) => !!state.tenant.seeded_at;
|
||||||
|
export const isTenantBuilt = (state) => !!state.tenant.initialized_at;
|
||||||
|
|
||||||
|
export const isTenantHasSubscription = () => false;
|
||||||
|
export const isTenantSubscriptionExpired = () => false;
|
||||||
16
client/src/store/organizations/organizations.actions.js
Normal file
16
client/src/store/organizations/organizations.actions.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import ApiService from 'services/ApiService';
|
||||||
|
import t from 'store/types';
|
||||||
|
|
||||||
|
export const fetchOrganizations = () => {
|
||||||
|
return (dispatch) => new Promise((resolve, reject) => {
|
||||||
|
ApiService.get('organization/all').then((response) => {
|
||||||
|
dispatch({
|
||||||
|
type: t.ORGANIZATIONS_LIST_SET,
|
||||||
|
payload: {
|
||||||
|
organizations: response.data.organizations,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
resolve(response)
|
||||||
|
}).catch(error => { reject(error); });
|
||||||
|
});
|
||||||
|
};
|
||||||
25
client/src/store/organizations/organizations.reducers.js
Normal file
25
client/src/store/organizations/organizations.reducers.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { createReducer } from '@reduxjs/toolkit';
|
||||||
|
import t from 'store/types';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
data: {},
|
||||||
|
byOrganizationId: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const reducer = createReducer(initialState, {
|
||||||
|
|
||||||
|
[t.ORGANIZATIONS_LIST_SET]: (state, action) => {
|
||||||
|
const { organizations } = action.payload;
|
||||||
|
const _data = {};
|
||||||
|
const _dataByOrganizationId = {};
|
||||||
|
|
||||||
|
organizations.forEach((organization) => {
|
||||||
|
_data[organization.id] = organization;
|
||||||
|
_dataByOrganizationId[organization.organization_id] = organization.id;
|
||||||
|
});
|
||||||
|
state.data = _data;
|
||||||
|
state.byOrganizationId = _dataByOrganizationId;
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default reducer;
|
||||||
18
client/src/store/organizations/organizations.selectors.js
Normal file
18
client/src/store/organizations/organizations.selectors.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
const oragnizationByTenantIdSelector = (state, props) => state.organizations[props.tenantId];
|
||||||
|
const organizationByIdSelector = (state, props) => state.organizations.byOrganizationId[props.organizationId];
|
||||||
|
|
||||||
|
export const getOrganizationByOrgIdFactory = () => createSelector(
|
||||||
|
organizationByIdSelector,
|
||||||
|
(organization) => {
|
||||||
|
return organization;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getOrganizationByTenantIdFactory = () => createSelector(
|
||||||
|
oragnizationByTenantIdSelector,
|
||||||
|
(organization) => {
|
||||||
|
return organization;
|
||||||
|
}
|
||||||
|
)
|
||||||
5
client/src/store/organizations/organizations.types.js
Normal file
5
client/src/store/organizations/organizations.types.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
ORGANIZATIONS_LIST_SET: 'ORGANIZATIONS_LIST_SET',
|
||||||
|
};
|
||||||
@@ -25,9 +25,11 @@ import bills from './Bills/bills.reducer';
|
|||||||
import vendors from './vendors/vendors.reducer';
|
import vendors from './vendors/vendors.reducer';
|
||||||
import paymentReceives from './PaymentReceive/paymentReceive.reducer';
|
import paymentReceives from './PaymentReceive/paymentReceive.reducer';
|
||||||
import paymentMades from './PaymentMades/paymentMade.reducer';
|
import paymentMades from './PaymentMades/paymentMade.reducer';
|
||||||
|
import organizations from './organizations/organizations.reducers';
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
authentication,
|
authentication,
|
||||||
|
organizations,
|
||||||
dashboard,
|
dashboard,
|
||||||
users,
|
users,
|
||||||
accounts,
|
accounts,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import bills from './Bills/bills.type';
|
|||||||
import vendors from './vendors/vendors.types';
|
import vendors from './vendors/vendors.types';
|
||||||
import paymentReceives from './PaymentReceive/paymentReceive.type';
|
import paymentReceives from './PaymentReceive/paymentReceive.type';
|
||||||
import paymentMades from './PaymentMades/paymentMade.type';
|
import paymentMades from './PaymentMades/paymentMade.type';
|
||||||
|
import organizations from './organizations/organizations.types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
...authentication,
|
...authentication,
|
||||||
@@ -52,4 +53,5 @@ export default {
|
|||||||
...bills,
|
...bills,
|
||||||
...paymentReceives,
|
...paymentReceives,
|
||||||
...paymentMades,
|
...paymentMades,
|
||||||
|
...organizations,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { Router, Request, Response } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import asyncMiddleware from "api/middleware/asyncMiddleware";
|
import asyncMiddleware from "api/middleware/asyncMiddleware";
|
||||||
import JWTAuth from 'api/middleware/jwtAuth';
|
import JWTAuth from 'api/middleware/jwtAuth';
|
||||||
import TenancyMiddleware from 'api/middleware/TenancyMiddleware';
|
import TenancyMiddleware from 'api/middleware/TenancyMiddleware';
|
||||||
@@ -27,12 +27,14 @@ export default class OrganizationController extends BaseController{
|
|||||||
router.use(JWTAuth);
|
router.use(JWTAuth);
|
||||||
router.use(AttachCurrentTenantUser);
|
router.use(AttachCurrentTenantUser);
|
||||||
router.use(TenancyMiddleware);
|
router.use(TenancyMiddleware);
|
||||||
router.use(SubscriptionMiddleware('main'));
|
|
||||||
|
|
||||||
// Should to seed organization tenant be configured.
|
// Should to seed organization tenant be configured.
|
||||||
|
router.use('/seed', SubscriptionMiddleware('main'));
|
||||||
router.use('/seed', SettingsMiddleware);
|
router.use('/seed', SettingsMiddleware);
|
||||||
router.use('/seed', EnsureConfiguredMiddleware);
|
router.use('/seed', EnsureConfiguredMiddleware);
|
||||||
|
|
||||||
|
router.use('/build', SubscriptionMiddleware('main'));
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/build',
|
'/build',
|
||||||
asyncMiddleware(this.build.bind(this))
|
asyncMiddleware(this.build.bind(this))
|
||||||
@@ -41,6 +43,10 @@ export default class OrganizationController extends BaseController{
|
|||||||
'/seed',
|
'/seed',
|
||||||
asyncMiddleware(this.seed.bind(this)),
|
asyncMiddleware(this.seed.bind(this)),
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/all',
|
||||||
|
asyncMiddleware(this.allOrganizations.bind(this)),
|
||||||
|
);
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,4 +122,21 @@ export default class OrganizationController extends BaseController{
|
|||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listing all organizations that assocaited to the authorized user.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
async allOrganizations(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { user } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const organizations = await this.organizationService.listOrganizations(user);
|
||||||
|
return res.status(200).send({ organizations });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
import { ITenant } from 'interfaces';
|
import { ISystemService, ISystemUser, ITenant } from 'interfaces';
|
||||||
import {
|
import {
|
||||||
EventDispatcher,
|
EventDispatcher,
|
||||||
EventDispatcherInterface,
|
EventDispatcherInterface,
|
||||||
@@ -38,7 +38,7 @@ export default class OrganizationService {
|
|||||||
* @param {srting} organizationId
|
* @param {srting} organizationId
|
||||||
* @return {Promise<void>}
|
* @return {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async build(organizationId: string): Promise<void> {
|
public async build(organizationId: string): Promise<void> {
|
||||||
const tenant = await this.getTenantByOrgIdOrThrowError(organizationId);
|
const tenant = await this.getTenantByOrgIdOrThrowError(organizationId);
|
||||||
this.throwIfTenantInitizalized(tenant);
|
this.throwIfTenantInitizalized(tenant);
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ export default class OrganizationService {
|
|||||||
* @param {number} organizationId
|
* @param {number} organizationId
|
||||||
* @return {Promise<void>}
|
* @return {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async seed(organizationId: string): Promise<void> {
|
public async seed(organizationId: string): Promise<void> {
|
||||||
const tenant = await this.getTenantByOrgIdOrThrowError(organizationId);
|
const tenant = await this.getTenantByOrgIdOrThrowError(organizationId);
|
||||||
this.throwIfTenantSeeded(tenant);
|
this.throwIfTenantSeeded(tenant);
|
||||||
|
|
||||||
@@ -91,6 +91,20 @@ export default class OrganizationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listing all associated organizations to the given user.
|
||||||
|
* @param {ISystemUser} user -
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async listOrganizations(user: ISystemUser): Promise<ITenant[]> {
|
||||||
|
this.logger.info('[organization] trying to list all organizations.', { user });
|
||||||
|
|
||||||
|
const { tenantRepository } = this.sysRepositories;
|
||||||
|
const tenant = await tenantRepository.getByIdWithSubscriptions(user.tenantId);
|
||||||
|
|
||||||
|
return [tenant];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Throws error in case the given tenant is undefined.
|
* Throws error in case the given tenant is undefined.
|
||||||
* @param {ITenant} tenant
|
* @param {ITenant} tenant
|
||||||
|
|||||||
@@ -63,11 +63,23 @@ export default class TenantRepository extends SystemRepository {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve tenant details by the given tenant id.
|
* Retrieve tenant details by the given tenant id.
|
||||||
* @param {string} tenantId
|
* @param {string} tenantId - Tenant id.
|
||||||
*/
|
*/
|
||||||
getById(tenantId: number) {
|
getById(tenantId: number) {
|
||||||
return this.cache.get(`tenant.id.${tenantId}`, () => {
|
return this.cache.get(`tenant.id.${tenantId}`, () => {
|
||||||
return Tenant.query().findById(tenantId);
|
return Tenant.query().findById(tenantId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve tenant details with associated subscriptions
|
||||||
|
* and plans by the given tenant id.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
*/
|
||||||
|
getByIdWithSubscriptions(tenantId: number) {
|
||||||
|
return this.cache.get(`tenant.id.${tenantId}.subscriptions`, () => {
|
||||||
|
return Tenant.query().findById(tenantId)
|
||||||
|
.withGraphFetched('subscriptions.plan');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user