From cc8f80ca07af2dd4e51ba30ce280c06eaf921652 Mon Sep 17 00:00:00 2001 From: truph01 Date: Thu, 5 Mar 2026 23:40:48 +0700 Subject: [PATCH 1/4] fix: update shouldRestrictUserBillableActions call in components --- src/components/MoneyReportHeader.tsx | 2 +- src/pages/AddUnreportedExpense.tsx | 3 ++- src/pages/NewReportWorkspaceSelectionPage.tsx | 3 ++- src/pages/ReportChangeWorkspacePage.tsx | 4 +++- src/pages/Search/EmptySearchView.tsx | 6 ++++-- .../request/MoneyRequestParticipantsSelector.tsx | 9 +++++++-- .../iou/request/step/IOURequestStepParticipants.tsx | 6 +++++- tests/unit/SubscriptionUtilsTest.ts | 13 ++++--------- 8 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 9c8d2907687bd..0f4c9d1ff040e 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -1718,7 +1718,7 @@ function MoneyReportHeader({ if (!moneyRequestReport?.reportID) { return; } - if (policy && shouldRestrictUserBillableActions(policy.id, userBillingGraceEndPeriods, ownerBillingGraceEndPeriod)) { + if (policy && shouldRestrictUserBillableActions(policy.id, userBillingGraceEndPeriods, undefined, ownerBillingGraceEndPeriod)) { Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policy.id)); return; } diff --git a/src/pages/AddUnreportedExpense.tsx b/src/pages/AddUnreportedExpense.tsx index 59050644db71b..59180a85d1b53 100644 --- a/src/pages/AddUnreportedExpense.tsx +++ b/src/pages/AddUnreportedExpense.tsx @@ -56,6 +56,7 @@ function AddUnreportedExpense({route}: AddUnreportedExpensePageType) { const [isLoadingUnreportedTransactions] = useOnyx(ONYXKEYS.IS_LOADING_UNREPORTED_TRANSACTIONS); const [cardList] = useOnyx(ONYXKEYS.CARD_LIST); const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails(); + const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const shouldShowUnreportedTransactionsSkeletons = isLoadingUnreportedTransactions && hasMoreUnreportedTransactionsResults && !isOffline; const getUnreportedTransactions = useCallback( @@ -279,7 +280,7 @@ function AddUnreportedExpense({route}: AddUnreportedExpensePageType) { { buttonText: translate('iou.createExpense'), buttonAction: () => { - if (report && report.policyID && shouldRestrictUserBillableActions(report.policyID)) { + if (report && report.policyID && shouldRestrictUserBillableActions(report.policyID, undefined, undefined, ownerBillingGraceEndPeriod)) { Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(report.policyID)); return; } diff --git a/src/pages/NewReportWorkspaceSelectionPage.tsx b/src/pages/NewReportWorkspaceSelectionPage.tsx index e260890e553d8..9d5e3162ab22a 100644 --- a/src/pages/NewReportWorkspaceSelectionPage.tsx +++ b/src/pages/NewReportWorkspaceSelectionPage.tsx @@ -70,6 +70,7 @@ function NewReportWorkspaceSelectionPage({route}: NewReportWorkspaceSelectionPag const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); const [hasDismissedEmptyReportsConfirmation] = useOnyx(ONYXKEYS.NVP_EMPTY_REPORTS_CONFIRMATION_DISMISSED); const [betas] = useOnyx(ONYXKEYS.BETAS); + const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const [policies, fetchStatus] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); @@ -180,7 +181,7 @@ function NewReportWorkspaceSelectionPage({route}: NewReportWorkspaceSelectionPag return; } - if (shouldRestrictUserBillableActions(policy.policyID)) { + if (shouldRestrictUserBillableActions(policy.policyID, undefined, undefined, ownerBillingGraceEndPeriod)) { Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policy.policyID)); return; } diff --git a/src/pages/ReportChangeWorkspacePage.tsx b/src/pages/ReportChangeWorkspacePage.tsx index 35c6dd3f3855f..d3e986dddb2ae 100644 --- a/src/pages/ReportChangeWorkspacePage.tsx +++ b/src/pages/ReportChangeWorkspacePage.tsx @@ -72,6 +72,7 @@ function ReportChangeWorkspacePage({report, route}: ReportChangeWorkspacePagePro const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT); const session = useSession(); const hasViolations = hasViolationsReportUtils(report?.reportID, transactionViolations, session?.accountID ?? CONST.DEFAULT_NUMBER_ID, session?.email ?? ''); + const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const selectPolicy = useCallback( (policyID?: string) => { @@ -79,7 +80,7 @@ function ReportChangeWorkspacePage({report, route}: ReportChangeWorkspacePagePro if (!policyID || !policy) { return; } - if (shouldRestrictUserBillableActions(policy.id)) { + if (shouldRestrictUserBillableActions(policy.id, undefined, undefined, ownerBillingGraceEndPeriod)) { Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policy.id)); return; } @@ -124,6 +125,7 @@ function ReportChangeWorkspacePage({report, route}: ReportChangeWorkspacePagePro }, [ policies, + ownerBillingGraceEndPeriod, route.params, reportID, report, diff --git a/src/pages/Search/EmptySearchView.tsx b/src/pages/Search/EmptySearchView.tsx index 428d44265cf3d..29566bb6cc973 100644 --- a/src/pages/Search/EmptySearchView.tsx +++ b/src/pages/Search/EmptySearchView.tsx @@ -148,6 +148,7 @@ function EmptySearchViewContent({ }); const hasViolations = hasViolationsReportUtils(undefined, transactionViolations, accountID ?? CONST.DEFAULT_NUMBER_ID, ''); + const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const [hasTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, { selector: hasTransactionsSelector, }); @@ -339,14 +340,15 @@ function EmptySearchViewContent({ if ( !workspaceIDForReportCreation || - (shouldRestrictUserBillableActions(workspaceIDForReportCreation) && groupPoliciesWithChatEnabled.length > 1) + (shouldRestrictUserBillableActions(workspaceIDForReportCreation, undefined, undefined, ownerBillingGraceEndPeriod) && + groupPoliciesWithChatEnabled.length > 1) ) { // If we couldn't guess the workspace to create the report, or a guessed workspace is past it's grace period and we have other workspaces to choose from Navigation.navigate(ROUTES.NEW_REPORT_WORKSPACE_SELECTION.getRoute()); return; } - if (!shouldRestrictUserBillableActions(workspaceIDForReportCreation)) { + if (!shouldRestrictUserBillableActions(workspaceIDForReportCreation, undefined, undefined, ownerBillingGraceEndPeriod)) { handleCreateReportClick(); } else { Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(workspaceIDForReportCreation)); diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index 345738bfb2976..7c6f313ab7f74 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -118,6 +118,7 @@ function MoneyRequestParticipantsSelector({ const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`]; + const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false}); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const currentUserLogin = currentUserPersonalDetails.login; @@ -513,7 +514,11 @@ function MoneyRequestParticipantsSelector({ const onSelectRow = useCallback( (option: Participant) => { - if (option.isPolicyExpenseChat && option.policyID && shouldRestrictUserBillableActions(option.policyID, userBillingGraceEndPeriodCollection)) { + if ( + option.isPolicyExpenseChat && + option.policyID && + shouldRestrictUserBillableActions(option.policyID, userBillingGraceEndPeriodCollection, undefined, ownerBillingGraceEndPeriod) + ) { Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(option.policyID)); return; } @@ -525,7 +530,7 @@ function MoneyRequestParticipantsSelector({ addSingleParticipant(option); }, - [isIOUSplit, addParticipantToSelection, addSingleParticipant, userBillingGraceEndPeriodCollection], + [isIOUSplit, addParticipantToSelection, addSingleParticipant, userBillingGraceEndPeriodCollection, ownerBillingGraceEndPeriod], ); const importContactsButtonComponent = useMemo(() => { diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.tsx b/src/pages/iou/request/step/IOURequestStepParticipants.tsx index d6843bade1463..7b8b0a5be863c 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.tsx +++ b/src/pages/iou/request/step/IOURequestStepParticipants.tsx @@ -113,11 +113,15 @@ function IOURequestStepParticipants({ const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); + const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const isActivePolicyRequest = - iouType === CONST.IOU.TYPE.CREATE && isPaidGroupPolicy(activePolicy) && activePolicy?.isPolicyExpenseChatEnabled && !shouldRestrictUserBillableActions(activePolicy.id); + iouType === CONST.IOU.TYPE.CREATE && + isPaidGroupPolicy(activePolicy) && + activePolicy?.isPolicyExpenseChatEnabled && + !shouldRestrictUserBillableActions(activePolicy.id, undefined, undefined, ownerBillingGraceEndPeriod); const isAndroidNative = getPlatform() === CONST.PLATFORM.ANDROID; const isMobileSafari = isMobileSafariBrowser(); diff --git a/tests/unit/SubscriptionUtilsTest.ts b/tests/unit/SubscriptionUtilsTest.ts index 1b5ac6108b5ce..3704526847e19 100644 --- a/tests/unit/SubscriptionUtilsTest.ts +++ b/tests/unit/SubscriptionUtilsTest.ts @@ -208,7 +208,6 @@ describe('SubscriptionUtils', () => { 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_OWED]: null, [ONYXKEYS.COLLECTION.POLICY]: null, }); @@ -287,14 +286,13 @@ describe('SubscriptionUtils', () => { 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(shouldRestrictUserBillableActions(policyID, undefined)).toBeFalsy(); + expect(shouldRestrictUserBillableActions(policyID, undefined, undefined, getUnixTime(addDays(new Date(), 3)))).toBeFalsy(); }); it("should return false if the user is the workspace's owner that is past due billing but isn't owning any amount", async () => { @@ -303,7 +301,6 @@ describe('SubscriptionUtils', () => { 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_OWED]: 0, [`${ONYXKEYS.COLLECTION.POLICY}${policyID}` as const]: { ...createRandomPolicy(Number(policyID)), @@ -311,7 +308,7 @@ describe('SubscriptionUtils', () => { }, }); - expect(shouldRestrictUserBillableActions(policyID, undefined)).toBeFalsy(); + expect(shouldRestrictUserBillableActions(policyID, undefined, undefined, getUnixTime(subDays(new Date(), 3)))).toBeFalsy(); }); it("should return true if the user is the workspace's owner that is past due billing and is owning some amount", async () => { @@ -320,7 +317,6 @@ describe('SubscriptionUtils', () => { 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_OWED]: 8010, [`${ONYXKEYS.COLLECTION.POLICY}${policyID}` as const]: { ...createRandomPolicy(Number(policyID)), @@ -328,7 +324,7 @@ describe('SubscriptionUtils', () => { }, }); - expect(shouldRestrictUserBillableActions(policyID, undefined)).toBeTruthy(); + expect(shouldRestrictUserBillableActions(policyID, undefined, undefined, getUnixTime(subDays(new Date(), 3)))).toBeTruthy(); }); it("should return false if the user is past due billing but is not the workspace's owner", async () => { @@ -337,7 +333,6 @@ describe('SubscriptionUtils', () => { 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_OWED]: 8010, [`${ONYXKEYS.COLLECTION.POLICY}${policyID}` as const]: { ...createRandomPolicy(Number(policyID)), @@ -345,7 +340,7 @@ describe('SubscriptionUtils', () => { }, }); - expect(shouldRestrictUserBillableActions(policyID, undefined)).toBeFalsy(); + expect(shouldRestrictUserBillableActions(policyID, undefined, undefined, getUnixTime(subDays(new Date(), 3)))).toBeFalsy(); }); it('should restrict when ownerBillingGraceEndPeriod is passed directly as 3rd param and is past due', async () => { From abcb9f2e22b55949046c0703d655418e7072fdb0 Mon Sep 17 00:00:00 2001 From: truph01 Date: Sat, 7 Mar 2026 00:11:20 +0700 Subject: [PATCH 2/4] fix: add more migrations --- .../FloatingCameraButton/BaseFloatingCameraButton.tsx | 3 ++- .../SearchMoneyRequestReportEmptyState.tsx | 7 ++++--- .../SearchPageHeader/SearchFiltersBarCreateButton.tsx | 5 +++-- src/components/SettlementButton/index.tsx | 3 ++- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/components/FloatingCameraButton/BaseFloatingCameraButton.tsx b/src/components/FloatingCameraButton/BaseFloatingCameraButton.tsx index 61aec9e812fb3..a4c5abcd8d05b 100644 --- a/src/components/FloatingCameraButton/BaseFloatingCameraButton.tsx +++ b/src/components/FloatingCameraButton/BaseFloatingCameraButton.tsx @@ -38,6 +38,7 @@ function BaseFloatingCameraButton({icon}: BaseFloatingCameraButtonProps) { const [session] = useOnyx(ONYXKEYS.SESSION, {selector: sessionSelector}); const [allTransactionDrafts] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT); const [userBillingGraceEndPeriods] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END); + const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const reportID = useMemo(() => generateReportID(), []); const policyChatForActivePolicySelector = useCallback( @@ -54,7 +55,7 @@ function BaseFloatingCameraButton({icon}: BaseFloatingCameraButtonProps) { const onPress = () => { interceptAnonymousUser(() => { - if (policyChatForActivePolicy?.policyID && shouldRestrictUserBillableActions(policyChatForActivePolicy.policyID, userBillingGraceEndPeriods)) { + if (policyChatForActivePolicy?.policyID && shouldRestrictUserBillableActions(policyChatForActivePolicy.policyID, userBillingGraceEndPeriods, undefined, ownerBillingGraceEndPeriod)) { Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policyChatForActivePolicy.policyID)); return; } diff --git a/src/components/MoneyRequestReportView/SearchMoneyRequestReportEmptyState.tsx b/src/components/MoneyRequestReportView/SearchMoneyRequestReportEmptyState.tsx index fbc406ba10737..77e9355a2339b 100644 --- a/src/components/MoneyRequestReportView/SearchMoneyRequestReportEmptyState.tsx +++ b/src/components/MoneyRequestReportView/SearchMoneyRequestReportEmptyState.tsx @@ -20,6 +20,7 @@ const minModalHeight = 380; function SearchMoneyRequestReportEmptyState({report, policy}: {report: OnyxTypes.Report; policy?: OnyxTypes.Policy}) { const [userBillingGraceEndPeriodCollection] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END); + const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`); const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -39,7 +40,7 @@ function SearchMoneyRequestReportEmptyState({report, policy}: {report: OnyxTypes if (!reportId) { return; } - if (policy && shouldRestrictUserBillableActions(policy.id, userBillingGraceEndPeriodCollection)) { + if (policy && shouldRestrictUserBillableActions(policy.id, userBillingGraceEndPeriodCollection, undefined, ownerBillingGraceEndPeriod)) { Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policy.id)); return; } @@ -54,7 +55,7 @@ function SearchMoneyRequestReportEmptyState({report, policy}: {report: OnyxTypes if (!reportId) { return; } - if (policy && shouldRestrictUserBillableActions(policy.id, userBillingGraceEndPeriodCollection)) { + if (policy && shouldRestrictUserBillableActions(policy.id, userBillingGraceEndPeriodCollection, undefined, ownerBillingGraceEndPeriod)) { Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policy.id)); return; } @@ -66,7 +67,7 @@ function SearchMoneyRequestReportEmptyState({report, policy}: {report: OnyxTypes text: translate('iou.addUnreportedExpense'), icon: icons.ReceiptPlus, onSelected: () => { - if (policy && shouldRestrictUserBillableActions(policy.id, userBillingGraceEndPeriodCollection)) { + if (policy && shouldRestrictUserBillableActions(policy.id, userBillingGraceEndPeriodCollection, undefined, ownerBillingGraceEndPeriod)) { Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policy.id)); return; } diff --git a/src/components/Search/SearchPageHeader/SearchFiltersBarCreateButton.tsx b/src/components/Search/SearchPageHeader/SearchFiltersBarCreateButton.tsx index e908cdd70fae0..40588bd92aed6 100644 --- a/src/components/Search/SearchPageHeader/SearchFiltersBarCreateButton.tsx +++ b/src/components/Search/SearchPageHeader/SearchFiltersBarCreateButton.tsx @@ -56,6 +56,7 @@ function SearchFiltersBarCreateButton() { const hasViolations = hasViolationsReportUtils(undefined, transactionViolations, session?.accountID ?? CONST.DEFAULT_NUMBER_ID, session?.email ?? ''); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`); + const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const {policyForMovingExpensesID, shouldSelectPolicy} = usePolicyForMovingExpenses(); const shouldNavigateToUpgradePath = !policyForMovingExpensesID && !shouldSelectPolicy; const {showConfirmModal} = useConfirmModal(); @@ -179,13 +180,13 @@ function SearchFiltersBarCreateButton() { const workspaceIDForReportCreation = defaultChatEnabledPolicyID; // No default or restricted with multiple workspaces → workspace selector - if (!workspaceIDForReportCreation || (shouldRestrictUserBillableActions(workspaceIDForReportCreation) && groupPoliciesWithChatEnabled.length > 1)) { + if (!workspaceIDForReportCreation || (shouldRestrictUserBillableActions(workspaceIDForReportCreation, undefined, undefined, ownerBillingGraceEndPeriod) && groupPoliciesWithChatEnabled.length > 1)) { Navigation.navigate(ROUTES.NEW_REPORT_WORKSPACE_SELECTION.getRoute()); return; } // Default workspace is not restricted → create report directly - if (!shouldRestrictUserBillableActions(workspaceIDForReportCreation)) { + if (!shouldRestrictUserBillableActions(workspaceIDForReportCreation, undefined, undefined, ownerBillingGraceEndPeriod)) { // Check if empty report confirmation should be shown if (shouldShowEmptyReportConfirmationForDefaultChatEnabledPolicy) { openCreateReportConfirmation(); diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx index 0c1cefb55061e..f028e84ab85e8 100644 --- a/src/components/SettlementButton/index.tsx +++ b/src/components/SettlementButton/index.tsx @@ -118,6 +118,7 @@ function SettlementButton({ const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID); const [betas] = useOnyx(ONYXKEYS.BETAS); const [userBillingGraceEndPeriods] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END); + const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const lastPaymentMethod = iouReport?.type ? getLastPolicyPaymentMethod(policyIDKey, personalPolicyID, lastPaymentMethods, iouReport?.type as keyof LastPaymentMethodType, isIOUReport(iouReport)) @@ -180,7 +181,7 @@ function SettlementButton({ return true; } - if (policy && shouldRestrictUserBillableActions(policy.id, userBillingGraceEndPeriods)) { + if (policy && shouldRestrictUserBillableActions(policy.id, userBillingGraceEndPeriods, undefined, ownerBillingGraceEndPeriod)) { Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policy.id)); return true; } From a31d789644f9b83d37726206cd838870afa74634 Mon Sep 17 00:00:00 2001 From: truph01 Date: Sat, 7 Mar 2026 00:13:10 +0700 Subject: [PATCH 3/4] fix: prettier --- .../FloatingCameraButton/BaseFloatingCameraButton.tsx | 5 ++++- .../Search/SearchPageHeader/SearchFiltersBarCreateButton.tsx | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/FloatingCameraButton/BaseFloatingCameraButton.tsx b/src/components/FloatingCameraButton/BaseFloatingCameraButton.tsx index a4c5abcd8d05b..1262679ced47b 100644 --- a/src/components/FloatingCameraButton/BaseFloatingCameraButton.tsx +++ b/src/components/FloatingCameraButton/BaseFloatingCameraButton.tsx @@ -55,7 +55,10 @@ function BaseFloatingCameraButton({icon}: BaseFloatingCameraButtonProps) { const onPress = () => { interceptAnonymousUser(() => { - if (policyChatForActivePolicy?.policyID && shouldRestrictUserBillableActions(policyChatForActivePolicy.policyID, userBillingGraceEndPeriods, undefined, ownerBillingGraceEndPeriod)) { + if ( + policyChatForActivePolicy?.policyID && + shouldRestrictUserBillableActions(policyChatForActivePolicy.policyID, userBillingGraceEndPeriods, undefined, ownerBillingGraceEndPeriod) + ) { Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policyChatForActivePolicy.policyID)); return; } diff --git a/src/components/Search/SearchPageHeader/SearchFiltersBarCreateButton.tsx b/src/components/Search/SearchPageHeader/SearchFiltersBarCreateButton.tsx index 40588bd92aed6..a5ada1499bb92 100644 --- a/src/components/Search/SearchPageHeader/SearchFiltersBarCreateButton.tsx +++ b/src/components/Search/SearchPageHeader/SearchFiltersBarCreateButton.tsx @@ -180,7 +180,10 @@ function SearchFiltersBarCreateButton() { const workspaceIDForReportCreation = defaultChatEnabledPolicyID; // No default or restricted with multiple workspaces → workspace selector - if (!workspaceIDForReportCreation || (shouldRestrictUserBillableActions(workspaceIDForReportCreation, undefined, undefined, ownerBillingGraceEndPeriod) && groupPoliciesWithChatEnabled.length > 1)) { + if ( + !workspaceIDForReportCreation || + (shouldRestrictUserBillableActions(workspaceIDForReportCreation, undefined, undefined, ownerBillingGraceEndPeriod) && groupPoliciesWithChatEnabled.length > 1) + ) { Navigation.navigate(ROUTES.NEW_REPORT_WORKSPACE_SELECTION.getRoute()); return; } From 86c98f5c79d1fd1dbc0423e5d62043bd9ec12c9f Mon Sep 17 00:00:00 2001 From: truph01 Date: Sat, 7 Mar 2026 00:43:50 +0700 Subject: [PATCH 4/4] fix: add test --- .../SearchFiltersBarCreateButtonTest.tsx | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/tests/ui/components/SearchFiltersBarCreateButtonTest.tsx b/tests/ui/components/SearchFiltersBarCreateButtonTest.tsx index d99ec6148a723..7ed52da48d542 100644 --- a/tests/ui/components/SearchFiltersBarCreateButtonTest.tsx +++ b/tests/ui/components/SearchFiltersBarCreateButtonTest.tsx @@ -1,4 +1,5 @@ import {act, fireEvent, render, screen} from '@testing-library/react-native'; +import {getUnixTime, subDays} from 'date-fns'; import React from 'react'; import Onyx from 'react-native-onyx'; import ComposeProviders from '@components/ComposeProviders'; @@ -261,4 +262,84 @@ describe('SearchFiltersBarCreateButton', () => { expect(screen.getByText(translateLocal('iou.trackDistance'))).toBeOnTheScreen(); expect(screen.getByText(translateLocal('report.newReport.createReport'))).toBeOnTheScreen(); }); + + it('should navigate to workspace selector when owner billing is restricted and multiple workspaces exist', async () => { + // Given the current user owns a workspace that is past due billing with an outstanding amount + const pastDueGracePeriod = getUnixTime(subDays(new Date(), 3)); + + mockUsePolicyForMovingExpenses.mockReturnValue({ + policyForMovingExpensesID: MOCK_POLICY_ID, + policyForMovingExpenses: MOCK_POLICY, + shouldSelectPolicy: false, + }); + + await act(async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${MOCK_POLICY_ID}`, { + ...MOCK_POLICY, + ownerAccountID: CURRENT_USER_ACCOUNT_ID, + }); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}policy-456`, { + ...MOCK_POLICY, + id: 'policy-456', + name: 'Second Workspace', + ownerAccountID: CURRENT_USER_ACCOUNT_ID, + }); + await Onyx.merge(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END, pastDueGracePeriod); + await Onyx.merge(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED, 8010); + await Onyx.merge(ONYXKEYS.NVP_ACTIVE_POLICY_ID, MOCK_POLICY_ID); + }); + await waitForBatchedUpdatesWithAct(); + + // When component is rendered and "Create report" is pressed + renderComponent(); + await waitForBatchedUpdatesWithAct(); + + const createButton = screen.getByText(translateLocal('common.create')); + fireEvent.press(createButton); + await waitForBatchedUpdatesWithAct(); + + const createReportItem = screen.getByText(translateLocal('report.newReport.createReport')); + fireEvent.press(createReportItem, createMockPressEvent(createReportItem)); + await waitForBatchedUpdatesWithAct(); + + // Then it navigates to workspace selection since there are multiple workspaces and the default is restricted + expect(mockNavigate).toHaveBeenCalledWith(ROUTES.NEW_REPORT_WORKSPACE_SELECTION.getRoute()); + }); + + it('should navigate to restricted action page when owner billing is restricted and only one workspace exists', async () => { + // Given the current user owns a single workspace that is past due billing with an outstanding amount + const pastDueGracePeriod = getUnixTime(subDays(new Date(), 3)); + + mockUsePolicyForMovingExpenses.mockReturnValue({ + policyForMovingExpensesID: MOCK_POLICY_ID, + policyForMovingExpenses: MOCK_POLICY, + shouldSelectPolicy: false, + }); + + await act(async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${MOCK_POLICY_ID}`, { + ...MOCK_POLICY, + ownerAccountID: CURRENT_USER_ACCOUNT_ID, + }); + await Onyx.merge(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END, pastDueGracePeriod); + await Onyx.merge(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED, 8010); + await Onyx.merge(ONYXKEYS.NVP_ACTIVE_POLICY_ID, MOCK_POLICY_ID); + }); + await waitForBatchedUpdatesWithAct(); + + // When component is rendered and "Create report" is pressed + renderComponent(); + await waitForBatchedUpdatesWithAct(); + + const createButton = screen.getByText(translateLocal('common.create')); + fireEvent.press(createButton); + await waitForBatchedUpdatesWithAct(); + + const createReportItem = screen.getByText(translateLocal('report.newReport.createReport')); + fireEvent.press(createReportItem, createMockPressEvent(createReportItem)); + await waitForBatchedUpdatesWithAct(); + + // Then it navigates to the restricted action page for the single restricted workspace + expect(mockNavigate).toHaveBeenCalledWith(ROUTES.RESTRICTED_ACTION.getRoute(MOCK_POLICY_ID)); + }); });