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})} + + +