Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure that disabled feature page cannot be accessed using deep-link #37902

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
17377da
add type PolicyFeatureName
rezkiy37 Mar 7, 2024
7ed9fa6
add a helper isPolicyFeatureEnabled
rezkiy37 Mar 7, 2024
c8a7312
implement FeatureEnabledAccessOrNotFoundComponent
rezkiy37 Mar 7, 2024
cf310c3
integrate FeatureEnabledAccessOrNotFoundComponent to WorkspaceCategor…
rezkiy37 Mar 7, 2024
941e7fa
integrate more features consts
rezkiy37 Mar 7, 2024
699f38a
integrate FeatureEnabledAccessOrNotFoundComponent to WorkspaceTagsPage
rezkiy37 Mar 7, 2024
daacbfe
integrate FeatureEnabledAccessOrNotFoundComponent to WorkspaceCategor…
rezkiy37 Mar 7, 2024
7fb586e
integrate FeatureEnabledAccessOrNotFoundComponent to CreateCategoryPage
rezkiy37 Mar 7, 2024
4bd3e8a
integrate FeatureEnabledAccessOrNotFoundComponent to CategorySettings…
rezkiy37 Mar 7, 2024
7a71b4b
Merge branch 'feature/37373-WorkspaceMoreFeaturesPage' of https://git…
rezkiy37 Mar 11, 2024
09203e4
confirm navigation after onyx updates
rezkiy37 Mar 11, 2024
2040059
redirect instead of not found
rezkiy37 Mar 11, 2024
c3e6470
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Mar 11, 2024
b6f7c34
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Mar 12, 2024
68e55e5
integrate FeatureEnabledAccessOrRedirectWrapper to PolicyDistanceRate…
rezkiy37 Mar 12, 2024
1c1d7fd
integrate FeatureEnabledAccessOrRedirectWrapper to tags pages
rezkiy37 Mar 12, 2024
45d502c
integrate FeatureEnabledAccessOrRedirectWrapper to workflows pages
rezkiy37 Mar 12, 2024
e0580db
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Mar 12, 2024
47b9904
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Mar 14, 2024
139d14b
remove lint disable
rezkiy37 Mar 14, 2024
4764459
fix nav type
rezkiy37 Mar 14, 2024
9da57fa
add a comment
rezkiy37 Mar 14, 2024
5fffd62
use timeout
rezkiy37 Mar 14, 2024
1a3b367
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Mar 14, 2024
f5b7ea0
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Mar 15, 2024
c901703
improve tax case
rezkiy37 Mar 15, 2024
9c23128
use not found flow
rezkiy37 Mar 15, 2024
540b2fa
use profile page to get back
rezkiy37 Mar 15, 2024
169ab18
Merge remote-tracking branch 'expensify/main' into feature/37785-rest…
waterim Mar 18, 2024
417ee81
Merge remote-tracking branch 'expensify/main' into feature/37785-rest…
waterim Mar 18, 2024
216015d
Merge remote-tracking branch 'expensify/main' into feature/37785-rest…
waterim Mar 19, 2024
96bf4ff
Merge remote-tracking branch 'expensify/main' into feature/37785-rest…
waterim Mar 19, 2024
44ae002
navigate to worspaces list on back button
waterim Mar 19, 2024
4e63dac
fix distance page
waterim Mar 19, 2024
1a61fe2
added force fullscrenn
waterim Mar 19, 2024
514bc3b
use fullPageNotFoundPage
waterim Mar 20, 2024
0b908d8
Merge remote-tracking branch 'expensify/main' into feature/37785-rest…
waterim Mar 20, 2024
58e1b4b
add tax pages, update wrapper workflows
waterim Mar 20, 2024
919f7c7
add wrapper to all pages
waterim Mar 20, 2024
c13cda3
ts fix
waterim Mar 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1472,6 +1472,15 @@ const CONST = {
MAKE_MEMBER: 'makeMember',
MAKE_ADMIN: 'makeAdmin',
},
MORE_FEATURES: {
ARE_CATEGORIES_ENABLED: 'areCategoriesEnabled',
ARE_TAGS_ENABLED: 'areTagsEnabled',
ARE_DISTANCE_RATES_ENABLED: 'areDistanceRatesEnabled',
ARE_WORKFLOWS_ENABLED: 'areWorkflowsEnabled',
ARE_REPORTFIELDS_ENABLED: 'areReportFieldsEnabled',
ARE_CONNECTIONS_ENABLED: 'areConnectionsEnabled',
ARE_TAXES_ENABLED: 'tax',
},
CATEGORIES_BULK_ACTION_TYPES: {
DELETE: 'delete',
DISABLE: 'disable',
Expand Down
10 changes: 10 additions & 0 deletions src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {PersonalDetailsList, Policy, PolicyCategories, PolicyMembers, PolicyTagList, PolicyTags} from '@src/types/onyx';
import type {PolicyFeatureName} from '@src/types/onyx/Policy';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import Navigation from './Navigation/Navigation';
Expand Down Expand Up @@ -276,6 +277,14 @@ function goBackFromInvalidPolicy() {
Navigation.navigate(ROUTES.SETTINGS_WORKSPACES);
}

function isPolicyFeatureEnabled(policy: OnyxEntry<Policy> | EmptyObject, featureName: PolicyFeatureName): boolean {
if (featureName === CONST.POLICY.MORE_FEATURES.ARE_TAXES_ENABLED) {
return Boolean(policy?.tax?.trackingEnabled);
}

return Boolean(policy?.[featureName]);
}

export {
getActivePolicies,
hasAccountingConnections,
Expand Down Expand Up @@ -306,6 +315,7 @@ export {
getPathWithoutPolicyID,
getPolicyMembersByIdWithoutCurrentUser,
goBackFromInvalidPolicy,
isPolicyFeatureEnabled,
hasTaxRateError,
hasPolicyCategoriesError,
};
Expand Down
10 changes: 9 additions & 1 deletion src/libs/actions/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3403,7 +3403,15 @@ function navigateWhenEnableFeature(policyID: string, featureRoute: Route) {
return;
}

Navigation.navigate(featureRoute);
/**
* The app needs to set a navigation action to the microtask queue, it guarantees to execute Onyx.update first, then the navigation action.
* More details - https://github.com/Expensify/App/issues/37785#issuecomment-1989056726.
*/
new Promise<void>((resolve) => {
resolve();
}).then(() => {
Navigation.navigate(featureRoute);
});
}

function enablePolicyCategories(policyID: string, enabled: boolean) {
Expand Down
74 changes: 74 additions & 0 deletions src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* eslint-disable rulesdir/no-negated-variables */
import React, {useEffect} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import Navigation from '@libs/Navigation/Navigation';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as Policy from '@userActions/Policy';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
import type {PolicyFeatureName} from '@src/types/onyx/Policy';
import {isEmptyObject} from '@src/types/utils/EmptyObject';

type FeatureEnabledAccessOrNotFoundOnyxProps = {
/** The report currently being looked at */
policy: OnyxEntry<OnyxTypes.Policy>;

/** Indicated whether the report data is loading */
isLoadingReportData: OnyxEntry<boolean>;
};

type FeatureEnabledAccessOrNotFoundComponentProps = FeatureEnabledAccessOrNotFoundOnyxProps & {
/** The children to render */
children: ((props: FeatureEnabledAccessOrNotFoundOnyxProps) => React.ReactNode) | React.ReactNode;

/** The report currently being looked at */
policyID: string;

/** The current feature name that the user tries to get access */
featureName: PolicyFeatureName;
};

function FeatureEnabledAccessOrNotFoundComponent(props: FeatureEnabledAccessOrNotFoundComponentProps) {
const isPolicyIDInRoute = !!props.policyID?.length;
const shouldShowFullScreenLoadingIndicator = props.isLoadingReportData !== false && (!Object.entries(props.policy ?? {}).length || !props.policy?.id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we created a new Workspace and then enabled a new feature right after that (ie: Workflows), we called create workspace and enable feature consecutively, this condition won't be enough to address that case so It will introduce the bug: #39443.
More detail in this comment: #39443 (comment)

const shouldShowNotFoundPage = isEmptyObject(props.policy) || !props.policy?.id || !PolicyUtils.isPolicyFeatureEnabled(props.policy, props.featureName);

useEffect(() => {
if (!isPolicyIDInRoute || !isEmptyObject(props.policy)) {
// If the workspace is not required or is already loaded, we don't need to call the API
return;
}

Policy.openWorkspace(props.policyID, []);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isPolicyIDInRoute, props.policyID]);

if (shouldShowFullScreenLoadingIndicator) {
return <FullscreenLoadingIndicator />;
}

if (shouldShowNotFoundPage) {
return (
<FullPageNotFoundView
shouldShow={shouldShowNotFoundPage}
onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)}
shouldForceFullScreen
/>
);
}

return typeof props.children === 'function' ? props.children(props) : props.children;
}

export default withOnyx<FeatureEnabledAccessOrNotFoundComponentProps, FeatureEnabledAccessOrNotFoundOnyxProps>({
policy: {
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID ?? ''}`,
},
isLoadingReportData: {
key: ONYXKEYS.IS_LOADING_REPORT_DATA,
},
})(FeatureEnabledAccessOrNotFoundComponent);
97 changes: 52 additions & 45 deletions src/pages/workspace/categories/CategorySettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ import Navigation from '@libs/Navigation/Navigation';
import type {SettingsNavigatorParamList} from '@navigation/types';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper';
import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper';
import * as Policy from '@userActions/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
Expand Down Expand Up @@ -71,53 +73,58 @@ function CategorySettingsPage({route, policyCategories}: CategorySettingsPagePro
return (
<AdminPolicyAccessOrNotFoundWrapper policyID={route.params.policyID}>
<PaidPolicyAccessOrNotFoundWrapper policyID={route.params.policyID}>
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
style={[styles.defaultModalContainer]}
testID={CategorySettingsPage.displayName}
<FeatureEnabledAccessOrNotFoundWrapper
policyID={route.params.policyID}
featureName={CONST.POLICY.MORE_FEATURES.ARE_CATEGORIES_ENABLED}
>
<HeaderWithBackButton
shouldShowThreeDotsButton
title={route.params.categoryName}
threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)}
threeDotsMenuItems={threeDotsMenuItems}
/>
<ConfirmModal
isVisible={deleteCategoryConfirmModalVisible}
onConfirm={deleteCategory}
onCancel={() => setDeleteCategoryConfirmModalVisible(false)}
title={translate('workspace.categories.deleteCategory')}
prompt={translate('workspace.categories.deleteCategoryPrompt')}
confirmText={translate('common.delete')}
cancelText={translate('common.cancel')}
danger
/>
<View style={styles.flexGrow1}>
<OfflineWithFeedback
errors={ErrorUtils.getLatestErrorMessageField(policyCategory)}
pendingAction={policyCategory?.pendingFields?.enabled}
errorRowStyles={styles.mh5}
onClose={() => Policy.clearCategoryErrors(route.params.policyID, route.params.categoryName)}
>
<View style={[styles.mt2, styles.mh5]}>
<View style={[styles.flexRow, styles.mb5, styles.mr2, styles.alignItemsCenter, styles.justifyContentBetween]}>
<Text>{translate('workspace.categories.enableCategory')}</Text>
<Switch
isOn={policyCategory.enabled}
accessibilityLabel={translate('workspace.categories.enableCategory')}
onToggle={updateWorkspaceRequiresCategory}
/>
</View>
</View>
</OfflineWithFeedback>
<MenuItemWithTopDescription
title={policyCategory.name}
description={translate(`workspace.categories.categoryName`)}
onPress={navigateToEditCategory}
shouldShowRightIcon
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
style={[styles.defaultModalContainer]}
testID={CategorySettingsPage.displayName}
>
<HeaderWithBackButton
shouldShowThreeDotsButton
title={route.params.categoryName}
threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)}
threeDotsMenuItems={threeDotsMenuItems}
/>
<ConfirmModal
isVisible={deleteCategoryConfirmModalVisible}
onConfirm={deleteCategory}
onCancel={() => setDeleteCategoryConfirmModalVisible(false)}
title={translate('workspace.categories.deleteCategory')}
prompt={translate('workspace.categories.deleteCategoryPrompt')}
confirmText={translate('common.delete')}
cancelText={translate('common.cancel')}
danger
/>
</View>
</ScreenWrapper>
<View style={styles.flexGrow1}>
<OfflineWithFeedback
errors={ErrorUtils.getLatestErrorMessageField(policyCategory)}
pendingAction={policyCategory?.pendingFields?.enabled}
errorRowStyles={styles.mh5}
onClose={() => Policy.clearCategoryErrors(route.params.policyID, route.params.categoryName)}
>
<View style={[styles.mt2, styles.mh5]}>
<View style={[styles.flexRow, styles.mb5, styles.mr2, styles.alignItemsCenter, styles.justifyContentBetween]}>
<Text>{translate('workspace.categories.enableCategory')}</Text>
<Switch
isOn={policyCategory.enabled}
accessibilityLabel={translate('workspace.categories.enableCategory')}
onToggle={updateWorkspaceRequiresCategory}
/>
</View>
</View>
</OfflineWithFeedback>
<MenuItemWithTopDescription
title={policyCategory.name}
description={translate(`workspace.categories.categoryName`)}
onPress={navigateToEditCategory}
shouldShowRightIcon
/>
</View>
</ScreenWrapper>
</FeatureEnabledAccessOrNotFoundWrapper>
</PaidPolicyAccessOrNotFoundWrapper>
</AdminPolicyAccessOrNotFoundWrapper>
);
Expand Down
35 changes: 21 additions & 14 deletions src/pages/workspace/categories/CreateCategoryPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import type {SettingsNavigatorParamList} from '@navigation/types';
import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper';
import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper';
import * as Policy from '@userActions/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
import type {PolicyCategories} from '@src/types/onyx';
Expand All @@ -38,21 +40,26 @@ function CreateCategoryPage({route, policyCategories}: CreateCategoryPageProps)
return (
<AdminPolicyAccessOrNotFoundWrapper policyID={route.params.policyID}>
<PaidPolicyAccessOrNotFoundWrapper policyID={route.params.policyID}>
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
style={[styles.defaultModalContainer]}
testID={CreateCategoryPage.displayName}
shouldEnableMaxHeight
<FeatureEnabledAccessOrNotFoundWrapper
policyID={route.params.policyID}
featureName={CONST.POLICY.MORE_FEATURES.ARE_CATEGORIES_ENABLED}
>
<HeaderWithBackButton
title={translate('workspace.categories.addCategory')}
onBackButtonPress={Navigation.goBack}
/>
<CategoryForm
onSubmit={createCategory}
policyCategories={policyCategories}
/>
</ScreenWrapper>
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
style={[styles.defaultModalContainer]}
testID={CreateCategoryPage.displayName}
shouldEnableMaxHeight
>
<HeaderWithBackButton
title={translate('workspace.categories.addCategory')}
onBackButtonPress={Navigation.goBack}
/>
<CategoryForm
onSubmit={createCategory}
policyCategories={policyCategories}
/>
</ScreenWrapper>
</FeatureEnabledAccessOrNotFoundWrapper>
</PaidPolicyAccessOrNotFoundWrapper>
</AdminPolicyAccessOrNotFoundWrapper>
);
Expand Down
37 changes: 22 additions & 15 deletions src/pages/workspace/categories/EditCategoryPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import type {SettingsNavigatorParamList} from '@navigation/types';
import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper';
import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper';
import * as Policy from '@userActions/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
Expand Down Expand Up @@ -40,22 +42,27 @@ function EditCategoryPage({route, policyCategories}: EditCategoryPageProps) {
return (
<AdminPolicyAccessOrNotFoundWrapper policyID={route.params.policyID}>
<PaidPolicyAccessOrNotFoundWrapper policyID={route.params.policyID}>
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
style={[styles.defaultModalContainer]}
testID={EditCategoryPage.displayName}
shouldEnableMaxHeight
<FeatureEnabledAccessOrNotFoundWrapper
policyID={route.params.policyID}
featureName={CONST.POLICY.MORE_FEATURES.ARE_CATEGORIES_ENABLED}
>
<HeaderWithBackButton
title={translate('workspace.categories.editCategory')}
onBackButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_CATEGORY_SETTINGS.getRoute(route.params.policyID, route.params.categoryName))}
/>
<CategoryForm
onSubmit={editCategory}
categoryName={currentCategoryName}
policyCategories={policyCategories}
/>
</ScreenWrapper>
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
style={[styles.defaultModalContainer]}
testID={EditCategoryPage.displayName}
shouldEnableMaxHeight
>
<HeaderWithBackButton
title={translate('workspace.categories.editCategory')}
onBackButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_CATEGORY_SETTINGS.getRoute(route.params.policyID, route.params.categoryName))}
/>
<CategoryForm
onSubmit={editCategory}
categoryName={currentCategoryName}
policyCategories={policyCategories}
/>
</ScreenWrapper>
</FeatureEnabledAccessOrNotFoundWrapper>
</PaidPolicyAccessOrNotFoundWrapper>
</AdminPolicyAccessOrNotFoundWrapper>
);
Expand Down
Loading
Loading