Skip to content
Merged
4 changes: 4 additions & 0 deletions src/hooks/usePreferredPolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ type UsePreferredPolicyResult = {

/** The ID of the preferred policy */
preferredPolicyID: string | undefined;

/** Whether the user is restricted from creating policies */
isRestrictedPolicyCreation: boolean;
};

/**
Expand Down Expand Up @@ -36,6 +39,7 @@ function usePreferredPolicy(): UsePreferredPolicyResult {
return {
isRestrictedToPreferredPolicy: isRestrictionEnabled && hasValidPolicyID,
preferredPolicyID: restrictedPolicyID,
isRestrictedPolicyCreation: securityGroup?.enableRestrictedPolicyCreation === true,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ function usePreloadFullScreenNavigators() {
const navigation = useNavigation<PlatformStackNavigationProp<AuthScreensParamList>>();
const route = useRoute();
const state = navigation.getState();
const preloadedRoutes = useMemo(() => state.preloadedRoutes, [state]);
// The fallback is used to prevent crashing from the UI test
const preloadedRoutes = useMemo(() => state.preloadedRoutes ?? [], [state]);
const subscriptionPlan = useSubscriptionPlan();
const isAuthenticated = useIsAuthenticated();
const hasPreloadedRef = useRef(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import useLocalize from '@hooks/useLocalize';
import useOnboardingMessages from '@hooks/useOnboardingMessages';
import useOnyx from '@hooks/useOnyx';
import usePermissions from '@hooks/usePermissions';
import usePreferredPolicy from '@hooks/usePreferredPolicy';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import {navigateAfterOnboardingWithMicrotaskQueue} from '@libs/navigateAfterOnboarding';
Expand Down Expand Up @@ -39,6 +40,7 @@ function BaseOnboardingWorkspaceOptional({shouldUseNativeStyles}: BaseOnboarding
const [onboardingAdminsChatReportID] = useOnyx(ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID, {canBeMissing: true});
const [conciergeChatReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID, {canBeMissing: true});
const {onboardingMessages} = useOnboardingMessages();
const {isRestrictedPolicyCreation} = usePreferredPolicy();
// When we merge public email with work email, we now want to navigate to the
// concierge chat report of the new work email and not the last accessed report.
const mergedAccountConciergeReportID = !onboardingValues?.shouldRedirectToClassicAfterMerge && onboardingValues?.shouldValidate ? conciergeChatReportID : undefined;
Expand Down Expand Up @@ -150,17 +152,19 @@ function BaseOnboardingWorkspaceOptional({shouldUseNativeStyles}: BaseOnboarding
onPress={() => completeOnboarding()}
/>
</View>
<View>
<Button
success
large
text={translate('onboarding.workspace.createWorkspace')}
onPress={() => {
setOnboardingErrorMessage(null);
Navigation.navigate(ROUTES.ONBOARDING_WORKSPACE_CONFIRMATION.getRoute());
}}
/>
</View>
{!isRestrictedPolicyCreation && (
<View>
<Button
success
large
text={translate('onboarding.workspace.createWorkspace')}
onPress={() => {
setOnboardingErrorMessage(null);
Navigation.navigate(ROUTES.ONBOARDING_WORKSPACE_CONFIRMATION.getRoute());
}}
/>
</View>
)}
</View>
</ScreenWrapper>
);
Expand Down
5 changes: 3 additions & 2 deletions src/pages/home/sidebar/FloatingActionButtonAndPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, ref
const [allTransactionDrafts] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {canBeMissing: true});
const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`, {canBeMissing: true});
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true});
const {isRestrictedToPreferredPolicy} = usePreferredPolicy();
const {isRestrictedToPreferredPolicy, isRestrictedPolicyCreation} = usePreferredPolicy();
const policyChatForActivePolicy = useMemo(() => {
if (isEmptyObject(activePolicy) || !activePolicy?.isPolicyExpenseChatEnabled) {
return {} as OnyxTypes.Report;
Expand Down Expand Up @@ -182,7 +182,8 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, ref
}, [allPolicies]);
const shouldShowCreateReportOption = shouldRedirectToExpensifyClassic || groupPoliciesWithChatEnabled.length > 0;

const shouldShowNewWorkspaceButton = Object.values(allPolicies ?? {}).every((policy) => !shouldShowPolicy(policy as OnyxEntry<OnyxTypes.Policy>, !!isOffline, session?.email));
const shouldShowNewWorkspaceButton =
Object.values(allPolicies ?? {}).every((policy) => !shouldShowPolicy(policy as OnyxEntry<OnyxTypes.Policy>, !!isOffline, session?.email)) && !isRestrictedPolicyCreation;

const quickActionAvatars = useMemo(() => {
if (isValidReport) {
Expand Down
30 changes: 19 additions & 11 deletions src/pages/workspace/WorkspacesListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
const route = useRoute<PlatformStackRouteProp<AuthScreensParamList, typeof SCREENS.WORKSPACES_LIST>>();
const [fundList] = useOnyx(ONYXKEYS.FUND_LIST, {canBeMissing: true});
const [duplicateWorkspace] = useOnyx(ONYXKEYS.DUPLICATE_WORKSPACE, {canBeMissing: true});
const {isRestrictedToPreferredPolicy, preferredPolicyID} = usePreferredPolicy();
const {isRestrictedToPreferredPolicy, preferredPolicyID, isRestrictedPolicyCreation} = usePreferredPolicy();
const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
const [reimbursementAccountError] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {canBeMissing: true, selector: reimbursementAccountErrorSelector});

Expand Down Expand Up @@ -590,16 +590,20 @@
</>
);

const getHeaderButton = () =>
workspaces.length ? (
const getHeaderButton = () => {
if (isRestrictedPolicyCreation || workspaces.length === 0) {
return null;
}
return (
<Button
accessibilityLabel={translate('workspace.new.newWorkspace')}
text={translate('workspace.new.newWorkspace')}
onPress={() => interceptAnonymousUser(() => Navigation.navigate(ROUTES.WORKSPACE_CONFIRMATION.getRoute(ROUTES.WORKSPACES_LIST.route)))}
icon={Expensicons.Plus}
style={shouldUseNarrowLayout && [styles.flexGrow1, styles.mb3]}
/>
) : null;
);
};

const getWorkspacesEmptyStateComponent = useCallback(
() => (
Expand All @@ -613,16 +617,20 @@
headerStyles={[styles.overflowHidden, StyleUtils.getBackgroundColorStyle(colors.pink800), StyleUtils.getHeight(variables.sectionIllustrationHeight)]}
lottieWebViewStyles={styles.emptyWorkspaceListIllustrationStyle}
headerContentStyles={styles.emptyWorkspaceListIllustrationStyle}
buttons={[
{
success: true,
buttonAction: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.WORKSPACE_CONFIRMATION.getRoute(ROUTES.WORKSPACES_LIST.route))),
buttonText: translate('workspace.new.newWorkspace'),
},
]}
buttons={
isRestrictedPolicyCreation
? []
: [
{
success: true,
buttonAction: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.WORKSPACE_CONFIRMATION.getRoute(ROUTES.WORKSPACES_LIST.route))),
buttonText: translate('workspace.new.newWorkspace'),
},
]
}
/>
),
[StyleUtils, styles, translate],

Check warning on line 633 in src/pages/workspace/WorkspacesListPage.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

React Hook useCallback has a missing dependency: 'isRestrictedPolicyCreation'. Either include it or remove the dependency array
);

const onBackButtonPress = () => {
Expand Down
128 changes: 128 additions & 0 deletions tests/ui/WorkspaceListPageTest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import {PortalProvider} from '@gorhom/portal';
import {NavigationContainer} from '@react-navigation/native';
import {render, screen} from '@testing-library/react-native';
import Onyx from 'react-native-onyx';
import ComposeProviders from '@components/ComposeProviders';
import {LocaleContextProvider} from '@components/LocaleContextProvider';
import OnyxListItemProvider from '@components/OnyxListItemProvider';
import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator';
import type {AuthScreensParamList} from '@libs/Navigation/types';
import WorkspacesListPage from '@pages/workspace/WorkspacesListPage';
import ONYXKEYS from '@src/ONYXKEYS';
import SCREENS from '@src/SCREENS';
import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct';

const Stack = createPlatformStackNavigator<AuthScreensParamList>();

const renderPage = (initialRouteName: typeof SCREENS.WORKSPACES_LIST = SCREENS.WORKSPACES_LIST) => {
return render(
<ComposeProviders components={[OnyxListItemProvider, LocaleContextProvider]}>
<PortalProvider>
<NavigationContainer>
<Stack.Navigator initialRouteName={initialRouteName}>
<Stack.Screen
name={SCREENS.WORKSPACES_LIST}
component={WorkspacesListPage}
/>
</Stack.Navigator>
</NavigationContainer>
</PortalProvider>
</ComposeProviders>,
);
};

describe('WorkspaceListPage', () => {
beforeAll(() => {
Onyx.init({
keys: ONYXKEYS,
});
});

afterAll(() => {
Onyx.clear();
});

it('should not show new workspace button when the restrict creation policy in the group domain is enabled', async () => {
const TEST_DOMAIN = 'domain.com';
const TEST_SECURITY_GROUP_ID = 'test-id';
const TEST_POLICY_ID = 'test-policy-id';
const TEST_EMAIL = 'test@domain.com';
const TEST_ACCOUNT_ID = 1;

await Onyx.set(ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS, {
[TEST_DOMAIN]: TEST_SECURITY_GROUP_ID,
});

await Onyx.set(`${ONYXKEYS.COLLECTION.SECURITY_GROUP}${TEST_SECURITY_GROUP_ID}`, {
enableRestrictedPolicyCreation: true,
});

await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${TEST_POLICY_ID}`, {
id: TEST_POLICY_ID,
name: 'Test Policy',
role: 'admin',
});

await Onyx.set(`${ONYXKEYS.SESSION}`, {
email: TEST_EMAIL,
accountID: TEST_ACCOUNT_ID,
});

await Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, {
[TEST_ACCOUNT_ID]: {
login: TEST_EMAIL,
accountID: TEST_ACCOUNT_ID,
displayName: TEST_EMAIL,
},
});

renderPage();

await waitForBatchedUpdatesWithAct();

const newWorkspaceButton = screen.queryByAccessibilityHint('New workspace');
expect(newWorkspaceButton).not.toBeOnTheScreen();
});

it('should show new workspace button when the restrict creation policy in the group domain is disabled', async () => {
const TEST_DOMAIN = 'domain.com';
const TEST_SECURITY_GROUP_ID = 'test-id';
const TEST_POLICY_ID = 'test-policy-id';
const TEST_EMAIL = 'test@domain.com';
const TEST_ACCOUNT_ID = 1;

await Onyx.set(ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS, {
[TEST_DOMAIN]: TEST_SECURITY_GROUP_ID,
});

await Onyx.set(`${ONYXKEYS.COLLECTION.SECURITY_GROUP}${TEST_SECURITY_GROUP_ID}`, {
enableRestrictedPolicyCreation: false,
});

await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${TEST_POLICY_ID}`, {
id: TEST_POLICY_ID,
name: 'Test Policy',
role: 'admin',
});

await Onyx.set(`${ONYXKEYS.SESSION}`, {
email: TEST_EMAIL,
accountID: TEST_ACCOUNT_ID,
});

await Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, {
[TEST_ACCOUNT_ID]: {
login: TEST_EMAIL,
accountID: TEST_ACCOUNT_ID,
displayName: TEST_EMAIL,
},
});

renderPage();

await waitForBatchedUpdatesWithAct();

const newWorkspaceButton = screen.queryByAccessibilityHint('New workspace');
expect(newWorkspaceButton).toBeOnTheScreen();
});
});
Loading