From a9907d067742f97b29cf4f81390cf10436fe9a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Tue, 25 Jun 2024 15:00:22 +0100 Subject: [PATCH 01/10] Restrict actions throughout the App --- src/components/SettlementButton.tsx | 7 ++++++ src/libs/actions/IOU.ts | 11 ++++++++++ .../AttachmentPickerWithMenuItems.tsx | 22 ++++++++++++++----- .../MoneyRequestParticipantsSelector.tsx | 19 +++++++++++++++- 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/components/SettlementButton.tsx b/src/components/SettlementButton.tsx index 2b1cd0729c0b..da98b2faf329 100644 --- a/src/components/SettlementButton.tsx +++ b/src/components/SettlementButton.tsx @@ -4,8 +4,10 @@ import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx, withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import playSound, {SOUNDS} from '@libs/Sound'; +import * as SubscriptionUtils from '@libs/SubscriptionUtils'; import * as BankAccounts from '@userActions/BankAccounts'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; @@ -228,6 +230,11 @@ function SettlementButton({ }, [currency, formattedAmount, iouReport, policyID, translate, shouldHidePaymentOptions, shouldShowApproveButton, shouldDisableApproveButton]); const selectPaymentType = (event: KYCFlowEvent, iouPaymentType: PaymentMethodType, triggerKYCFlow: TriggerKYCFlow) => { + if (policy && SubscriptionUtils.shouldRestrictUserBillableActions(policy.id)) { + Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policy.id)); + return; + } + if (iouPaymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY || iouPaymentType === CONST.IOU.PAYMENT_TYPE.VBBA) { triggerKYCFlow(event, iouPaymentType); BankAccounts.setPersonalBankAccountContinueKYCOnSuccess(ROUTES.ENABLE_PAYMENTS); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index fd95947c5153..8bcfa58552ea 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -44,6 +44,7 @@ import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import type {OptimisticChatReport, OptimisticCreatedReportAction, OptimisticIOUReportAction, TransactionDetails} from '@libs/ReportUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import * as SubscriptionUtils from '@libs/SubscriptionUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import type {IOUAction, IOUType} from '@src/CONST'; @@ -6205,6 +6206,11 @@ function hasIOUToApproveOrPay(chatReport: OnyxEntry | EmptyObj } function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject, full?: boolean) { + if (expenseReport.policyID && SubscriptionUtils.shouldRestrictUserBillableActions(expenseReport.policyID)) { + Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(expenseReport.policyID)); + return; + } + const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`] ?? null; let total = expenseReport.total ?? 0; const hasHeldExpenses = ReportUtils.hasHeldExpenses(expenseReport.reportID); @@ -6568,6 +6574,11 @@ function cancelPayment(expenseReport: OnyxTypes.Report, chatReport: OnyxTypes.Re } function payMoneyRequest(paymentType: PaymentMethodType, chatReport: OnyxTypes.Report, iouReport: OnyxTypes.Report, full = true) { + if (chatReport.policyID && SubscriptionUtils.shouldRestrictUserBillableActions(chatReport.policyID)) { + Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(chatReport.policyID)); + return; + } + const recipient = {accountID: iouReport.ownerAccountID}; const {params, optimisticData, successData, failureData} = getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentType, full); diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx index fd9a244f210b..d1e26681eeae 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx @@ -19,13 +19,16 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as Browser from '@libs/Browser'; import getIconForAction from '@libs/getIconForAction'; +import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; +import * as SubscriptionUtils from '@libs/SubscriptionUtils'; import * as IOU from '@userActions/IOU'; import * as Report from '@userActions/Report'; import * as Task from '@userActions/Task'; import type {IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; type MoneyRequestOptions = Record, PopoverMenuItem>; @@ -125,31 +128,40 @@ function AttachmentPickerWithMenuItems({ * Returns the list of IOU Options */ const moneyRequestOptions = useMemo(() => { + const selectOption = (onSelected: () => void) => { + if (policy && SubscriptionUtils.shouldRestrictUserBillableActions(policy.id)) { + Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policy.id)); + return; + } + + onSelected(); + }; + const options: MoneyRequestOptions = { [CONST.IOU.TYPE.SPLIT]: { icon: Expensicons.Transfer, text: translate('iou.splitExpense'), - onSelected: () => IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, report?.reportID ?? '-1'), + onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, report?.reportID ?? '-1')), }, [CONST.IOU.TYPE.SUBMIT]: { icon: getIconForAction(CONST.IOU.TYPE.REQUEST), text: translate('iou.submitExpense'), - onSelected: () => IOU.startMoneyRequest(CONST.IOU.TYPE.SUBMIT, report?.reportID ?? '-1'), + onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SUBMIT, report?.reportID ?? '-1')), }, [CONST.IOU.TYPE.PAY]: { icon: getIconForAction(CONST.IOU.TYPE.SEND), text: translate('iou.paySomeone', {name: ReportUtils.getPayeeName(report)}), - onSelected: () => IOU.startMoneyRequest(CONST.IOU.TYPE.PAY, report?.reportID ?? '-1'), + onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.PAY, report?.reportID ?? '-1')), }, [CONST.IOU.TYPE.TRACK]: { icon: getIconForAction(CONST.IOU.TYPE.TRACK), text: translate('iou.trackExpense'), - onSelected: () => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, report?.reportID ?? '-1'), + onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, report?.reportID ?? '-1')), }, [CONST.IOU.TYPE.INVOICE]: { icon: Expensicons.InvoiceGeneric, text: translate('workspace.invoices.sendInvoice'), - onSelected: () => IOU.startMoneyRequest(CONST.IOU.TYPE.INVOICE, report?.reportID ?? '-1'), + onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.INVOICE, report?.reportID ?? '-1')), }, }; diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index 46c0d10e08ac..8a095b1a0d63 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -19,13 +19,16 @@ import usePermissions from '@hooks/usePermissions'; import useScreenWrapperTranstionStatus from '@hooks/useScreenWrapperTransitionStatus'; import useThemeStyles from '@hooks/useThemeStyles'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import * as SubscriptionUtils from '@libs/SubscriptionUtils'; import * as Policy from '@userActions/Policy/Policy'; import * as Report from '@userActions/Report'; import type {IOUAction, IOURequestType, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type {Participant} from '@src/types/onyx/IOU'; type MoneyRequestParticipantsSelectorProps = { @@ -378,6 +381,20 @@ function MoneyRequestParticipantsSelector({participants = [], onFinish, onPartic onFinish, ]); + const onSelectRow = (option: Participant) => { + if (option.isPolicyExpenseChat && option.policyID && SubscriptionUtils.shouldRestrictUserBillableActions(option.policyID)) { + Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(option.policyID)); + return; + } + + if (isIOUSplit) { + addParticipantToSelection(option); + return; + } + + addSingleParticipant(option); + }; + return ( (isIOUSplit ? addParticipantToSelection(item) : addSingleParticipant(item))} + onSelectRow={onSelectRow} shouldDebounceRowSelect footerContent={footerContent} headerMessage={header} From e7f3c92feafed7af9168504a92bc0491a627a358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 26 Jun 2024 11:12:46 +0100 Subject: [PATCH 02/10] Fix billing banner pre-trial subtitle link text --- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 2a5b32be5038..fbbb17bdf3b9 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3286,7 +3286,7 @@ export default { preTrial: { title: 'Start a free trial', subtitle: 'To get started, ', - subtitleLink: 'complete your setup checklist here', + subtitleLink: 'complete your setup checklist here.', }, }, cardSection: { diff --git a/src/languages/es.ts b/src/languages/es.ts index da228096eaf1..165f5f82ad82 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3790,7 +3790,7 @@ export default { preTrial: { title: 'Iniciar una prueba gratuita', subtitle: 'Para empezar, ', - subtitleLink: 'completa la lista de configuración aquí', + subtitleLink: 'completa la lista de configuración aquí.', }, }, cardSection: { From 101925efa6a9f6195dde7e658b3afa31a6b7027a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 26 Jun 2024 17:56:14 +0100 Subject: [PATCH 03/10] Fix AttachmentPicker logic to dont include Invoice option to restrict --- .../AttachmentPickerWithMenuItems.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx index d1e26681eeae..dc8091b2f793 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx @@ -128,8 +128,8 @@ function AttachmentPickerWithMenuItems({ * Returns the list of IOU Options */ const moneyRequestOptions = useMemo(() => { - const selectOption = (onSelected: () => void) => { - if (policy && SubscriptionUtils.shouldRestrictUserBillableActions(policy.id)) { + const selectOption = (onSelected: () => void, shouldRestrictAction: boolean) => { + if (shouldRestrictAction && policy && SubscriptionUtils.shouldRestrictUserBillableActions(policy.id)) { Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policy.id)); return; } @@ -141,27 +141,27 @@ function AttachmentPickerWithMenuItems({ [CONST.IOU.TYPE.SPLIT]: { icon: Expensicons.Transfer, text: translate('iou.splitExpense'), - onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, report?.reportID ?? '-1')), + onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, report?.reportID ?? '-1'), true), }, [CONST.IOU.TYPE.SUBMIT]: { icon: getIconForAction(CONST.IOU.TYPE.REQUEST), text: translate('iou.submitExpense'), - onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SUBMIT, report?.reportID ?? '-1')), + onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SUBMIT, report?.reportID ?? '-1'), true), }, [CONST.IOU.TYPE.PAY]: { icon: getIconForAction(CONST.IOU.TYPE.SEND), text: translate('iou.paySomeone', {name: ReportUtils.getPayeeName(report)}), - onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.PAY, report?.reportID ?? '-1')), + onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.PAY, report?.reportID ?? '-1'), true), }, [CONST.IOU.TYPE.TRACK]: { icon: getIconForAction(CONST.IOU.TYPE.TRACK), text: translate('iou.trackExpense'), - onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, report?.reportID ?? '-1')), + onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, report?.reportID ?? '-1'), true), }, [CONST.IOU.TYPE.INVOICE]: { icon: Expensicons.InvoiceGeneric, text: translate('workspace.invoices.sendInvoice'), - onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.INVOICE, report?.reportID ?? '-1')), + onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.INVOICE, report?.reportID ?? '-1'), false), }, }; From 05d214f45a0332422737f9d347bb81fedc094b7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 26 Jun 2024 17:56:21 +0100 Subject: [PATCH 04/10] Fix shouldRestrictUserBillableActions() logic when user is past due but not the workspace owner --- src/libs/SubscriptionUtils.ts | 24 ++++++++++++++--- tests/unit/SubscriptionUtilsTest.ts | 42 ++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index 50dc4f99eec0..67ebfe0f047e 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -4,6 +4,15 @@ import Onyx from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {BillingGraceEndPeriod, Policy} from '@src/types/onyx'; +import * as PolicyUtils from './PolicyUtils'; + +let currentUserAccountID: number = -1; +Onyx.connect({ + key: ONYXKEYS.SESSION, + callback: (value) => { + currentUserAccountID = value?.accountID ?? -1; + }, +}); let firstDayFreeTrial: OnyxEntry; Onyx.connect({ @@ -106,6 +115,8 @@ function doesUserHavePaymentCardAdded(): boolean { function shouldRestrictUserBillableActions(policyID: string): boolean { const currentDate = new Date(); + const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; + // This logic will be executed if the user is a workspace's non-owner (normal user or admin). // We should restrict the workspace's non-owner actions if it's member of a workspace where the owner is // past due and is past its grace period end. @@ -114,10 +125,9 @@ function shouldRestrictUserBillableActions(policyID: string): boolean { if (userBillingGracePeriodEnd && isAfter(currentDate, fromUnixTime(userBillingGracePeriodEnd.value))) { // Extracts the owner account ID from the collection member key. - const ownerAccountID = entryKey.slice(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END.length); + const ownerAccountID = Number(entryKey.slice(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END.length)); - const ownerPolicy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; - if (String(ownerPolicy?.ownerAccountID ?? -1) === ownerAccountID) { + if (PolicyUtils.isPolicyOwner(policy, ownerAccountID)) { return true; } } @@ -125,7 +135,13 @@ function shouldRestrictUserBillableActions(policyID: string): boolean { // If it reached here it means that the user is actually the workspace's owner. // We should restrict the workspace's owner actions if it's past its grace period end date and it's owing some amount. - if (ownerBillingGraceEndPeriod && amountOwed !== undefined && amountOwed > 0 && isAfter(currentDate, fromUnixTime(ownerBillingGraceEndPeriod))) { + if ( + PolicyUtils.isPolicyOwner(policy, currentUserAccountID) && + ownerBillingGraceEndPeriod && + amountOwed !== undefined && + amountOwed > 0 && + isAfter(currentDate, fromUnixTime(ownerBillingGraceEndPeriod)) + ) { return true; } diff --git a/tests/unit/SubscriptionUtilsTest.ts b/tests/unit/SubscriptionUtilsTest.ts index 7767ae9f387b..05d236c21240 100644 --- a/tests/unit/SubscriptionUtilsTest.ts +++ b/tests/unit/SubscriptionUtilsTest.ts @@ -148,6 +148,7 @@ describe('SubscriptionUtils', () => { afterEach(async () => { await Onyx.clear(); await Onyx.multiSet({ + [ONYXKEYS.SESSION]: null, [ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END]: null, [ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END]: null, [ONYXKEYS.NVP_PRIVATE_AMOUNT_OWNED]: null, @@ -213,36 +214,71 @@ describe('SubscriptionUtils', () => { expect(SubscriptionUtils.shouldRestrictUserBillableActions(policyID)).toBeTruthy(); }); - it('should return false if the user is a workspace owner but is not past due billing', async () => { + it("should return false if the user is the workspace's owner but is not past due billing", async () => { + const accountID = 1; const policyID = '1001'; await Onyx.multiSet({ + [ONYXKEYS.SESSION]: {email: '', accountID}, [ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END]: getUnixTime(addDays(new Date(), 3)), // not past due + [`${ONYXKEYS.COLLECTION.POLICY}${policyID}` as const]: { + ...createRandomPolicy(Number(policyID)), + ownerAccountID: accountID, + }, }); expect(SubscriptionUtils.shouldRestrictUserBillableActions(policyID)).toBeFalsy(); }); - it("should return false if the user is a workspace owner but is past due billing but isn't owning any amount", async () => { + it("should return false if the user is the workspace's owner that is past due billing but isn't owning any amount", async () => { + const accountID = 1; const policyID = '1001'; await Onyx.multiSet({ + [ONYXKEYS.SESSION]: {email: '', accountID}, [ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END]: getUnixTime(subDays(new Date(), 3)), // past due [ONYXKEYS.NVP_PRIVATE_AMOUNT_OWNED]: 0, + [`${ONYXKEYS.COLLECTION.POLICY}${policyID}` as const]: { + ...createRandomPolicy(Number(policyID)), + ownerAccountID: accountID, + }, }); expect(SubscriptionUtils.shouldRestrictUserBillableActions(policyID)).toBeFalsy(); }); - it('should return true if the user is a workspace owner but is past due billing and is owning some amount', async () => { + it("should return true if the user is the workspace's owner that is past due billing and is owning some amount", async () => { + const accountID = 1; const policyID = '1001'; await Onyx.multiSet({ + [ONYXKEYS.SESSION]: {email: '', accountID}, [ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END]: getUnixTime(subDays(new Date(), 3)), // past due [ONYXKEYS.NVP_PRIVATE_AMOUNT_OWNED]: 8010, + [`${ONYXKEYS.COLLECTION.POLICY}${policyID}` as const]: { + ...createRandomPolicy(Number(policyID)), + ownerAccountID: accountID, + }, }); expect(SubscriptionUtils.shouldRestrictUserBillableActions(policyID)).toBeTruthy(); }); + + it("should return false if the user is past due billing but is not the workspace's owner", async () => { + const accountID = 1; + const policyID = '1001'; + + await Onyx.multiSet({ + [ONYXKEYS.SESSION]: {email: '', accountID}, + [ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END]: getUnixTime(subDays(new Date(), 3)), // past due + [ONYXKEYS.NVP_PRIVATE_AMOUNT_OWNED]: 8010, + [`${ONYXKEYS.COLLECTION.POLICY}${policyID}` as const]: { + ...createRandomPolicy(Number(policyID)), + ownerAccountID: 2, // not the user + }, + }); + + expect(SubscriptionUtils.shouldRestrictUserBillableActions(policyID)).toBeFalsy(); + }); }); }); From 3c13487c0559f2588ae289d8574548c8e305e7ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 26 Jun 2024 18:20:18 +0100 Subject: [PATCH 05/10] Improve Restricted Action screen navigation --- .../Workspace/WorkspaceAdminRestrictedAction.tsx | 4 ++-- .../Workspace/WorkspaceOwnerRestrictedAction.tsx | 3 ++- .../Workspace/WorkspaceUserRestrictedAction.tsx | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx b/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx index efc9c36de51a..63c5613ec8e7 100644 --- a/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx +++ b/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx @@ -28,7 +28,7 @@ function WorkspaceAdminRestrictedAction({policyID}: WorkspaceAdminRestrictedActi const openAdminsReport = useCallback(() => { const reportID = `${PolicyUtils.getPolicy(policyID)?.chatReportIDAdmins}` ?? '-1'; - Report.openReport(reportID); + Navigation.resetToHome(); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); }, [policyID]); @@ -39,7 +39,7 @@ function WorkspaceAdminRestrictedAction({policyID}: WorkspaceAdminRestrictedActi > { + Navigation.resetToHome(); Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION); }; @@ -30,7 +31,7 @@ function WorkspaceOwnerRestrictedAction() { > diff --git a/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx b/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx index 33c159446398..21c1996ae762 100644 --- a/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx +++ b/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx @@ -28,7 +28,7 @@ function WorkspaceUserRestrictedAction({policyID}: WorkspaceUserRestrictedAction const openPolicyExpenseReport = useCallback(() => { const reportID = ReportUtils.findPolicyExpenseChatByPolicyID(policyID)?.reportID ?? '-1'; - Report.openReport(reportID); + Navigation.resetToHome(); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); }, [policyID]); @@ -39,7 +39,7 @@ function WorkspaceUserRestrictedAction({policyID}: WorkspaceUserRestrictedAction > Date: Wed, 26 Jun 2024 18:26:42 +0100 Subject: [PATCH 06/10] Restrict actions on FAB quick actions --- .../FloatingActionButtonAndPopover.tsx | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index ff5cfa05b57b..9c10fc1de6e9 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -23,6 +23,7 @@ import Navigation from '@libs/Navigation/Navigation'; import type {CentralPaneName, NavigationPartialRoute, RootStackParamList} from '@libs/Navigation/types'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import * as SubscriptionUtils from '@libs/SubscriptionUtils'; import * as App from '@userActions/App'; import * as IOU from '@userActions/IOU'; import * as Policy from '@userActions/Policy/Policy'; @@ -228,41 +229,51 @@ function FloatingActionButtonAndPopover( }, [personalDetails, quickActionReport, quickAction?.action, quickActionAvatars]); const navigateToQuickAction = () => { + const selectOption = (onSelected: () => void, shouldRestrictAction: boolean) => { + if (shouldRestrictAction && quickActionReport?.policyID && SubscriptionUtils.shouldRestrictUserBillableActions(quickActionReport.policyID)) { + Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(quickActionReport.policyID)); + return; + } + + onSelected(); + }; + const isValidReport = !(isEmptyObject(quickActionReport) || ReportUtils.isArchivedRoom(quickActionReport)); const quickActionReportID = isValidReport ? quickActionReport?.reportID ?? '-1' : ReportUtils.generateReportID(); + switch (quickAction?.action) { case CONST.QUICK_ACTIONS.REQUEST_MANUAL: - IOU.startMoneyRequest(CONST.IOU.TYPE.SUBMIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true); + selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SUBMIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true), true); return; case CONST.QUICK_ACTIONS.REQUEST_SCAN: - IOU.startMoneyRequest(CONST.IOU.TYPE.SUBMIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.SCAN, true); + selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SUBMIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.SCAN, true), true); return; case CONST.QUICK_ACTIONS.REQUEST_DISTANCE: - IOU.startMoneyRequest(CONST.IOU.TYPE.SUBMIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.DISTANCE, true); + selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SUBMIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.DISTANCE, true), true); return; case CONST.QUICK_ACTIONS.SPLIT_MANUAL: - IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true); + selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true), true); return; case CONST.QUICK_ACTIONS.SPLIT_SCAN: - IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.SCAN, true); + selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.SCAN, true), true); return; case CONST.QUICK_ACTIONS.SPLIT_DISTANCE: - IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.DISTANCE, true); + selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.DISTANCE, true), true); return; case CONST.QUICK_ACTIONS.SEND_MONEY: - IOU.startMoneyRequest(CONST.IOU.TYPE.PAY, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true); + selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.PAY, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true), true); return; case CONST.QUICK_ACTIONS.ASSIGN_TASK: - Task.clearOutTaskInfoAndNavigate(isValidReport ? quickActionReportID : '', isValidReport ? quickActionReport : undefined, quickAction.targetAccountID ?? -1, true); + selectOption(() => Task.clearOutTaskInfoAndNavigate(isValidReport ? quickActionReportID : '', isValidReport ? quickActionReport : undefined, quickAction.targetAccountID ?? -1, true), false); break; case CONST.QUICK_ACTIONS.TRACK_MANUAL: - IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true); + selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true), true); break; case CONST.QUICK_ACTIONS.TRACK_SCAN: - IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.SCAN, true); + selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.SCAN, true), true); break; case CONST.QUICK_ACTIONS.TRACK_DISTANCE: - IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.DISTANCE, true); + selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.DISTANCE, true), true); break; default: } From ca58a723c067d7d6bd8f81158eb8b4c2b428a721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 26 Jun 2024 21:11:16 +0100 Subject: [PATCH 07/10] Fix lint and prettier --- src/libs/SubscriptionUtils.ts | 2 +- .../Workspace/WorkspaceAdminRestrictedAction.tsx | 1 - .../Workspace/WorkspaceUserRestrictedAction.tsx | 1 - .../sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx | 5 ++++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index 67ebfe0f047e..ed705397e5ec 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -6,7 +6,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {BillingGraceEndPeriod, Policy} from '@src/types/onyx'; import * as PolicyUtils from './PolicyUtils'; -let currentUserAccountID: number = -1; +let currentUserAccountID = -1; Onyx.connect({ key: ONYXKEYS.SESSION, callback: (value) => { diff --git a/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx b/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx index 63c5613ec8e7..a513351f8cb4 100644 --- a/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx +++ b/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx @@ -10,7 +10,6 @@ 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'; diff --git a/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx b/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx index 21c1996ae762..3db1304a2137 100644 --- a/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx +++ b/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx @@ -10,7 +10,6 @@ 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'; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 9c10fc1de6e9..6717cda9724e 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -264,7 +264,10 @@ function FloatingActionButtonAndPopover( selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.PAY, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true), true); return; case CONST.QUICK_ACTIONS.ASSIGN_TASK: - selectOption(() => Task.clearOutTaskInfoAndNavigate(isValidReport ? quickActionReportID : '', isValidReport ? quickActionReport : undefined, quickAction.targetAccountID ?? -1, true), false); + selectOption( + () => Task.clearOutTaskInfoAndNavigate(isValidReport ? quickActionReportID : '', isValidReport ? quickActionReport : undefined, quickAction.targetAccountID ?? -1, true), + false, + ); break; case CONST.QUICK_ACTIONS.TRACK_MANUAL: selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true), true); From acab5494a36a06f0f6785254396bb7250122b626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Fri, 28 Jun 2024 15:01:04 +0100 Subject: [PATCH 08/10] Address comments --- .../Workspace/WorkspaceUserRestrictedAction.tsx | 4 ++-- .../ReportActionCompose/AttachmentPickerWithMenuItems.tsx | 2 +- .../SidebarScreen/FloatingActionButtonAndPopover.tsx | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx b/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx index 3db1304a2137..86da3e327f98 100644 --- a/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx +++ b/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx @@ -27,7 +27,7 @@ function WorkspaceUserRestrictedAction({policyID}: WorkspaceUserRestrictedAction const openPolicyExpenseReport = useCallback(() => { const reportID = ReportUtils.findPolicyExpenseChatByPolicyID(policyID)?.reportID ?? '-1'; - Navigation.resetToHome(); + // Navigation.resetToHome(); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); }, [policyID]); @@ -38,7 +38,7 @@ function WorkspaceUserRestrictedAction({policyID}: WorkspaceUserRestrictedAction > selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.PAY, report?.reportID ?? '-1'), true), + onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.PAY, report?.reportID ?? '-1'), false), }, [CONST.IOU.TYPE.TRACK]: { icon: getIconForAction(CONST.IOU.TYPE.TRACK), diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 6717cda9724e..a870948b959a 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -261,7 +261,7 @@ function FloatingActionButtonAndPopover( selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.DISTANCE, true), true); return; case CONST.QUICK_ACTIONS.SEND_MONEY: - selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.PAY, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true), true); + selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.PAY, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true), false); return; case CONST.QUICK_ACTIONS.ASSIGN_TASK: selectOption( @@ -270,13 +270,13 @@ function FloatingActionButtonAndPopover( ); break; case CONST.QUICK_ACTIONS.TRACK_MANUAL: - selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true), true); + selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true), false); break; case CONST.QUICK_ACTIONS.TRACK_SCAN: - selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.SCAN, true), true); + selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.SCAN, true), false); break; case CONST.QUICK_ACTIONS.TRACK_DISTANCE: - selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.DISTANCE, true), true); + selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.DISTANCE, true), false); break; default: } From 45cffc0ec8abbab782b4716022ef6d13e2ae665b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Mon, 1 Jul 2024 11:41:36 +0100 Subject: [PATCH 09/10] Fix workspace restricted action navigations --- .../Workspace/WorkspaceAdminRestrictedAction.tsx | 4 ++-- .../Workspace/WorkspaceOwnerRestrictedAction.tsx | 4 ++-- .../Workspace/WorkspaceUserRestrictedAction.tsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx b/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx index a513351f8cb4..b8880f372809 100644 --- a/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx +++ b/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx @@ -27,7 +27,7 @@ function WorkspaceAdminRestrictedAction({policyID}: WorkspaceAdminRestrictedActi const openAdminsReport = useCallback(() => { const reportID = `${PolicyUtils.getPolicy(policyID)?.chatReportIDAdmins}` ?? '-1'; - Navigation.resetToHome(); + Navigation.closeRHPFlow(); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); }, [policyID]); @@ -38,7 +38,7 @@ function WorkspaceAdminRestrictedAction({policyID}: WorkspaceAdminRestrictedActi > { - Navigation.resetToHome(); + Navigation.closeRHPFlow(); Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION); }; @@ -31,7 +31,7 @@ function WorkspaceOwnerRestrictedAction() { > diff --git a/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx b/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx index 86da3e327f98..4d2aabd8774e 100644 --- a/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx +++ b/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx @@ -27,7 +27,7 @@ function WorkspaceUserRestrictedAction({policyID}: WorkspaceUserRestrictedAction const openPolicyExpenseReport = useCallback(() => { const reportID = ReportUtils.findPolicyExpenseChatByPolicyID(policyID)?.reportID ?? '-1'; - // Navigation.resetToHome(); + Navigation.closeRHPFlow(); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); }, [policyID]); From 43fbc5a1e830ee261e4a8c57e7cff9c5fe4c5b3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Mon, 1 Jul 2024 12:10:20 +0100 Subject: [PATCH 10/10] Restrict submit action --- src/libs/actions/IOU.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 03ab428eb818..645b547e7088 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -6357,6 +6357,11 @@ function approveMoneyRequest(expenseReport: OnyxEntry, full?: } function submitReport(expenseReport: OnyxTypes.Report) { + if (expenseReport.policyID && SubscriptionUtils.shouldRestrictUserBillableActions(expenseReport.policyID)) { + Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(expenseReport.policyID)); + return; + } + const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`] ?? null; const parentReport = getReportOrDraftReport(expenseReport.parentReportID); const policy = PolicyUtils.getPolicy(expenseReport.policyID);