mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 14:50:32 +00:00
feat: add feature guard as hook and component.
This commit is contained in:
6
src/common/features.js
Normal file
6
src/common/features.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export const Features = {
|
||||||
|
Warehouses: 'warehouses',
|
||||||
|
Branches: 'branches'
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from './TableStyle';
|
export * from './TableStyle';
|
||||||
|
export * from './features';
|
||||||
|
|
||||||
export const Align = { Left: 'left', Right: 'right', Center: 'center' };
|
export const Align = { Left: 'left', Right: 'right', Center: 'center' };
|
||||||
|
|||||||
13
src/components/FeatureGuard/FeatureCan.js
Normal file
13
src/components/FeatureGuard/FeatureCan.js
Normal 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);
|
||||||
1
src/components/FeatureGuard/index.js
Normal file
1
src/components/FeatureGuard/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './FeatureCan';
|
||||||
17
src/components/FeatureGuard/withFeatureCan.js
Normal file
17
src/components/FeatureGuard/withFeatureCan.js
Normal 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);
|
||||||
|
};
|
||||||
@@ -96,6 +96,7 @@ export * from './Skeleton';
|
|||||||
export * from './FinancialStatement';
|
export * from './FinancialStatement';
|
||||||
export * from './FinancialReport';
|
export * from './FinancialReport';
|
||||||
export * from './FinancialSheet';
|
export * from './FinancialSheet';
|
||||||
|
export * from './FeatureGuard';
|
||||||
|
|
||||||
const Hint = FieldHint;
|
const Hint = FieldHint;
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Tab } from '@blueprintjs/core';
|
import { Tab } from '@blueprintjs/core';
|
||||||
|
|
||||||
import { DrawerMainTabs, FormattedMessage as T } from 'components';
|
import { DrawerMainTabs, FormattedMessage as T } from 'components';
|
||||||
import { ItemPaymentTransactions } from './ItemPaymentTransactions';
|
import { ItemPaymentTransactions } from './ItemPaymentTransactions';
|
||||||
import ItemDetailHeader from './ItemDetailHeader';
|
import ItemDetailHeader from './ItemDetailHeader';
|
||||||
import WarehousesLocationsTable from './WarehousesLocations';
|
import WarehousesLocationsTable from './WarehousesLocations';
|
||||||
|
|
||||||
|
import { Features } from 'common';
|
||||||
|
import { useFeatureCan } from 'hooks/state';
|
||||||
|
|
||||||
export default function ItemDetailTab() {
|
export default function ItemDetailTab() {
|
||||||
|
const { featureCan } = useFeatureCan();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DrawerMainTabs renderActiveTabPanelOnly={true}>
|
<DrawerMainTabs renderActiveTabPanelOnly={true}>
|
||||||
<Tab
|
<Tab
|
||||||
@@ -19,11 +24,13 @@ export default function ItemDetailTab() {
|
|||||||
title={<T id={'transactions'} />}
|
title={<T id={'transactions'} />}
|
||||||
panel={<ItemPaymentTransactions />}
|
panel={<ItemPaymentTransactions />}
|
||||||
/>
|
/>
|
||||||
<Tab
|
{featureCan(Features.Warehouses) && (
|
||||||
id={'warehouses'}
|
<Tab
|
||||||
title={<T id={'warehouse_locations.label'} />}
|
id={'warehouses'}
|
||||||
panel={<WarehousesLocationsTable />}
|
title={<T id={'warehouse_locations.label'} />}
|
||||||
/>
|
panel={<WarehousesLocationsTable />}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</DrawerMainTabs>
|
</DrawerMainTabs>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,48 +7,66 @@ import {
|
|||||||
NavbarDivider,
|
NavbarDivider,
|
||||||
} from '@blueprintjs/core';
|
} 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 { useInvoiceFormContext } from './InvoiceFormProvider';
|
||||||
|
import { Features } from 'common';
|
||||||
|
|
||||||
export default function InvoiceFormTopBar() {
|
export default function InvoiceFormTopBar() {
|
||||||
const { warehouses, branches } = useInvoiceFormContext();
|
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 (
|
return (
|
||||||
<Navbar className={'navbar--dashboard-topbar'}>
|
<Navbar className={'navbar--dashboard-topbar'}>
|
||||||
<NavbarGroup align={Alignment.LEFT}>
|
<NavbarGroup align={Alignment.LEFT}>
|
||||||
<FastField name={'branch_id'}>
|
<FeatureCan feature={Features.Warehouses}>
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
<FastField name={'branch_id'}>
|
||||||
<CustomSelectList
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
items={branches}
|
<CustomSelectList
|
||||||
text={'Branch'}
|
items={branches}
|
||||||
onItemSelected={({ id }) => {
|
text={'Branch'}
|
||||||
form.setFieldValue('branch_id', id);
|
onItemSelected={({ id }) => {
|
||||||
}}
|
form.setFieldValue('branch_id', id);
|
||||||
selectedItemId={value}
|
}}
|
||||||
buttonProps={{
|
selectedItemId={value}
|
||||||
icon: <Icon icon={'branch-16'} iconSize={20} />,
|
buttonProps={{
|
||||||
}}
|
icon: <Icon icon={'branch-16'} iconSize={20} />,
|
||||||
/>
|
}}
|
||||||
)}
|
/>
|
||||||
</FastField>
|
)}
|
||||||
|
</FastField>
|
||||||
|
</FeatureCan>
|
||||||
|
|
||||||
<NavbarDivider />
|
{featureCan(Features.Warehouses) && featureCan(Features.Branches) && (
|
||||||
<FastField name={'warehouse_id'}>
|
<NavbarDivider />
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
)}
|
||||||
<CustomSelectList
|
<FeatureCan feature={Features.Warehouses}>
|
||||||
items={warehouses}
|
<FastField name={'warehouse_id'}>
|
||||||
text={'Warehosue'}
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
onItemSelected={({ id }) => {
|
<CustomSelectList
|
||||||
form.setFieldValue('warehouse_id', id);
|
items={warehouses}
|
||||||
}}
|
text={'Warehosue'}
|
||||||
selectedItemId={value}
|
onItemSelected={({ id }) => {
|
||||||
buttonProps={{
|
form.setFieldValue('warehouse_id', id);
|
||||||
icon: <Icon icon={'warehouse-16'} iconSize={20} />,
|
}}
|
||||||
}}
|
selectedItemId={value}
|
||||||
/>
|
buttonProps={{
|
||||||
)}
|
icon: <Icon icon={'warehouse-16'} iconSize={20} />,
|
||||||
</FastField>
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</FeatureCan>
|
||||||
</NavbarGroup>
|
</NavbarGroup>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
);
|
);
|
||||||
|
|||||||
17
src/hooks/state/feature.js
Normal file
17
src/hooks/state/feature.js
Normal 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];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -3,4 +3,5 @@ export * from './authentication';
|
|||||||
export * from './globalErrors';
|
export * from './globalErrors';
|
||||||
export * from './subscriptions';
|
export * from './subscriptions';
|
||||||
export * from './organizations';
|
export * from './organizations';
|
||||||
export * from './settings';
|
export * from './settings';
|
||||||
|
export * from './feature';
|
||||||
@@ -19,6 +19,10 @@ const initialState = {
|
|||||||
splashScreenLoading: null,
|
splashScreenLoading: null,
|
||||||
appIsLoading: true,
|
appIsLoading: true,
|
||||||
appIntlIsLoading: true,
|
appIntlIsLoading: true,
|
||||||
|
features: {
|
||||||
|
branches: true,
|
||||||
|
warehouses: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const STORAGE_KEY = 'bigcapital:dashboard';
|
const STORAGE_KEY = 'bigcapital:dashboard';
|
||||||
|
|||||||
@@ -38,3 +38,12 @@ export const getDrawerPayloadFactory = () =>
|
|||||||
createSelector(drawerByNameSelector, (drawer) => {
|
createSelector(drawerByNameSelector, (drawer) => {
|
||||||
return { ...drawer?.payload };
|
return { ...drawer?.payload };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const featuresSelector = (state, props) => {
|
||||||
|
return state.dashboard.features;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDashboardFeaturesSelector = () =>
|
||||||
|
createSelector(featuresSelector, (features) => {
|
||||||
|
return features;
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user