feat: register pages routes guards.

feat: retrieve all organizations details to authenticated user.
feat: redux organization reducers and actions.
This commit is contained in:
Ahmed Bouhuolia
2020-10-11 00:08:51 +02:00
parent 8622320eef
commit 507690fedf
22 changed files with 348 additions and 66 deletions

View File

@@ -20,7 +20,7 @@ function App({ locale }) {
const queryConfig = {
queries: {
refetchOnWindowFocus: false,
}
},
};
return (
<IntlProvider locale={locale} messages={messages}>

View File

@@ -1,6 +1,8 @@
import React from 'react';
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 DashboardContent from 'components/Dashboard/DashboardContent';
@@ -9,29 +11,56 @@ import PreferencesContent from 'components/Preferences/PreferencesContent';
import PreferencesSidebar from 'components/Preferences/PreferencesSidebar';
import Search from 'containers/GeneralSearch/Search';
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 (
<div className={classNames('dashboard')}>
<Switch>
<Route path="/preferences">
<DashboardSplitPane>
<Sidebar />
<PreferencesSidebar />
</DashboardSplitPane>
<PreferencesContent />
</Route>
<EnsureOrganizationIsReady>
<DashboardLoadingIndicator
isLoading={
fetchOrganizations.isLoading
}>
<Switch>
<Route path="/preferences">
<DashboardSplitPane>
<Sidebar />
<PreferencesSidebar />
</DashboardSplitPane>
<PreferencesContent />
</Route>
<Route path="/">
<DashboardSplitPane>
<Sidebar />
<DashboardContent />
</DashboardSplitPane>
</Route>
</Switch>
<Route path="/">
<DashboardSplitPane>
<Sidebar />
<DashboardContent />
</DashboardSplitPane>
</Route>
</Switch>
<Search />
<DialogsContainer />
</div>
<Search />
<DialogsContainer />
</DashboardLoadingIndicator>
</EnsureOrganizationIsReady>
);
}
export default compose(
withSettingsActions,
withOrganizationsActions,
)(Dashboard);

View 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>
);
}

View 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'
}}
/>
);
}

View File

@@ -19,6 +19,7 @@ import Icon from 'components/Icon';
import { If } from 'components';
import withAuthenticationActions from './withAuthenticationActions';
import withOrganizationsActions from 'containers/Organization/withOrganizationActions';
import { compose } from 'utils';
@@ -29,6 +30,7 @@ const ERRORS_TYPES = {
};
function Login({
requestLogin,
requestOrganizationsList,
}) {
const { formatMessage } = useIntl();
const history = useHistory();
@@ -168,4 +170,5 @@ function Login({
export default compose(
withAuthenticationActions,
withOrganizationsActions,
)(Login);

View File

@@ -1,12 +1,23 @@
import React, { useState } from 'react';
import { Icon } from 'components';
import React, { useState, useCallback } from 'react';
import { Icon, If } from 'components';
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 onClickLogout = useCallback(() => {
requestLogout();
}, [requestLogout]);
return (
<section className={'register-page__left-section'}>
<div className={'content'}>
@@ -27,17 +38,18 @@ export default function RegisterLeftSection({
<T id={'you_have_a_bigcapital_account'} />
</p>
<div className={'content-org'}>
<span>
<T id={'welcome'} />
{org},
</span>
<span>
<a href={'#!'}>
<T id={'sign_out'} />
</a>
</span>
</div>
<If condition={!!isAuthorized}>
<div className={'content-org'}>
<span>
<T id={'welcome'} />
{org},
</span>
<span>
<a onClick={onClickLogout} href="#"><T id={'sign_out'} /></a>
</span>
</div>
</If>
<div className={'content-contact'}>
<a href={'#!'}>
@@ -54,4 +66,9 @@ export default function RegisterLeftSection({
</div>
</section>
)
}
}
export default compose(
withAuthentication(({ isAuthorized }) => ({ isAuthorized })),
withAuthenticationActions,
)(RegisterLeftSection);

View File

@@ -1,26 +1,55 @@
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 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 () {
const history = useHistory();
return (
<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">
<Switch>
{ registerRoutes.map((route, index) => (
<Route
exact={route.exact}
key={index}
path={`${route.path}`}
component={route.component}
/>
)) }
</Switch>
</div>
<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>
)
}

View File

@@ -77,17 +77,12 @@ function RegisterUserForm({ requestRegister, requestLogin }) {
onSubmit: (values, { setSubmitting, setErrors }) => {
requestRegister(values)
.then((response) => {
// AppToaster.show({
// message: formatMessage({
// id: 'welcome_organization_account_has_been_created',
// }),
// intent: Intent.SUCCESS,
// });
requestLogin({
crediential: values.email,
password: values.password,
})
.then(() => {
history.push('/register/subscription');
setSubmitting(false);
})
.catch((errors) => {
@@ -98,7 +93,6 @@ function RegisterUserForm({ requestRegister, requestLogin }) {
intent: Intent.SUCCESS,
});
});
// history.push('/auth/login');
})
.catch((errors) => {
if (errors.some((e) => e.type === 'PHONE_NUMBER_EXISTS')) {

View File

@@ -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);

View File

@@ -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);
};

View File

@@ -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);
};

View File

@@ -5,6 +5,7 @@ const initialState = {
token: '',
organization: '',
user: '',
tenant: {},
locale: '',
errors: [],
};
@@ -15,6 +16,7 @@ export default createReducer(initialState, {
state.token = token;
state.user = user;
state.organization = tenant.organization_id;
state.tenant = tenant;
},
[t.LOGIN_FAILURE]: (state, action) => {
@@ -36,3 +38,9 @@ export const isAuthenticated = (state) => !!state.authentication.token;
export const hasErrorType = (state, 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;

View 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); });
});
};

View 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;

View 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;
}
)

View File

@@ -0,0 +1,5 @@
export default {
ORGANIZATIONS_LIST_SET: 'ORGANIZATIONS_LIST_SET',
};

View File

@@ -25,9 +25,11 @@ import bills from './Bills/bills.reducer';
import vendors from './vendors/vendors.reducer';
import paymentReceives from './PaymentReceive/paymentReceive.reducer';
import paymentMades from './PaymentMades/paymentMade.reducer';
import organizations from './organizations/organizations.reducers';
export default combineReducers({
authentication,
organizations,
dashboard,
users,
accounts,

View File

@@ -24,6 +24,7 @@ import bills from './Bills/bills.type';
import vendors from './vendors/vendors.types';
import paymentReceives from './PaymentReceive/paymentReceive.type';
import paymentMades from './PaymentMades/paymentMade.type';
import organizations from './organizations/organizations.types';
export default {
...authentication,
@@ -52,4 +53,5 @@ export default {
...bills,
...paymentReceives,
...paymentMades,
...organizations,
};