diff --git a/assets/images/simple-illustrations/simple-illustration__lockclosed_orange.svg b/assets/images/simple-illustrations/simple-illustration__lockclosed_orange.svg
new file mode 100644
index 000000000000..91af173f0357
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__lockclosed_orange.svg
@@ -0,0 +1,52 @@
+
+
\ No newline at end of file
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index c1fdd68951fa..5fd4d2e88645 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -908,6 +908,10 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/taxes',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/taxes` as const,
},
+ RESTRICTED_ACTION: {
+ route: 'restricted-action/workspace/:policyID',
+ getRoute: (policyID: string) => `restricted-action/workspace/${policyID}` as const,
+ },
} as const;
/**
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index f884cca94ef5..270799810fb6 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -141,6 +141,7 @@ const SCREENS = {
TRAVEL: 'Travel',
SEARCH_REPORT: 'SearchReport',
SETTINGS_CATEGORIES: 'SettingsCategories',
+ RESTRICTED_ACTION: 'RestrictedAction',
},
ONBOARDING_MODAL: {
ONBOARDING: 'Onboarding',
@@ -378,6 +379,7 @@ const SCREENS = {
KEYBOARD_SHORTCUTS: 'KeyboardShortcuts',
TRANSACTION_RECEIPT: 'TransactionReceipt',
FEATURE_TRAINING_ROOT: 'FeatureTraining_Root',
+ RESTRICTED_ACTION_ROOT: 'RestrictedAction_Root',
} as const;
type Screen = DeepValueOf;
diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts
index 3fe36239d631..e699badc43ec 100644
--- a/src/components/Icon/Illustrations.ts
+++ b/src/components/Icon/Illustrations.ts
@@ -61,6 +61,7 @@ import House from '@assets/images/simple-illustrations/simple-illustration__hous
import InvoiceBlue from '@assets/images/simple-illustrations/simple-illustration__invoice.svg';
import Lightbulb from '@assets/images/simple-illustrations/simple-illustration__lightbulb.svg';
import LockClosed from '@assets/images/simple-illustrations/simple-illustration__lockclosed.svg';
+import LockClosedOrange from '@assets/images/simple-illustrations/simple-illustration__lockclosed_orange.svg';
import LockOpen from '@assets/images/simple-illustrations/simple-illustration__lockopen.svg';
import Luggage from '@assets/images/simple-illustrations/simple-illustration__luggage.svg';
import Mailbox from '@assets/images/simple-illustrations/simple-illustration__mailbox.svg';
@@ -192,4 +193,5 @@ export {
SendMoney,
CheckmarkCircle,
CreditCardEyes,
+ LockClosedOrange,
};
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 99ed3265f02b..ae93e191beb7 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -2689,6 +2689,19 @@ export default {
errorDescriptionPartTwo: 'reach out to Concierge',
errorDescriptionPartThree: 'for help.',
},
+ restrictedAction: {
+ restricted: 'Restricted',
+ actionsAreCurrentlyRestricted: ({workspaceName}) => `Actions on the ${workspaceName} workspace are currently restricted`,
+ workspaceOwnerWillNeedToAddOrUpdatePaymentCard: ({workspaceOwnerName}) =>
+ `Workspace owner, ${workspaceOwnerName} will need to add or update the payment card on file to unlock new workspace activity.`,
+ youWillNeedToAddOrUpdatePaymentCard: "You'll need to add or update the payment card on file to unlock new workspace activity.",
+ addPaymentCardToUnlock: 'Add a payment card to unlock!',
+ addPaymentCardToContinueUsingWorkspace: 'Add a payment card to continue using this workspace',
+ pleaseReachOutToYourWorkspaceAdmin: 'Please reach out to your workspace admin for any questions.',
+ chatWithYourAdmin: 'Chat with your admin',
+ chatInAdmins: 'Chat in #admins',
+ addPaymentCard: 'Add payment card',
+ },
},
getAssistancePage: {
title: 'Get assistance',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 96346458af37..b6e9bac27fee 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -2727,6 +2727,19 @@ export default {
errorDescriptionPartTwo: 'contacta con el conserje',
errorDescriptionPartThree: 'por ayuda.',
},
+ restrictedAction: {
+ restricted: 'Restringido',
+ actionsAreCurrentlyRestricted: ({workspaceName}) => `Las acciones en el espacio de trabajo ${workspaceName} están actualmente restringidas`,
+ workspaceOwnerWillNeedToAddOrUpdatePaymentCard: ({workspaceOwnerName}) =>
+ `El propietario del espacio de trabajo, ${workspaceOwnerName} tendrá que añadir o actualizar la tarjeta de pago registrada para desbloquear nueva actividad en el espacio de trabajo.`,
+ youWillNeedToAddOrUpdatePaymentCard: 'Debes añadir o actualizar la tarjeta de pago registrada para desbloquear nueva actividad en el espacio de trabajo.',
+ addPaymentCardToUnlock: 'Añade una tarjeta para desbloquearlo!',
+ addPaymentCardToContinueUsingWorkspace: 'Añade una tarjeta de pago para seguir utilizando este espacio de trabajo',
+ pleaseReachOutToYourWorkspaceAdmin: 'Si tienes alguna pregunta, ponte en contacto con el administrador de su espacio de trabajo.',
+ chatWithYourAdmin: 'Chatea con tu administrador',
+ chatInAdmins: 'Chatea en #admins',
+ addPaymentCard: 'Agregar tarjeta de pago',
+ },
},
getAssistancePage: {
title: 'Obtener ayuda',
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
index 0577fdcfc5aa..a6efe5fa5aed 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
@@ -372,6 +372,10 @@ const SearchReportModalStackNavigator = createModalStackNavigator require('../../../../pages/home/ReportScreen').default as React.ComponentType,
});
+const RestrictedActionModalStackNavigator = createModalStackNavigator({
+ [SCREENS.RESTRICTED_ACTION_ROOT]: () => require('../../../../pages/RestrictedAction/Workspace/WorkspaceRestrictedActionPage').default as React.ComponentType,
+});
+
export {
AddPersonalBankAccountModalStackNavigator,
EditRequestStackNavigator,
@@ -400,4 +404,5 @@ export {
TaskModalStackNavigator,
WalletStatementStackNavigator,
SearchReportModalStackNavigator,
+ RestrictedActionModalStackNavigator,
};
diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx
index be23969417f7..e58a895b6024 100644
--- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx
+++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx
@@ -144,6 +144,10 @@ function RightModalNavigator({navigation}: RightModalNavigatorProps) {
name={SCREENS.RIGHT_MODAL.SEARCH_REPORT}
component={ModalStackNavigators.SearchReportModalStackNavigator}
/>
+
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 1b4288a9b3a9..a8751ca0ba84 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -740,6 +740,11 @@ const config: LinkingOptions['config'] = {
[SCREENS.SEARCH.REPORT_RHP]: ROUTES.SEARCH_REPORT.route,
},
},
+ [SCREENS.RIGHT_MODAL.RESTRICTED_ACTION]: {
+ screens: {
+ [SCREENS.RESTRICTED_ACTION_ROOT]: ROUTES.RESTRICTED_ACTION.route,
+ },
+ },
},
},
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index f90a91fe0f19..06cb8d805850 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -770,6 +770,7 @@ type RightModalNavigatorParamList = {
[SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: NavigatorScreenParams;
[SCREENS.RIGHT_MODAL.TRAVEL]: NavigatorScreenParams;
[SCREENS.RIGHT_MODAL.SEARCH_REPORT]: NavigatorScreenParams;
+ [SCREENS.RIGHT_MODAL.RESTRICTED_ACTION]: NavigatorScreenParams;
};
type TravelNavigatorParamList = {
@@ -943,6 +944,12 @@ type SearchReportParamList = {
};
};
+type RestrictedActionParamList = {
+ [SCREENS.RESTRICTED_ACTION_ROOT]: {
+ policyID: string;
+ };
+};
+
type RootStackParamList = PublicScreensParamList & AuthScreensParamList & LeftModalNavigatorParamList;
type BottomTabName = keyof BottomTabNavigatorParamList;
@@ -1007,4 +1014,5 @@ export type {
WalletStatementNavigatorParamList,
WelcomeVideoModalNavigatorParamList,
SearchReportParamList,
+ RestrictedActionParamList,
};
diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index af3f3b264d13..e8e640ba49eb 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -153,6 +153,12 @@ function isExpensifyTeam(email: string | undefined): boolean {
const isPolicyAdmin = (policy: OnyxInputOrEntry | EmptyObject, currentUserLogin?: string): boolean =>
(policy?.role ?? (currentUserLogin && policy?.employeeList?.[currentUserLogin]?.role)) === CONST.POLICY.ROLE.ADMIN;
+/**
+ * Checks if the current user is an user of the policy.
+ */
+const isPolicyUser = (policy: OnyxInputOrEntry | EmptyObject, currentUserLogin?: string): boolean =>
+ (policy?.role ?? (currentUserLogin && policy?.employeeList?.[currentUserLogin]?.role)) === CONST.POLICY.ROLE.USER;
+
/**
* Checks if the policy is a free group policy.
*/
@@ -533,6 +539,7 @@ export {
isPaidGroupPolicy,
isPendingDeletePolicy,
isPolicyAdmin,
+ isPolicyUser,
isPolicyEmployee,
isPolicyFeatureEnabled,
isPolicyOwner,
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index a7ead4e100fa..0202d9f269c4 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -6984,6 +6984,10 @@ function getChatUsedForOnboarding(): OnyxEntry {
return Object.values(allReports ?? {}).find(isChatUsedForOnboarding);
}
+function findPolicyExpenseChatByPolicyID(policyID: string): OnyxEntry {
+ return Object.values(allReports ?? {}).find((report) => isPolicyExpenseChat(report) && report?.policyID === policyID);
+}
+
export {
addDomainToShortMention,
areAllRequestsBeingSmartScanned,
@@ -7258,6 +7262,7 @@ export {
createDraftWorkspaceAndNavigateToConfirmationScreen,
isChatUsedForOnboarding,
getChatUsedForOnboarding,
+ findPolicyExpenseChatByPolicyID,
};
export type {
diff --git a/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx b/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx
new file mode 100644
index 000000000000..efc9c36de51a
--- /dev/null
+++ b/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx
@@ -0,0 +1,74 @@
+import React, {useCallback} from 'react';
+import {View} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
+import Button from '@components/Button';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import * as Illustrations from '@components/Icon/Illustrations';
+import ImageSVG from '@components/ImageSVG';
+import ScreenWrapper from '@components/ScreenWrapper';
+import ScrollView from '@components/ScrollView';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as Report from '@libs/actions/Report';
+import Navigation from '@libs/Navigation/Navigation';
+import * as PolicyUtils from '@libs/PolicyUtils';
+import variables from '@styles/variables';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+
+type WorkspaceAdminRestrictedActionProps = {
+ policyID: string;
+};
+
+function WorkspaceAdminRestrictedAction({policyID}: WorkspaceAdminRestrictedActionProps) {
+ const {translate} = useLocalize();
+ const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);
+ const styles = useThemeStyles();
+
+ const openAdminsReport = useCallback(() => {
+ const reportID = `${PolicyUtils.getPolicy(policyID)?.chatReportIDAdmins}` ?? '-1';
+ Report.openReport(reportID);
+ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID));
+ }, [policyID]);
+
+ return (
+
+
+
+
+
+
+ {translate('workspace.restrictedAction.actionsAreCurrentlyRestricted', {workspaceName: policy?.name})}
+
+
+ {translate('workspace.restrictedAction.workspaceOwnerWillNeedToAddOrUpdatePaymentCard', {workspaceOwnerName: policy?.owner})}
+
+
+
+
+
+ );
+}
+
+WorkspaceAdminRestrictedAction.displayName = 'WorkspaceAdminRestrictedAction';
+
+export default WorkspaceAdminRestrictedAction;
diff --git a/src/pages/RestrictedAction/Workspace/WorkspaceOwnerRestrictedAction.tsx b/src/pages/RestrictedAction/Workspace/WorkspaceOwnerRestrictedAction.tsx
new file mode 100644
index 000000000000..fd6d0fc717b8
--- /dev/null
+++ b/src/pages/RestrictedAction/Workspace/WorkspaceOwnerRestrictedAction.tsx
@@ -0,0 +1,66 @@
+import React from 'react';
+import {View} from 'react-native';
+import Badge from '@components/Badge';
+import Button from '@components/Button';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import Icon from '@components/Icon';
+import * as Expensicons from '@components/Icon/Expensicons';
+import * as Illustrations from '@components/Icon/Illustrations';
+import ScreenWrapper from '@components/ScreenWrapper';
+import ScrollView from '@components/ScrollView';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import Navigation from '@libs/Navigation/Navigation';
+import variables from '@styles/variables';
+import ROUTES from '@src/ROUTES';
+
+function WorkspaceOwnerRestrictedAction() {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+
+ const addPaymentCard = () => {
+ Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {translate('workspace.restrictedAction.addPaymentCardToContinueUsingWorkspace')}
+ {translate('workspace.restrictedAction.youWillNeedToAddOrUpdatePaymentCard')}
+
+
+
+
+ );
+}
+
+WorkspaceOwnerRestrictedAction.displayName = 'WorkspaceOwnerRestrictedAction';
+
+export default WorkspaceOwnerRestrictedAction;
diff --git a/src/pages/RestrictedAction/Workspace/WorkspaceRestrictedActionPage.tsx b/src/pages/RestrictedAction/Workspace/WorkspaceRestrictedActionPage.tsx
new file mode 100644
index 000000000000..42b1e64d8282
--- /dev/null
+++ b/src/pages/RestrictedAction/Workspace/WorkspaceRestrictedActionPage.tsx
@@ -0,0 +1,43 @@
+import type {StackScreenProps} from '@react-navigation/stack';
+import React from 'react';
+import {useOnyx} from 'react-native-onyx';
+import type {RestrictedActionParamList} from '@libs/Navigation/types';
+import * as PolicyUtils from '@libs/PolicyUtils';
+import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type SCREENS from '@src/SCREENS';
+import WorkspaceAdminRestrictedAction from './WorkspaceAdminRestrictedAction';
+import WorkspaceOwnerRestrictedAction from './WorkspaceOwnerRestrictedAction';
+import WorkspaceUserRestrictedAction from './WorkspaceUserRestrictedAction';
+
+type WorkspaceRestrictedActionPageProps = StackScreenProps;
+
+function WorkspaceRestrictedActionPage({
+ route: {
+ params: {policyID},
+ },
+}: WorkspaceRestrictedActionPageProps) {
+ const [session] = useOnyx(ONYXKEYS.SESSION);
+ const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);
+
+ // Workspace Owner
+ if (PolicyUtils.isPolicyOwner(policy, session?.accountID ?? -1)) {
+ return ;
+ }
+
+ // Workspace Admin
+ if (PolicyUtils.isPolicyAdmin(policy, session?.email)) {
+ return ;
+ }
+
+ // Workspace User
+ if (PolicyUtils.isPolicyUser(policy, session?.email)) {
+ return ;
+ }
+
+ return ;
+}
+
+WorkspaceRestrictedActionPage.displayName = 'WorkspaceRestrictedActionPage';
+
+export default WorkspaceRestrictedActionPage;
diff --git a/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx b/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx
new file mode 100644
index 000000000000..33c159446398
--- /dev/null
+++ b/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx
@@ -0,0 +1,74 @@
+import React, {useCallback} from 'react';
+import {View} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
+import Button from '@components/Button';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import * as Illustrations from '@components/Icon/Illustrations';
+import ImageSVG from '@components/ImageSVG';
+import ScreenWrapper from '@components/ScreenWrapper';
+import ScrollView from '@components/ScrollView';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as Report from '@libs/actions/Report';
+import Navigation from '@libs/Navigation/Navigation';
+import * as ReportUtils from '@libs/ReportUtils';
+import variables from '@styles/variables';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+
+type WorkspaceUserRestrictedActionProps = {
+ policyID: string;
+};
+
+function WorkspaceUserRestrictedAction({policyID}: WorkspaceUserRestrictedActionProps) {
+ const {translate} = useLocalize();
+ const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);
+ const styles = useThemeStyles();
+
+ const openPolicyExpenseReport = useCallback(() => {
+ const reportID = ReportUtils.findPolicyExpenseChatByPolicyID(policyID)?.reportID ?? '-1';
+ Report.openReport(reportID);
+ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID));
+ }, [policyID]);
+
+ return (
+
+
+
+
+
+
+ {translate('workspace.restrictedAction.actionsAreCurrentlyRestricted', {workspaceName: policy?.name})}
+
+
+ {translate('workspace.restrictedAction.pleaseReachOutToYourWorkspaceAdmin')}
+
+
+
+
+
+ );
+}
+
+WorkspaceUserRestrictedAction.displayName = 'WorkspaceUserRestrictedAction';
+
+export default WorkspaceUserRestrictedAction;
diff --git a/src/styles/variables.ts b/src/styles/variables.ts
index f0f23db986a5..ab26f94da63c 100644
--- a/src/styles/variables.ts
+++ b/src/styles/variables.ts
@@ -207,6 +207,7 @@ export default {
oldDotWireframeIconWidth: 263.38,
oldDotWireframeIconHeight: 143.28,
sectionIllustrationHeight: 220,
+ restrictedActionIllustrationHeight: 136,
photoUploadPopoverWidth: 335,
onboardingModalWidth: 500,
welcomeVideoDelay: 1000,