diff --git a/client/src/common/registerWizard.js b/client/src/common/registerWizard.js index b7dedb9f3..634f2271a 100644 --- a/client/src/common/registerWizard.js +++ b/client/src/common/registerWizard.js @@ -2,15 +2,15 @@ export const registerWizardSteps = [ { - label: 'organization_register', + label: 'payment_or_trial', }, { - label: 'payment_or_trial', + label: 'initializing', }, { label: 'getting_started', }, { - label: 'initializing', + label: 'Congratulations', }, ]; \ No newline at end of file diff --git a/client/src/components/Dashboard/EnsureOrganizationIsReady.js b/client/src/components/Dashboard/EnsureOrganizationIsReady.js index a36d48896..d24e32079 100644 --- a/client/src/components/Dashboard/EnsureOrganizationIsReady.js +++ b/client/src/components/Dashboard/EnsureOrganizationIsReady.js @@ -2,8 +2,10 @@ 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 withOrganizationByOrgId from 'containers/Organization/withOrganizationByOrgId'; +import withOrganization from 'containers/Organization/withOrganization'; + function EnsureOrganizationIsReady({ // #ownProps @@ -11,9 +13,9 @@ function EnsureOrganizationIsReady({ redirectTo = '/setup', // #withOrganizationByOrgId - organization, + isOrganizationBuilt, }) { - return (organization.is_ready) ? children : ( + return (isOrganizationBuilt) ? children : ( @@ -25,5 +27,5 @@ export default compose( connect((state, props) => ({ organizationId: props.currentOrganizationId, })), - withOrganizationByOrgId(), + withOrganization(({ isOrganizationBuilt }) => ({ isOrganizationBuilt })), )(EnsureOrganizationIsReady); \ No newline at end of file diff --git a/client/src/components/Dashboard/PrivatePages.js b/client/src/components/Dashboard/PrivatePages.js index 2b34c076b..499898113 100644 --- a/client/src/components/Dashboard/PrivatePages.js +++ b/client/src/components/Dashboard/PrivatePages.js @@ -14,11 +14,12 @@ import { compose } from 'utils'; * Dashboard inner private pages. */ function DashboardPrivatePages({ - requestOrganizationsList, + + // #withOrganizationActions + requestAllOrganizations, }) { const fetchOrganizations = useQuery( - ['organizations'], - () => requestOrganizationsList(), + ['organizations'], () => requestAllOrganizations(), ); return ( diff --git a/client/src/containers/Authentication/withAuthentication.js b/client/src/containers/Authentication/withAuthentication.js index 34a678227..94d846f28 100644 --- a/client/src/containers/Authentication/withAuthentication.js +++ b/client/src/containers/Authentication/withAuthentication.js @@ -6,7 +6,7 @@ export default (mapState) => { const mapped = { isAuthorized: isAuthenticated(state), user: state.authentication.user, - currentOrganizationId: state.authentication?.tenant?.organization_id, + currentOrganizationId: state.authentication?.organizationId, }; return mapState ? mapState(mapped, state, props) : mapped; }; diff --git a/client/src/containers/Organization/withOrganization.js b/client/src/containers/Organization/withOrganization.js new file mode 100644 index 000000000..af467dffa --- /dev/null +++ b/client/src/containers/Organization/withOrganization.js @@ -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); +}; \ No newline at end of file diff --git a/client/src/containers/Organization/withOrganizationActions.js b/client/src/containers/Organization/withOrganizationActions.js index d2eeea2a6..e44284b3b 100644 --- a/client/src/containers/Organization/withOrganizationActions.js +++ b/client/src/containers/Organization/withOrganizationActions.js @@ -5,10 +5,10 @@ import { seedTenant, } from 'store/organizations/organizations.actions'; -export const mapDispatchToProps = (dispatch) => ({ - requestOrganizationsList: () => dispatch(fetchOrganizations()), - requestBuildTenant: () => dispatch(buildTenant()), - requestSeedTenant: () => dispatch(seedTenant()), +const mapDispatchToProps = (dispatch) => ({ + requestOrganizationBuild: () => dispatch(buildTenant()), + requestOrganizationSeed: () => dispatch(seedTenant()), + requestAllOrganizations: () => dispatch(fetchOrganizations()), }); export default connect(null, mapDispatchToProps); \ No newline at end of file diff --git a/client/src/containers/Organization/withOrganizationByOrgId.js b/client/src/containers/Organization/withOrganizationByOrgId.js deleted file mode 100644 index 9d94d97f0..000000000 --- a/client/src/containers/Organization/withOrganizationByOrgId.js +++ /dev/null @@ -1,16 +0,0 @@ -import { connect } from 'react-redux'; -import { - getOrganizationByOrgIdFactory, -} from 'store/organizations/organizations.selectors'; - -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); -}; \ No newline at end of file diff --git a/client/src/containers/Organization/withOrganizationByTenId.js b/client/src/containers/Organization/withOrganizationByTenId.js deleted file mode 100644 index a92fe1a8b..000000000 --- a/client/src/containers/Organization/withOrganizationByTenId.js +++ /dev/null @@ -1,16 +0,0 @@ -import { connect } from 'react-redux'; -import { - getOrganizationByTenantIdFactory, -} from 'store/organizations/organizations.selectors'; - -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); -}; \ No newline at end of file diff --git a/client/src/containers/Setup/EnsureOrganizationIsNotReady.js b/client/src/containers/Setup/EnsureOrganizationIsNotReady.js index a31425b9b..a559b0f07 100644 --- a/client/src/containers/Setup/EnsureOrganizationIsNotReady.js +++ b/client/src/containers/Setup/EnsureOrganizationIsNotReady.js @@ -3,23 +3,25 @@ import { connect } from 'react-redux'; import { Redirect } from 'react-router-dom'; import { compose } from 'utils'; import withAuthentication from 'containers/Authentication/withAuthentication'; -import withOrganizationByOrgId from 'containers/Organization/withOrganizationByOrgId'; +import withOrganization from 'containers/Organization/withOrganization'; function EnsureOrganizationIsNotReady({ children, - // #withOrganizationByOrgId - organization, + // #withOrganization + isOrganizationReady, }) { - return (organization.is_ready) ? ( + return (isOrganizationReady) ? ( ) : children; } export default compose( - withAuthentication(), + withAuthentication(({ currentOrganizationId }) => ({ + currentOrganizationId, + })), connect((state, props) => ({ organizationId: props.currentOrganizationId, })), - withOrganizationByOrgId(), + withOrganization(({ isOrganizationReady }) => ({ isOrganizationReady })), )(EnsureOrganizationIsNotReady); \ No newline at end of file diff --git a/client/src/containers/Setup/SetupInitializingForm.js b/client/src/containers/Setup/SetupInitializingForm.js new file mode 100644 index 000000000..bbae58a09 --- /dev/null +++ b/client/src/containers/Setup/SetupInitializingForm.js @@ -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 ( +
+

You organization is initializin...

+
+ ); +} + +export default compose( + withOrganizationActions +)(SetupInitializingForm); \ No newline at end of file diff --git a/client/src/containers/Setup/SetupLeftSection.js b/client/src/containers/Setup/SetupLeftSection.js index 5dc327998..732bf0f66 100644 --- a/client/src/containers/Setup/SetupLeftSection.js +++ b/client/src/containers/Setup/SetupLeftSection.js @@ -1,8 +1,7 @@ import React, { useState, useCallback } from 'react'; -import { Icon, If } from 'components'; +import { Icon } from 'components'; import { FormattedMessage as T } from 'react-intl'; -import withAuthentication from 'containers/Authentication/withAuthentication'; import withAuthenticationActions from 'containers/Authentication/withAuthenticationActions'; import { compose } from 'utils'; @@ -11,8 +10,8 @@ import { compose } from 'utils'; * Wizard setup left section. */ function SetupLeftSection({ + // #withAuthenticationActions requestLogout, - isAuthorized }) { const [org] = useState('LibyanSpider'); @@ -40,17 +39,15 @@ function SetupLeftSection({

- -
- - - {org}, - - - - -
-
+
+ + + {org}, + + + + +
@@ -70,6 +67,5 @@ function SetupLeftSection({ } export default compose( - withAuthentication(({ isAuthorized }) => ({ isAuthorized })), withAuthenticationActions, )(SetupLeftSection); diff --git a/client/src/containers/Setup/SetupOrganizationForm.js b/client/src/containers/Setup/SetupOrganizationForm.js index 29c264bd0..23ba2d122 100644 --- a/client/src/containers/Setup/SetupOrganizationForm.js +++ b/client/src/containers/Setup/SetupOrganizationForm.js @@ -26,7 +26,10 @@ import withOrganizationActions from 'containers/Organization/withOrganizationAct import { compose, optionsMapToArray } from 'utils'; -function SetupOrganizationForm({ requestSubmitOptions, requestSeedTenant }) { +function SetupOrganizationForm({ + requestSubmitOptions, + requestSeedTenant +}) { const { formatMessage } = useIntl(); const [selected, setSelected] = useState(); const history = useHistory(); diff --git a/client/src/containers/Setup/SetupRightSection.js b/client/src/containers/Setup/SetupRightSection.js index d00d38ccf..645ef849a 100644 --- a/client/src/containers/Setup/SetupRightSection.js +++ b/client/src/containers/Setup/SetupRightSection.js @@ -2,33 +2,48 @@ 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 ({ - isTenantHasSubscriptions: hasSubscriptions = false, + // #withAuthentication + currentOrganizationId, + + // #withOrganization + isOrganizationInitialized, + isOrganizationSubscribed: hasSubscriptions, + isOrganizationSeeded }) { const history = useHistory(); + const handleSkip = useCallback(({ step, push }) => { const scenarios = [ - { condition: hasSubscriptions, redirectTo: 'organization' }, { condition: !hasSubscriptions, redirectTo: 'subscription' }, + // { condition: , redirectTo: 'initializing' } + { condition: !hasSubscriptions, redirectTo: 'organization' }, ]; const scenario = scenarios.find((scenario) => scenario.condition); if (scenario) { push(scenario.redirectTo); } - }, [hasSubscriptions]); + }, [ + hasSubscriptions, + isOrganizationInitialized, + isOrganizationSeeded, + ]); return (
@@ -48,6 +63,10 @@ function SetupRightSection ({ + + + + @@ -66,5 +85,19 @@ function SetupRightSection ({ } export default compose( - withAuthentication(({ isAuthorized }) => ({ isAuthorized })), + withAuthentication(({ currentOrganizationId }) => ({ currentOrganizationId })), + connect((state, props) => ({ + organizationId: props.currentOrganizationId, + })), + withOrganization(({ + organization, + isOrganizationInitialized, + isOrganizationSubscribed, + isOrganizationSeeded, + }) => ({ + organization, + isOrganizationInitialized, + isOrganizationSubscribed, + isOrganizationSeeded, + })), )(SetupRightSection); \ No newline at end of file diff --git a/client/src/containers/Setup/SetupSubscriptionForm.js b/client/src/containers/Setup/SetupSubscriptionForm.js index 17fab2a76..5ae1e9684 100644 --- a/client/src/containers/Setup/SetupSubscriptionForm.js +++ b/client/src/containers/Setup/SetupSubscriptionForm.js @@ -5,6 +5,9 @@ import { FormattedMessage as T } from 'react-intl'; import { Button, Intent } from '@blueprintjs/core'; import BillingTab from 'containers/Subscriptions/BillingTab'; +/** + * Subscription step of wizard setup. + */ export default function SetupSubscriptionForm({ }) { diff --git a/client/src/containers/Setup/WizardSetupPage.js b/client/src/containers/Setup/WizardSetupPage.js index dcd4debb8..ba775531b 100644 --- a/client/src/containers/Setup/WizardSetupPage.js +++ b/client/src/containers/Setup/WizardSetupPage.js @@ -5,9 +5,7 @@ import SetupRightSection from './SetupRightSection'; import SetupLeftSection from './SetupLeftSection'; -export default function WizardSetupPage({ - organizationId, -}) { +export default function WizardSetupPage() { return (
diff --git a/client/src/store/authentication/authentication.reducer.js b/client/src/store/authentication/authentication.reducer.js index 4cd2f0adc..12db81283 100644 --- a/client/src/store/authentication/authentication.reducer.js +++ b/client/src/store/authentication/authentication.reducer.js @@ -4,6 +4,7 @@ import t from 'store/types'; const initialState = { token: '', organization: '', + organizationId: null, user: '', tenant: {}, locale: '', @@ -16,6 +17,7 @@ export default createReducer(initialState, { state.token = token; state.user = user; state.organization = tenant.organization_id; + state.organizationId = tenant.id; state.tenant = tenant; }, diff --git a/client/src/store/organizations/organizations.actions.js b/client/src/store/organizations/organizations.actions.js index 8f6b84934..3cbb39f22 100644 --- a/client/src/store/organizations/organizations.actions.js +++ b/client/src/store/organizations/organizations.actions.js @@ -13,18 +13,38 @@ export const fetchOrganizations = () => (dispatch) => new Promise((resolve, reje }).catch(error => { reject(error); }); }); -export const buildTenant = () => (dispatch) => new Promise((resolve, reject) => { +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) => new Promise((resolve, reject) => { +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 || []); diff --git a/client/src/store/organizations/organizations.reducers.js b/client/src/store/organizations/organizations.reducers.js index b8407108d..5f1d1f848 100644 --- a/client/src/store/organizations/organizations.reducers.js +++ b/client/src/store/organizations/organizations.reducers.js @@ -20,6 +20,44 @@ const reducer = createReducer(initialState, { state.data = _data; 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; \ No newline at end of file diff --git a/client/src/store/organizations/organizations.selectors.js b/client/src/store/organizations/organizations.selectors.js index e4a2ba6da..a8b751a8e 100644 --- a/client/src/store/organizations/organizations.selectors.js +++ b/client/src/store/organizations/organizations.selectors.js @@ -1,18 +1,50 @@ import { createSelector } from '@reduxjs/toolkit'; -const oragnizationByTenantIdSelector = (state, props) => state.organizations[props.tenantId]; -const organizationByIdSelector = (state, props) => state.organizations.byOrganizationId[props.organizationId]; -const organizationsDataSelector = (state, props) => state.organizations.data; +const organizationSelector = (state, props) => state.organizations.data[props.organizationId]; -export const getOrganizationByOrgIdFactory = () => createSelector( - organizationByIdSelector, - organizationsDataSelector, - (organizationId, organizationsData) => { - return organizationsData[organizationId]; - } +export const getOrganizationByIdFactory = () => createSelector( + organizationSelector, + (organization) => organization ); -export const getOrganizationByTenantIdFactory = () => createSelector( - oragnizationByTenantIdSelector, - (organization) => organization, -); \ No newline at end of file +export const isOrganizationSeededFactory = () => createSelector( + organizationSelector, + (organization) => { + return !!organization?.seeded_at; + }, +); + +export const isOrganizationBuiltFactory = () => createSelector( + organizationSelector, + (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; + } +) \ No newline at end of file diff --git a/client/src/store/organizations/organizations.types.js b/client/src/store/organizations/organizations.types.js index 1b9d47692..924ea7238 100644 --- a/client/src/store/organizations/organizations.types.js +++ b/client/src/store/organizations/organizations.types.js @@ -1,5 +1,10 @@ - export default { 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', }; \ No newline at end of file diff --git a/server/src/database/seeds/core/20200810121807_seed_views.js b/server/src/database/seeds/core/20200810121807_seed_views.js index 5e945978c..19da37d7b 100644 --- a/server/src/database/seeds/core/20200810121807_seed_views.js +++ b/server/src/database/seeds/core/20200810121807_seed_views.js @@ -1,3 +1,5 @@ +import Container from 'typedi'; +import TenancyService from 'services/Tenancy/TenancyService' exports.up = (knex) => { const tenancyService = Container.get(TenancyService); diff --git a/server/src/services/Authentication/index.ts b/server/src/services/Authentication/index.ts index 12ccab4ec..5a97bc254 100644 --- a/server/src/services/Authentication/index.ts +++ b/server/src/services/Authentication/index.ts @@ -87,9 +87,6 @@ export default class AuthenticationService implements IAuthenticationService { // Remove password property from user object. Reflect.deleteProperty(user, 'password'); - // Remove id property from tenant object. - Reflect.deleteProperty(tenant, 'id'); - return { user, token, tenant }; }