diff --git a/src/common/features.js b/src/common/features.js new file mode 100644 index 000000000..81df5f3be --- /dev/null +++ b/src/common/features.js @@ -0,0 +1,6 @@ + + +export const Features = { + Warehouses: 'warehouses', + Branches: 'branches' +} \ No newline at end of file diff --git a/src/common/index.js b/src/common/index.js index 626d9153b..2dc45dc41 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -1,3 +1,4 @@ export * from './TableStyle'; +export * from './features'; export const Align = { Left: 'left', Right: 'right', Center: 'center' }; diff --git a/src/components/FeatureGuard/FeatureCan.js b/src/components/FeatureGuard/FeatureCan.js new file mode 100644 index 000000000..9e21441b6 --- /dev/null +++ b/src/components/FeatureGuard/FeatureCan.js @@ -0,0 +1,13 @@ +import React from 'react'; +import * as R from 'ramda'; +import withFeatureCan from './withFeatureCan'; + +function FeatureCanJSX({ feature, children, isFeatureCan }) { + return isFeatureCan && children; +} + +export const FeatureCan = R.compose( + withFeatureCan(({ isFeatureCan }) => ({ + isFeatureCan, + })), +)(FeatureCanJSX); diff --git a/src/components/FeatureGuard/index.js b/src/components/FeatureGuard/index.js new file mode 100644 index 000000000..d8e32e3b0 --- /dev/null +++ b/src/components/FeatureGuard/index.js @@ -0,0 +1 @@ +export * from './FeatureCan'; \ No newline at end of file diff --git a/src/components/FeatureGuard/withFeatureCan.js b/src/components/FeatureGuard/withFeatureCan.js new file mode 100644 index 000000000..aee7666a8 --- /dev/null +++ b/src/components/FeatureGuard/withFeatureCan.js @@ -0,0 +1,17 @@ +import { connect } from 'react-redux'; +import { getDashboardFeaturesSelector } from '../../store/dashboard/dashboard.selectors'; + +export default (mapState) => { + const featuresSelector = getDashboardFeaturesSelector(); + + const mapStateToProps = (state, props) => { + const features = featuresSelector(state); + + const mapped = { + isFeatureCan: !!features[props.feature], + features, + }; + return mapState ? mapState(mapped, state, props) : mapped; + }; + return connect(mapStateToProps); +}; diff --git a/src/components/index.js b/src/components/index.js index d24fccb58..79f7b5acd 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -96,6 +96,7 @@ export * from './Skeleton'; export * from './FinancialStatement'; export * from './FinancialReport'; export * from './FinancialSheet'; +export * from './FeatureGuard'; const Hint = FieldHint; diff --git a/src/containers/Drawers/ItemDetailDrawer/ItemDetailTab.js b/src/containers/Drawers/ItemDetailDrawer/ItemDetailTab.js index a200ba943..3acaea233 100644 --- a/src/containers/Drawers/ItemDetailDrawer/ItemDetailTab.js +++ b/src/containers/Drawers/ItemDetailDrawer/ItemDetailTab.js @@ -1,12 +1,17 @@ import React from 'react'; import { Tab } from '@blueprintjs/core'; + import { DrawerMainTabs, FormattedMessage as T } from 'components'; import { ItemPaymentTransactions } from './ItemPaymentTransactions'; import ItemDetailHeader from './ItemDetailHeader'; import WarehousesLocationsTable from './WarehousesLocations'; +import { Features } from 'common'; +import { useFeatureCan } from 'hooks/state'; export default function ItemDetailTab() { + const { featureCan } = useFeatureCan(); + return ( } panel={} /> - } - panel={} - /> + {featureCan(Features.Warehouses) && ( + } + panel={} + /> + )} ); } diff --git a/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormTopBar.js b/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormTopBar.js index 1cc828986..90d4c9bbd 100644 --- a/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormTopBar.js +++ b/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormTopBar.js @@ -7,48 +7,66 @@ import { NavbarDivider, } from '@blueprintjs/core'; -import { Icon, FormattedMessage as T, CustomSelectList } from 'components'; - +import { useFeatureCan } from 'hooks/state'; +import { + Icon, + FormattedMessage as T, + CustomSelectList, + FeatureCan, +} from 'components'; import { useInvoiceFormContext } from './InvoiceFormProvider'; +import { Features } from 'common'; export default function InvoiceFormTopBar() { const { warehouses, branches } = useInvoiceFormContext(); + const { featureCan } = useFeatureCan(); + + // Can't display the navigation bar if warehouses or branches feature is not enabled. + if (!featureCan(Features.Warehouses) || !featureCan(Features.Branches)) { + return null; + } return ( - - {({ form, field: { value }, meta: { error, touched } }) => ( - { - form.setFieldValue('branch_id', id); - }} - selectedItemId={value} - buttonProps={{ - icon: , - }} - /> - )} - + + + {({ form, field: { value }, meta: { error, touched } }) => ( + { + form.setFieldValue('branch_id', id); + }} + selectedItemId={value} + buttonProps={{ + icon: , + }} + /> + )} + + - - - {({ form, field: { value }, meta: { error, touched } }) => ( - { - form.setFieldValue('warehouse_id', id); - }} - selectedItemId={value} - buttonProps={{ - icon: , - }} - /> - )} - + {featureCan(Features.Warehouses) && featureCan(Features.Branches) && ( + + )} + + + {({ form, field: { value }, meta: { error, touched } }) => ( + { + form.setFieldValue('warehouse_id', id); + }} + selectedItemId={value} + buttonProps={{ + icon: , + }} + /> + )} + + ); diff --git a/src/hooks/state/feature.js b/src/hooks/state/feature.js new file mode 100644 index 000000000..4ffe96954 --- /dev/null +++ b/src/hooks/state/feature.js @@ -0,0 +1,17 @@ +import { useSelector } from 'react-redux'; +import { createSelector } from 'reselect'; + +const featuresSelector = createSelector( + (state) => state.dashboard.features, + (features) => features, +); + +export const useFeatureCan = () => { + const features = useSelector(featuresSelector); + + return { + featureCan: (feature) => { + return !!features[feature]; + }, + }; +}; diff --git a/src/hooks/state/index.js b/src/hooks/state/index.js index 75580b504..0b5bb3434 100644 --- a/src/hooks/state/index.js +++ b/src/hooks/state/index.js @@ -3,4 +3,5 @@ export * from './authentication'; export * from './globalErrors'; export * from './subscriptions'; export * from './organizations'; -export * from './settings'; \ No newline at end of file +export * from './settings'; +export * from './feature'; \ No newline at end of file diff --git a/src/store/dashboard/dashboard.reducer.js b/src/store/dashboard/dashboard.reducer.js index 4108cf3af..a512de8f8 100644 --- a/src/store/dashboard/dashboard.reducer.js +++ b/src/store/dashboard/dashboard.reducer.js @@ -19,6 +19,10 @@ const initialState = { splashScreenLoading: null, appIsLoading: true, appIntlIsLoading: true, + features: { + branches: true, + warehouses: true, + }, }; const STORAGE_KEY = 'bigcapital:dashboard'; diff --git a/src/store/dashboard/dashboard.selectors.js b/src/store/dashboard/dashboard.selectors.js index 53e9f2e5c..fd7ef77d1 100644 --- a/src/store/dashboard/dashboard.selectors.js +++ b/src/store/dashboard/dashboard.selectors.js @@ -38,3 +38,12 @@ export const getDrawerPayloadFactory = () => createSelector(drawerByNameSelector, (drawer) => { return { ...drawer?.payload }; }); + +const featuresSelector = (state, props) => { + return state.dashboard.features; +}; + +export const getDashboardFeaturesSelector = () => + createSelector(featuresSelector, (features) => { + return features; + });