diff --git a/src/ROUTES.ts b/src/ROUTES.ts index b98717b51f5dd..dcd343bb932a4 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1743,7 +1743,12 @@ const ROUTES = { }, POLICY_ACCOUNTING_XERO_ORGANIZATION: { route: 'settings/workspaces/:policyID/accounting/xero/organization/:currentOrganizationID', - getRoute: (policyID: string, currentOrganizationID: string) => `settings/workspaces/${policyID}/accounting/xero/organization/${currentOrganizationID}` as const, + getRoute: (policyID: string | undefined, currentOrganizationID: string | undefined) => { + if (!policyID || !currentOrganizationID) { + Log.warn('Invalid policyID is used to build the POLICY_ACCOUNTING_XERO_ORGANIZATION route'); + } + return `settings/workspaces/${policyID}/accounting/xero/organization/${currentOrganizationID}` as const; + }, }, POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES: { route: 'settings/workspaces/:policyID/accounting/xero/import/tracking-categories', @@ -1837,7 +1842,12 @@ const ROUTES = { MISSING_PERSONAL_DETAILS: 'missing-personal-details', POLICY_ACCOUNTING_NETSUITE_SUBSIDIARY_SELECTOR: { route: 'settings/workspaces/:policyID/accounting/netsuite/subsidiary-selector', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/subsidiary-selector` as const, + getRoute: (policyID: string | undefined) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the POLICY_ACCOUNTING_NETSUITE_SUBSIDIARY_SELECTOR route'); + } + return `settings/workspaces/${policyID}/accounting/netsuite/subsidiary-selector` as const; + }, }, POLICY_ACCOUNTING_NETSUITE_EXISTING_CONNECTIONS: { route: 'settings/workspaces/:policyID/accounting/netsuite/existing-connections', @@ -2043,7 +2053,12 @@ const ROUTES = { }, POLICY_ACCOUNTING_SAGE_INTACCT_ENTITY: { route: 'settings/workspaces/:policyID/accounting/sage-intacct/entity', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/entity` as const, + getRoute: (policyID: string | undefined) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the POLICY_ACCOUNTING_SAGE_INTACCT_ENTITY route'); + } + return `settings/workspaces/${policyID}/accounting/sage-intacct/entity` as const; + }, }, POLICY_ACCOUNTING_SAGE_INTACCT_IMPORT: { route: 'settings/workspaces/:policyID/accounting/sage-intacct/import', diff --git a/src/components/ThreeDotsMenu/types.ts b/src/components/ThreeDotsMenu/types.ts index 6b645d8e972cf..dc7f411f3af1b 100644 --- a/src/components/ThreeDotsMenu/types.ts +++ b/src/components/ThreeDotsMenu/types.ts @@ -1,4 +1,4 @@ -import type {StyleProp, ViewStyle} from 'react-native'; +import type {LayoutRectangle, NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native'; import type {PopoverMenuItem} from '@components/PopoverMenu'; import type {TranslationPaths} from '@src/languages/types'; import type {AnchorPosition} from '@src/styles'; @@ -49,4 +49,7 @@ type ThreeDotsMenuProps = { shouldShowProductTrainingTooltip?: boolean; }; +type LayoutChangeEventWithTarget = NativeSyntheticEvent<{layout: LayoutRectangle; target: HTMLElement}>; + +export type {LayoutChangeEventWithTarget}; export default ThreeDotsMenuProps; diff --git a/src/pages/Search/SavedSearchItemThreeDotMenu.tsx b/src/pages/Search/SavedSearchItemThreeDotMenu.tsx index 4d92ef295f2d1..2a0733e40c7e2 100644 --- a/src/pages/Search/SavedSearchItemThreeDotMenu.tsx +++ b/src/pages/Search/SavedSearchItemThreeDotMenu.tsx @@ -1,4 +1,5 @@ import React, {useRef, useState} from 'react'; +import type {LayoutChangeEvent, LayoutRectangle, NativeSyntheticEvent} from 'react-native'; import {View} from 'react-native'; import type {PopoverMenuItem} from '@components/PopoverMenu'; import ThreeDotsMenu from '@components/ThreeDotsMenu'; @@ -12,6 +13,9 @@ type SavedSearchItemThreeDotMenuProps = { renderTooltipContent: () => React.JSX.Element; shouldRenderTooltip: boolean; }; + +type LayoutChangeEventWithTarget = NativeSyntheticEvent<{layout: LayoutRectangle; target: HTMLElement}>; + function SavedSearchItemThreeDotMenu({menuItems, isDisabledItem, hideProductTrainingTooltip, renderTooltipContent, shouldRenderTooltip}: SavedSearchItemThreeDotMenuProps) { const threeDotsMenuContainerRef = useRef(null); const [threeDotsMenuPosition, setThreeDotsMenuPosition] = useState({horizontal: 0, vertical: 0}); @@ -20,17 +24,18 @@ function SavedSearchItemThreeDotMenu({menuItems, isDisabledItem, hideProductTrai { + const target = e.target || (e as LayoutChangeEventWithTarget).nativeEvent.target; + target?.measureInWindow((x, y, width) => { + setThreeDotsMenuPosition({ + horizontal: x + width, + vertical: y, + }); + }); + }} > { - threeDotsMenuContainerRef.current?.measureInWindow((x, y, width) => { - setThreeDotsMenuPosition({ - horizontal: x + width, - vertical: y, - }); - }); - }} anchorPosition={threeDotsMenuPosition} renderProductTrainingTooltipContent={renderTooltipContent} shouldShowProductTrainingTooltip={shouldRenderTooltip} diff --git a/src/pages/settings/Subscription/CardSection/CardSectionActions/index.tsx b/src/pages/settings/Subscription/CardSection/CardSectionActions/index.tsx index 663ea38f81d7c..99d20cf01b0d7 100644 --- a/src/pages/settings/Subscription/CardSection/CardSectionActions/index.tsx +++ b/src/pages/settings/Subscription/CardSection/CardSectionActions/index.tsx @@ -1,8 +1,10 @@ -import React, {useCallback, useMemo, useRef, useState} from 'react'; +import React, {useMemo, useRef, useState} from 'react'; +import type {LayoutChangeEvent} from 'react-native'; import {View} from 'react-native'; import * as Expensicons from '@components/Icon/Expensicons'; import ThreeDotsMenu from '@components/ThreeDotsMenu'; import type ThreeDotsMenuProps from '@components/ThreeDotsMenu/types'; +import type {LayoutChangeEventWithTarget} from '@components/ThreeDotsMenu/types'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import Navigation from '@navigation/Navigation'; @@ -37,22 +39,23 @@ function CardSectionActions() { [translate], ); - const calculateAndSetThreeDotsMenuPosition = useCallback(() => { - if (shouldUseNarrowLayout) { - return; - } - threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => { - setThreeDotsMenuPosition({ - horizontal: x + width, - vertical: y + height, - }); - }); - }, [shouldUseNarrowLayout]); - return ( - + { + if (shouldUseNarrowLayout) { + return; + } + const target = e.target || (e as LayoutChangeEventWithTarget).nativeEvent.target; + target?.measureInWindow((x, y, width) => { + setThreeDotsMenuPosition({ + horizontal: x + width, + vertical: y, + }); + }); + }} + > { - Subscription.requestTaxExempt(); - Report.navigateToConciergeChat(); + requestTaxExempt(); + navigateToConciergeChat(); }, }, ], [translate], ); - const calculateAndSetThreeDotsMenuPosition = useCallback(() => { - if (shouldUseNarrowLayout) { - return; - } - threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => { - setThreeDotsMenuPosition({ - horizontal: x + width, - vertical: y + height, - }); - }); - }, [shouldUseNarrowLayout]); - return ( { + if (shouldUseNarrowLayout) { + return; + } + const target = e.target || (e as LayoutChangeEventWithTarget).nativeEvent.target; + target?.measureInWindow((x, y, width) => { + setThreeDotsMenuPosition({ + horizontal: x + width, + vertical: y, + }); + }); + }} > - - { - if (shouldUseNarrowLayout) { - return; - } - threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => { - setThreeDotsMenuPosition({ - horizontal: x + width, - vertical: y + height, - }); + { + if (shouldUseNarrowLayout) { + return; + } + const target = e.target || (e as LayoutChangeEventWithTarget).nativeEvent.target; + target?.measureInWindow((x, y, width) => { + setThreeDotsMenuPosition({ + horizontal: x + width, + vertical: y, }); - }} + }); + }} + > + 1)) { return; } - Navigation.navigate(ROUTES.POLICY_ACCOUNTING_XERO_ORGANIZATION.getRoute(policyID, currentXeroOrganization?.id ?? '-1')); + Navigation.navigate(ROUTES.POLICY_ACCOUNTING_XERO_ORGANIZATION.getRoute(policyID, currentXeroOrganization?.id)); }, pendingAction: settingsPendingAction([CONST.XERO_CONFIG.TENANT_ID], policy?.connections?.xero?.config?.pendingFields), brickRoadIndicator: areSettingsInErrorFields([CONST.XERO_CONFIG.TENANT_ID], policy?.connections?.xero?.config?.errorFields) @@ -274,7 +276,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { }, [connectedIntegration, currentXeroOrganization?.id, policy, policyID, styles.fontWeightNormal, styles.sectionMenuItemTopDescription, tenants.length, translate]); const connectionsMenuItems: MenuItemData[] = useMemo(() => { - if (isEmptyObject(policy?.connections) && !isSyncInProgress) { + if (isEmptyObject(policy?.connections) && !isSyncInProgress && policyID) { return accountingIntegrations .map((integration) => { const integrationData = getAccountingIntegrationData(integration, policyID, translate); @@ -327,7 +329,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { .filter(Boolean) as MenuItemData[]; } - if (!connectedIntegration) { + if (!connectedIntegration || !policyID) { return []; } const shouldHideConfigurationOptions = isConnectionUnverified(policy, connectedIntegration); @@ -399,16 +401,19 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { color={theme.spinner} /> ) : ( - - { - threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => { - setThreeDotsMenuPosition({ - horizontal: x + width, - vertical: y + height, - }); + { + const target = e.target || (e as LayoutChangeEventWithTarget).nativeEvent.target; + target?.measureInWindow((x, y, width) => { + setThreeDotsMenuPosition({ + horizontal: x + width, + vertical: y, }); - }} + }); + }} + > + { - if (isEmptyObject(policy?.connections) && !isSyncInProgress) { + if ((isEmptyObject(policy?.connections) && !isSyncInProgress) || !policyID) { return; } const otherIntegrations = accountingIntegrations.filter( @@ -589,7 +594,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { /> ))} - {hasUnsupportedNDIntegration && hasSyncError && ( + {hasUnsupportedNDIntegration && hasSyncError && !!policyID && ( )} - {hasUnsupportedNDIntegration && !hasSyncError && ( + {hasUnsupportedNDIntegration && !hasSyncError && !!policyID && ( {translate('workspace.accounting.needAnotherAccounting')} - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(chatReportID ?? ''))}>{chatTextLink} + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(chatReportID))}>{chatTextLink} )} @@ -656,7 +661,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { title={translate('workspace.accounting.disconnectTitle', {connectionName: connectedIntegration})} isVisible={isDisconnectModalOpen} onConfirm={() => { - if (connectedIntegration) { + if (connectedIntegration && policyID) { removePolicyConnection(policyID, connectedIntegration); } setIsDisconnectModalOpen(false);