From e6bf33fe233eca84b4b99ba1039b073b91c409f7 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 25 Feb 2025 15:32:25 +0100 Subject: [PATCH 1/9] fix: change the condition for displaying Expensify Cards --- src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx b/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx index 968c22283c7b..7e1ec82dee9e 100644 --- a/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx @@ -54,6 +54,7 @@ function WorkspaceMemberNewCardPage({route, personalDetails}: WorkspaceMemberNew const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`); const [selectedFeed, setSelectedFeed] = useState(''); const [shouldShowError, setShouldShowError] = useState(false); + const [expensifyCardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`); const accountID = Number(route.params.accountID); const memberLogin = personalDetails?.[accountID]?.login ?? ''; @@ -133,7 +134,7 @@ function WorkspaceMemberNewCardPage({route, personalDetails}: WorkspaceMemberNew })); const feeds = - workspaceAccountID && policy?.areExpensifyCardsEnabled + policy?.areExpensifyCardsEnabled && expensifyCardSettings?.paymentBankAccountID ? [ ...companyCardFeeds, { From 4dde402c7b8725c4846dfb10ac154f4b59080d7e Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 25 Feb 2025 15:36:14 +0100 Subject: [PATCH 2/9] fix: create an util function --- src/libs/CardUtils.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index eaac4bbd4491..91f21e3b2dae 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -9,7 +9,7 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type {OnyxValues} from '@src/ONYXKEYS'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {BankAccountList, Card, CardFeeds, CardList, CompanyCardFeed, PersonalDetailsList, WorkspaceCardsList} from '@src/types/onyx'; +import type {BankAccountList, Card, CardFeeds, CardList, CompanyCardFeed, ExpensifyCardSettings, PersonalDetailsList, Policy, WorkspaceCardsList} from '@src/types/onyx'; import type {FilteredCardList} from '@src/types/onyx/Card'; import type {CompanyCardFeedWithNumber, CompanyCardNicknames, CompanyFeeds, DirectCardFeedData} from '@src/types/onyx/CardFeeds'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -521,6 +521,17 @@ function hasCardListObject(workspaceAccountID: number, feedName: CompanyCardFeed return !!workspaceCards.cardList; } +/** + * Check if the Expensify Card is fully setup and a new card can be issued + * + * @param policy the policy object + * @param cardSettings the card settings object + * @returns boolean + */ +function isExpensifyCardFullySetup(policy?: OnyxEntry, cardSettings?: OnyxEntry): boolean { + return !!(policy?.areExpensifyCardsEnabled && cardSettings?.paymentBankAccountID); +} + export { isExpensifyCard, isCorporateCard, @@ -560,4 +571,5 @@ export { flatAllCardsList, checkIfFeedConnectionIsBroken, hasCardListObject, + isExpensifyCardFullySetup, }; From 37877ee4024bb77cb94b59ad6dceaf40adbb3e07 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 25 Feb 2025 15:44:06 +0100 Subject: [PATCH 3/9] chore: replace the condition with the util function --- .../accounting/PolicyAccountingPage.tsx | 15 +++++-- .../reconciliation/CardReconciliationPage.tsx | 3 +- .../members/WorkspaceMemberNewCardPage.tsx | 44 ++++++++++--------- 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx index 3303ff79219b..e9dfb20ad8c3 100644 --- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx +++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx @@ -34,6 +34,7 @@ import {getRouteParamForConnection} from '@libs/AccountingUtils'; import {isAuthenticationError, isConnectionInProgress, isConnectionUnverified, removePolicyConnection, syncConnection} from '@libs/actions/connections'; import {getAssignedSupportData} from '@libs/actions/Policy/Policy'; import {getConciergeReportID} from '@libs/actions/Report'; +import {isExpensifyCardFullySetup} from '@libs/CardUtils'; import { areSettingsInErrorFields, findCurrentXeroOrganization, @@ -118,7 +119,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { const currentXeroOrganization = findCurrentXeroOrganization(tenants, policy?.connections?.xero?.config?.tenantID); const shouldShowSynchronizationError = !!synchronizationError; const shouldShowReinstallConnectorMenuItem = shouldShowSynchronizationError && connectedIntegration === CONST.POLICY.CONNECTIONS.NAME.QBD; - const shouldShowCardReconciliationOption = policy?.areExpensifyCardsEnabled && cardSettings?.paymentBankAccountID; + const shouldShowCardReconciliationOption = isExpensifyCardFullySetup(policy, cardSettings); const overflowMenu: ThreeDotsMenuProps['menuItems'] = useMemo( () => [ @@ -292,7 +293,12 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { return; } - const iconProps = integrationData?.icon ? {icon: integrationData.icon, iconType: CONST.ICON_TYPE_AVATAR} : {}; + const iconProps = integrationData?.icon + ? { + icon: integrationData.icon, + iconType: CONST.ICON_TYPE_AVATAR, + } + : {}; return { ...iconProps, @@ -416,7 +422,10 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { ), diff --git a/src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx b/src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx index 19429e147426..79f16b4e4e0a 100644 --- a/src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx +++ b/src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx @@ -9,6 +9,7 @@ import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as AccountingUtils from '@libs/AccountingUtils'; +import {isExpensifyCardFullySetup} from '@libs/CardUtils'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import Navigation from '@navigation/Navigation'; import type {SettingsNavigatorParamList} from '@navigation/types'; @@ -43,7 +44,7 @@ function CardReconciliationPage({policy, route}: CardReconciliationPageProps) { const {connection} = route.params; const connectionName = AccountingUtils.getConnectionNameFromRouteParam(connection) as ConnectionName; const autoSync = !!policy?.connections?.[connectionName]?.config?.autoSync?.enabled; - const shouldShow = policy?.areExpensifyCardsEnabled && cardSettings?.paymentBankAccountID; + const shouldShow = isExpensifyCardFullySetup(policy, cardSettings); const toggleContinuousReconciliation = (value: boolean) => { Card.toggleContinuousReconciliation(workspaceAccountID, value, connectionName, currentConnectionName); diff --git a/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx b/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx index 7e1ec82dee9e..500051a958eb 100644 --- a/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx @@ -18,6 +18,7 @@ import { hasCardListObject, hasOnlyOneCardToAssign, isCustomFeed, + isExpensifyCardFullySetup, isSelectedFeedExpired, } from '@libs/CardUtils'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; @@ -54,7 +55,7 @@ function WorkspaceMemberNewCardPage({route, personalDetails}: WorkspaceMemberNew const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`); const [selectedFeed, setSelectedFeed] = useState(''); const [shouldShowError, setShouldShowError] = useState(false); - const [expensifyCardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`); + const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`); const accountID = Number(route.params.accountID); const memberLogin = personalDetails?.[accountID]?.login ?? ''; @@ -65,6 +66,8 @@ function WorkspaceMemberNewCardPage({route, personalDetails}: WorkspaceMemberNew const [list] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${selectedFeed}`); const filteredCardList = getFilteredCardList(list, cardFeeds?.settings?.oAuthAccountDetails?.[selectedFeed as CompanyCardFeed]); + const shouldShowExpensifyCard = isExpensifyCardFullySetup(policy, cardSettings); + const handleSubmit = () => { if (!selectedFeed) { setShouldShowError(true); @@ -133,26 +136,25 @@ function WorkspaceMemberNewCardPage({route, personalDetails}: WorkspaceMemberNew ), })); - const feeds = - policy?.areExpensifyCardsEnabled && expensifyCardSettings?.paymentBankAccountID - ? [ - ...companyCardFeeds, - { - value: CONST.EXPENSIFY_CARD.NAME, - text: translate('workspace.common.expensifyCard'), - keyForList: CONST.EXPENSIFY_CARD.NAME, - isSelected: selectedFeed === CONST.EXPENSIFY_CARD.NAME, - leftElement: ( - - ), - }, - ] - : companyCardFeeds; + const feeds = shouldShowExpensifyCard + ? [ + ...companyCardFeeds, + { + value: CONST.EXPENSIFY_CARD.NAME, + text: translate('workspace.common.expensifyCard'), + keyForList: CONST.EXPENSIFY_CARD.NAME, + isSelected: selectedFeed === CONST.EXPENSIFY_CARD.NAME, + leftElement: ( + + ), + }, + ] + : companyCardFeeds; const goBack = () => Navigation.goBack(); From 0503b24b05d13fb553b8b9c0234168c7f12a13b9 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 25 Feb 2025 15:45:03 +0100 Subject: [PATCH 4/9] fix: minor naming improvement --- src/libs/CardUtils.ts | 6 +++--- src/pages/workspace/accounting/PolicyAccountingPage.tsx | 4 ++-- .../accounting/reconciliation/CardReconciliationPage.tsx | 4 ++-- src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 91f21e3b2dae..0f0c3909e4fc 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -522,13 +522,13 @@ function hasCardListObject(workspaceAccountID: number, feedName: CompanyCardFeed } /** - * Check if the Expensify Card is fully setup and a new card can be issued + * Check if the Expensify Card is fully set up and a new card can be issued * * @param policy the policy object * @param cardSettings the card settings object * @returns boolean */ -function isExpensifyCardFullySetup(policy?: OnyxEntry, cardSettings?: OnyxEntry): boolean { +function isExpensifyCardFullySetUp(policy?: OnyxEntry, cardSettings?: OnyxEntry): boolean { return !!(policy?.areExpensifyCardsEnabled && cardSettings?.paymentBankAccountID); } @@ -571,5 +571,5 @@ export { flatAllCardsList, checkIfFeedConnectionIsBroken, hasCardListObject, - isExpensifyCardFullySetup, + isExpensifyCardFullySetUp, }; diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx index e9dfb20ad8c3..fcfc16ad4ede 100644 --- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx +++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx @@ -34,7 +34,7 @@ import {getRouteParamForConnection} from '@libs/AccountingUtils'; import {isAuthenticationError, isConnectionInProgress, isConnectionUnverified, removePolicyConnection, syncConnection} from '@libs/actions/connections'; import {getAssignedSupportData} from '@libs/actions/Policy/Policy'; import {getConciergeReportID} from '@libs/actions/Report'; -import {isExpensifyCardFullySetup} from '@libs/CardUtils'; +import {isExpensifyCardFullySetUp} from '@libs/CardUtils'; import { areSettingsInErrorFields, findCurrentXeroOrganization, @@ -119,7 +119,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { const currentXeroOrganization = findCurrentXeroOrganization(tenants, policy?.connections?.xero?.config?.tenantID); const shouldShowSynchronizationError = !!synchronizationError; const shouldShowReinstallConnectorMenuItem = shouldShowSynchronizationError && connectedIntegration === CONST.POLICY.CONNECTIONS.NAME.QBD; - const shouldShowCardReconciliationOption = isExpensifyCardFullySetup(policy, cardSettings); + const shouldShowCardReconciliationOption = isExpensifyCardFullySetUp(policy, cardSettings); const overflowMenu: ThreeDotsMenuProps['menuItems'] = useMemo( () => [ diff --git a/src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx b/src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx index 79f16b4e4e0a..e63e00795797 100644 --- a/src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx +++ b/src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx @@ -9,7 +9,7 @@ import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as AccountingUtils from '@libs/AccountingUtils'; -import {isExpensifyCardFullySetup} from '@libs/CardUtils'; +import {isExpensifyCardFullySetUp} from '@libs/CardUtils'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import Navigation from '@navigation/Navigation'; import type {SettingsNavigatorParamList} from '@navigation/types'; @@ -44,7 +44,7 @@ function CardReconciliationPage({policy, route}: CardReconciliationPageProps) { const {connection} = route.params; const connectionName = AccountingUtils.getConnectionNameFromRouteParam(connection) as ConnectionName; const autoSync = !!policy?.connections?.[connectionName]?.config?.autoSync?.enabled; - const shouldShow = isExpensifyCardFullySetup(policy, cardSettings); + const shouldShow = isExpensifyCardFullySetUp(policy, cardSettings); const toggleContinuousReconciliation = (value: boolean) => { Card.toggleContinuousReconciliation(workspaceAccountID, value, connectionName, currentConnectionName); diff --git a/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx b/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx index 500051a958eb..cced85973b9b 100644 --- a/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx @@ -18,7 +18,7 @@ import { hasCardListObject, hasOnlyOneCardToAssign, isCustomFeed, - isExpensifyCardFullySetup, + isExpensifyCardFullySetUp, isSelectedFeedExpired, } from '@libs/CardUtils'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; @@ -66,7 +66,7 @@ function WorkspaceMemberNewCardPage({route, personalDetails}: WorkspaceMemberNew const [list] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${selectedFeed}`); const filteredCardList = getFilteredCardList(list, cardFeeds?.settings?.oAuthAccountDetails?.[selectedFeed as CompanyCardFeed]); - const shouldShowExpensifyCard = isExpensifyCardFullySetup(policy, cardSettings); + const shouldShowExpensifyCard = isExpensifyCardFullySetUp(policy, cardSettings); const handleSubmit = () => { if (!selectedFeed) { From aa0ed8604b07832be92f8d462d25b6f47fc34b4f Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 25 Feb 2025 16:29:40 +0100 Subject: [PATCH 5/9] fix: eslint errors --- src/ROUTES.ts | 10 +++++----- .../reconciliation/CardReconciliationPage.tsx | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 1974f9851a88..f3e13840d812 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1110,7 +1110,7 @@ const ROUTES = { }, WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_ADVANCED: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/advanced', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/advanced` as const, + getRoute: (policyID?: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/advanced` as const, }, WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_ACCOUNT_SELECTOR: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/account-selector', @@ -1127,7 +1127,7 @@ const ROUTES = { }, WORKSPACE_ACCOUNTING_RECONCILIATION_ACCOUNT_SETTINGS: { route: 'settings/workspaces/:policyID/accounting/:connection/card-reconciliation/account', - getRoute: (policyID: string, connection?: ValueOf) => + getRoute: (policyID?: string, connection?: ValueOf) => `settings/workspaces/${policyID}/accounting/${connection as string}/card-reconciliation/account` as const, }, WORKSPACE_ACCOUNTING_MULTI_CONNECTION_SELECTOR: { @@ -1767,7 +1767,7 @@ const ROUTES = { }, POLICY_ACCOUNTING_XERO_ADVANCED: { route: 'settings/workspaces/:policyID/accounting/xero/advanced', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/advanced` as const, + getRoute: (policyID?: string) => `settings/workspaces/${policyID}/accounting/xero/advanced` as const, }, POLICY_ACCOUNTING_XERO_BILL_STATUS_SELECTOR: { route: 'settings/workspaces/:policyID/accounting/xero/export/purchase-bill-status-selector', @@ -1938,7 +1938,7 @@ const ROUTES = { }, POLICY_ACCOUNTING_NETSUITE_ADVANCED: { route: 'settings/workspaces/:policyID/connections/netsuite/advanced/', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/` as const, + getRoute: (policyID?: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/` as const, }, POLICY_ACCOUNTING_NETSUITE_REIMBURSEMENT_ACCOUNT_SELECT: { route: 'settings/workspaces/:policyID/connections/netsuite/advanced/reimbursement-account/select', @@ -2104,7 +2104,7 @@ const ROUTES = { }, POLICY_ACCOUNTING_SAGE_INTACCT_ADVANCED: { route: 'settings/workspaces/:policyID/accounting/sage-intacct/advanced', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/advanced` as const, + getRoute: (policyID?: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/advanced` as const, }, POLICY_ACCOUNTING_SAGE_INTACCT_PAYMENT_ACCOUNT: { route: 'settings/workspaces/:policyID/accounting/sage-intacct/advanced/payment-account', diff --git a/src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx b/src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx index e63e00795797..70aa71d6a314 100644 --- a/src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx +++ b/src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx @@ -8,7 +8,7 @@ import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as AccountingUtils from '@libs/AccountingUtils'; +import {getConnectionNameFromRouteParam} from '@libs/AccountingUtils'; import {isExpensifyCardFullySetUp} from '@libs/CardUtils'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import Navigation from '@navigation/Navigation'; @@ -17,7 +17,7 @@ import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; import withPolicyConnections from '@pages/workspace/withPolicyConnections'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; -import * as Card from '@userActions/Card'; +import {toggleContinuousReconciliation} from '@userActions/Card'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -30,24 +30,24 @@ function CardReconciliationPage({policy, route}: CardReconciliationPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const workspaceAccountID = policy?.workspaceAccountID ?? -1; + const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID; const [isContinuousReconciliationOn] = useOnyx(`${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION}${workspaceAccountID}`); const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`); const [currentConnectionName] = useOnyx(`${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION}${workspaceAccountID}`); const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); - const paymentBankAccountID = cardSettings?.paymentBankAccountID ?? 0; + const paymentBankAccountID = cardSettings?.paymentBankAccountID ?? CONST.DEFAULT_NUMBER_ID; const bankAccountTitle = bankAccountList?.[paymentBankAccountID]?.title ?? ''; - const policyID = policy?.id ?? '-1'; + const policyID = policy?.id; const {connection} = route.params; - const connectionName = AccountingUtils.getConnectionNameFromRouteParam(connection) as ConnectionName; + const connectionName = getConnectionNameFromRouteParam(connection) as ConnectionName; const autoSync = !!policy?.connections?.[connectionName]?.config?.autoSync?.enabled; const shouldShow = isExpensifyCardFullySetUp(policy, cardSettings); - const toggleContinuousReconciliation = (value: boolean) => { - Card.toggleContinuousReconciliation(workspaceAccountID, value, connectionName, currentConnectionName); + const handleToggleContinuousReconciliation = (value: boolean) => { + toggleContinuousReconciliation(workspaceAccountID, value, connectionName, currentConnectionName); if (value) { Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_RECONCILIATION_ACCOUNT_SETTINGS.getRoute(policyID, connection)); } @@ -94,7 +94,7 @@ function CardReconciliationPage({policy, route}: CardReconciliationPageProps) { switchAccessibilityLabel={translate('workspace.accounting.continuousReconciliation')} disabled={!autoSync} isActive={!!isContinuousReconciliationOn} - onToggle={toggleContinuousReconciliation} + onToggle={handleToggleContinuousReconciliation} wrapperStyle={styles.ph5} /> {!autoSync && ( From f84c3fea0106ea932fafdc3a86064025317f9c30 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 26 Feb 2025 09:14:49 +0100 Subject: [PATCH 6/9] fix: minor fix --- src/ROUTES.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index f32f05364567..e393f12a2707 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1767,7 +1767,12 @@ const ROUTES = { }, POLICY_ACCOUNTING_XERO_ADVANCED: { route: 'settings/workspaces/:policyID/accounting/xero/advanced', - getRoute: (policyID?: string) => `settings/workspaces/${policyID}/accounting/xero/advanced` as const, + getRoute: (policyID: string | undefined) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the POLICY_ACCOUNTING_XERO_ADVANCED route'); + } + return `settings/workspaces/${policyID}/accounting/xero/advanced` as const; + }, }, POLICY_ACCOUNTING_XERO_BILL_STATUS_SELECTOR: { route: 'settings/workspaces/:policyID/accounting/xero/export/purchase-bill-status-selector', From 5708437026eca1d867e3a26ae38448975dcac93e Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 26 Feb 2025 10:07:54 +0100 Subject: [PATCH 7/9] fix: replace with util --- src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index a9c6bd14f0c2..5b8caab13534 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -22,7 +22,7 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import {setPolicyPreventSelfApproval} from '@libs/actions/Policy/Policy'; import {removeApprovalWorkflow as removeApprovalWorkflowAction, updateApprovalWorkflow} from '@libs/actions/Workflow'; -import {getAllCardsForWorkspace, getCardFeedIcon, getCompanyFeeds, maskCardNumber} from '@libs/CardUtils'; +import {getAllCardsForWorkspace, getCardFeedIcon, getCompanyFeeds, isExpensifyCardFullySetUp, maskCardNumber} from '@libs/CardUtils'; import {convertToDisplayString} from '@libs/CurrencyUtils'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import {getDisplayNameOrDefault} from '@libs/PersonalDetailsUtils'; @@ -85,7 +85,6 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM const ownerDetails = useMemo(() => personalDetails?.[policy?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID] ?? ({} as PersonalDetails), [personalDetails, policy?.ownerAccountID]); const policyOwnerDisplayName = formatPhoneNumber(getDisplayNameOrDefault(ownerDetails)) ?? policy?.owner ?? ''; const hasMultipleFeeds = Object.values(getCompanyFeeds(cardFeeds)).filter((feed) => !feed.pending).length > 0; - const paymentBankAccountID = cardSettings?.paymentBankAccountID; const workspaceCards = getAllCardsForWorkspace(workspaceAccountID, cardList); const policyApproverEmail = policy?.approver; @@ -261,7 +260,7 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM return ; } - const shouldShowCardsSection = (!!paymentBankAccountID && !!policy?.areExpensifyCardsEnabled) || hasMultipleFeeds; + const shouldShowCardsSection = isExpensifyCardFullySetUp(policy, cardSettings) || hasMultipleFeeds; return ( Date: Wed, 26 Feb 2025 10:15:16 +0100 Subject: [PATCH 8/9] fix: route types --- src/ROUTES.ts | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index e393f12a2707..7f4157a5a819 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -135,7 +135,10 @@ const ROUTES = { SETTINGS_TIMEZONE_SELECT: 'settings/profile/timezone/select', SETTINGS_PRONOUNS: 'settings/profile/pronouns', SETTINGS_PREFERENCES: 'settings/preferences', - SETTINGS_SUBSCRIPTION: {route: 'settings/subscription', getRoute: (backTo?: string) => getUrlWithBackToParam('settings/subscription', backTo)}, + SETTINGS_SUBSCRIPTION: { + route: 'settings/subscription', + getRoute: (backTo?: string) => getUrlWithBackToParam('settings/subscription', backTo), + }, SETTINGS_SUBSCRIPTION_SIZE: { route: 'settings/subscription/subscription-size', getRoute: (canChangeSize: 0 | 1) => `settings/subscription/subscription-size?canChangeSize=${canChangeSize as number}` as const, @@ -333,7 +336,10 @@ const ROUTES = { route: 'r/:reportID/edit/policyField/:policyID/:fieldID', getRoute: (reportID: string | undefined, policyID: string | undefined, fieldID: string, backTo?: string) => { if (!policyID || !reportID) { - Log.warn('Invalid policyID or reportID is used to build the EDIT_REPORT_FIELD_REQUEST route', {policyID, reportID}); + Log.warn('Invalid policyID or reportID is used to build the EDIT_REPORT_FIELD_REQUEST route', { + policyID, + reportID, + }); } return getUrlWithBackToParam(`r/${reportID}/edit/policyField/${policyID}/${encodeURIComponent(fieldID)}` as const, backTo); }, @@ -1110,7 +1116,12 @@ const ROUTES = { }, WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_ADVANCED: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/advanced', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/advanced` as const, + getRoute: (policyID: string | undefined) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_ADVANCED route'); + } + return `settings/workspaces/${policyID}/accounting/quickbooks-online/advanced` as const; + }, }, WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_ACCOUNT_SELECTOR: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/account-selector', @@ -1127,8 +1138,12 @@ const ROUTES = { }, WORKSPACE_ACCOUNTING_RECONCILIATION_ACCOUNT_SETTINGS: { route: 'settings/workspaces/:policyID/accounting/:connection/card-reconciliation/account', - getRoute: (policyID: string, connection?: ValueOf) => - `settings/workspaces/${policyID}/accounting/${connection as string}/card-reconciliation/account` as const, + getRoute: (policyID: string | undefined, connection?: ValueOf) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the WORKSPACE_ACCOUNTING_RECONCILIATION_ACCOUNT_SETTINGS route'); + } + return `settings/workspaces/${policyID}/accounting/${connection as string}/card-reconciliation/account` as const; + }, }, WORKSPACE_ACCOUNTING_MULTI_CONNECTION_SELECTOR: { route: 'settings/workspaces/:policyID/accounting/:connection/connection-selector', @@ -2133,7 +2148,12 @@ const ROUTES = { }, POLICY_ACCOUNTING_SAGE_INTACCT_ADVANCED: { route: 'settings/workspaces/:policyID/accounting/sage-intacct/advanced', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/advanced` as const, + getRoute: (policyID: string | undefined) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the POLICY_ACCOUNTING_SAGE_INTACCT_ADVANCED route'); + } + return `settings/workspaces/${policyID}/accounting/sage-intacct/advanced` as const; + }, }, POLICY_ACCOUNTING_SAGE_INTACCT_PAYMENT_ACCOUNT: { route: 'settings/workspaces/:policyID/accounting/sage-intacct/advanced/payment-account', From f89b7751ce532158636f2948a4c9f78652bcc7d7 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 27 Feb 2025 17:49:22 +0100 Subject: [PATCH 9/9] feat: add unit test --- src/libs/CardUtils.ts | 4 ---- tests/unit/CardUtilsTest.ts | 46 ++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index b2c22699659a..2981df506363 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -531,10 +531,6 @@ function hasCardListObject(workspaceAccountID: number, feedName: CompanyCardFeed /** * Check if the Expensify Card is fully set up and a new card can be issued - * - * @param policy the policy object - * @param cardSettings the card settings object - * @returns boolean */ function isExpensifyCardFullySetUp(policy?: OnyxEntry, cardSettings?: OnyxEntry): boolean { return !!(policy?.areExpensifyCardsEnabled && cardSettings?.paymentBankAccountID); diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index e14b886c35d6..99e28e1e5763 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -14,9 +14,10 @@ import { getYearFromExpirationDateString, hasIssuedExpensifyCard, isCustomFeed as isCustomFeedCardUtils, + isExpensifyCardFullySetUp, maskCardNumber, } from '@src/libs/CardUtils'; -import type {CardFeeds, CompanyCardFeed, WorkspaceCardsList} from '@src/types/onyx'; +import type {CardFeeds, CompanyCardFeed, ExpensifyCardSettings, Policy, WorkspaceCardsList} from '@src/types/onyx'; import type {CompanyCardFeedWithNumber} from '@src/types/onyx/CardFeeds'; const shortDate = '0924'; @@ -180,6 +181,22 @@ const customFeedCardsList = { } as unknown as WorkspaceCardsList; const customFeedName = 'Custom feed name'; +const policyWithCardsEnabled = { + areExpensifyCardsEnabled: true, +} as unknown as Policy; + +const policyWithCardsDisabled = { + areExpensifyCardsEnabled: false, +} as unknown as Policy; + +const cardSettingsWithPaymentBankAccountID = { + paymentBankAccountID: '12345', +} as unknown as ExpensifyCardSettings; + +const cardSettingsWithoutPaymentBankAccountID = { + paymentBankAccountID: undefined, +} as unknown as ExpensifyCardSettings; + const cardFeedsCollection: OnyxCollection = { // Policy with both custom and direct feeds FAKE_ID_1: { @@ -582,4 +599,31 @@ describe('CardUtils', () => { expect(hasIssuedExpensifyCard(workspaceAccountID, {})).toBe(false); }); }); + + describe('isExpensifyCardFullySetUp', () => { + it('should return true when policy has enabled cards and cardSettings has payment bank account ID', () => { + const result = isExpensifyCardFullySetUp(policyWithCardsEnabled, cardSettingsWithPaymentBankAccountID); + expect(result).toBe(true); + }); + + it('should return false when policy has disabled cards', () => { + const result = isExpensifyCardFullySetUp(policyWithCardsDisabled, cardSettingsWithoutPaymentBankAccountID); + expect(result).toBe(false); + }); + + it('should return false when cardSettings has no payment bank account ID', () => { + const result = isExpensifyCardFullySetUp(policyWithCardsEnabled, cardSettingsWithoutPaymentBankAccountID); + expect(result).toBe(false); + }); + + it('should return false when cardSettings is undefined', () => { + const result = isExpensifyCardFullySetUp(policyWithCardsEnabled, undefined); + expect(result).toBe(false); + }); + + it('should return false when both policy and cardSettings are undefined', () => { + const result = isExpensifyCardFullySetUp(undefined, undefined); + expect(result).toBe(false); + }); + }); });