feat: add feature guard as hook and component.

This commit is contained in:
a.bouhuolia
2022-02-13 16:29:50 +02:00
parent fdd52f1ecf
commit 9b7befc544
12 changed files with 134 additions and 39 deletions

6
src/common/features.js Normal file
View File

@@ -0,0 +1,6 @@
export const Features = {
Warehouses: 'warehouses',
Branches: 'branches'
}

View File

@@ -1,3 +1,4 @@
export * from './TableStyle';
export * from './features';
export const Align = { Left: 'left', Right: 'right', Center: 'center' };

View File

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

View File

@@ -0,0 +1 @@
export * from './FeatureCan';

View File

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

View File

@@ -96,6 +96,7 @@ export * from './Skeleton';
export * from './FinancialStatement';
export * from './FinancialReport';
export * from './FinancialSheet';
export * from './FeatureGuard';
const Hint = FieldHint;

View File

@@ -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 (
<DrawerMainTabs renderActiveTabPanelOnly={true}>
<Tab
@@ -19,11 +24,13 @@ export default function ItemDetailTab() {
title={<T id={'transactions'} />}
panel={<ItemPaymentTransactions />}
/>
<Tab
id={'warehouses'}
title={<T id={'warehouse_locations.label'} />}
panel={<WarehousesLocationsTable />}
/>
{featureCan(Features.Warehouses) && (
<Tab
id={'warehouses'}
title={<T id={'warehouse_locations.label'} />}
panel={<WarehousesLocationsTable />}
/>
)}
</DrawerMainTabs>
);
}

View File

@@ -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 (
<Navbar className={'navbar--dashboard-topbar'}>
<NavbarGroup align={Alignment.LEFT}>
<FastField name={'branch_id'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<CustomSelectList
items={branches}
text={'Branch'}
onItemSelected={({ id }) => {
form.setFieldValue('branch_id', id);
}}
selectedItemId={value}
buttonProps={{
icon: <Icon icon={'branch-16'} iconSize={20} />,
}}
/>
)}
</FastField>
<FeatureCan feature={Features.Warehouses}>
<FastField name={'branch_id'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<CustomSelectList
items={branches}
text={'Branch'}
onItemSelected={({ id }) => {
form.setFieldValue('branch_id', id);
}}
selectedItemId={value}
buttonProps={{
icon: <Icon icon={'branch-16'} iconSize={20} />,
}}
/>
)}
</FastField>
</FeatureCan>
<NavbarDivider />
<FastField name={'warehouse_id'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<CustomSelectList
items={warehouses}
text={'Warehosue'}
onItemSelected={({ id }) => {
form.setFieldValue('warehouse_id', id);
}}
selectedItemId={value}
buttonProps={{
icon: <Icon icon={'warehouse-16'} iconSize={20} />,
}}
/>
)}
</FastField>
{featureCan(Features.Warehouses) && featureCan(Features.Branches) && (
<NavbarDivider />
)}
<FeatureCan feature={Features.Warehouses}>
<FastField name={'warehouse_id'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<CustomSelectList
items={warehouses}
text={'Warehosue'}
onItemSelected={({ id }) => {
form.setFieldValue('warehouse_id', id);
}}
selectedItemId={value}
buttonProps={{
icon: <Icon icon={'warehouse-16'} iconSize={20} />,
}}
/>
)}
</FastField>
</FeatureCan>
</NavbarGroup>
</Navbar>
);

View File

@@ -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];
},
};
};

View File

@@ -3,4 +3,5 @@ export * from './authentication';
export * from './globalErrors';
export * from './subscriptions';
export * from './organizations';
export * from './settings';
export * from './settings';
export * from './feature';

View File

@@ -19,6 +19,10 @@ const initialState = {
splashScreenLoading: null,
appIsLoading: true,
appIntlIsLoading: true,
features: {
branches: true,
warehouses: true,
},
};
const STORAGE_KEY = 'bigcapital:dashboard';

View File

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