From 9d50ed5123f4937b1620f53a4bff8ea2fe2438d1 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Fri, 6 Jun 2025 16:26:16 +0300 Subject: [PATCH 01/14] issu with plaid modal and force close during assign card --- .../workspace/companyCards/BankConnection/index.native.tsx | 4 ++-- src/pages/workspace/companyCards/BankConnection/index.tsx | 4 ++-- .../workspace/companyCards/assignCard/ConfirmationStep.tsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/workspace/companyCards/BankConnection/index.native.tsx b/src/pages/workspace/companyCards/BankConnection/index.native.tsx index 396b83e25c94..b6d0654ead22 100644 --- a/src/pages/workspace/companyCards/BankConnection/index.native.tsx +++ b/src/pages/workspace/companyCards/BankConnection/index.native.tsx @@ -115,9 +115,9 @@ function BankConnection({policyID: policyIDFromProps, feed, route}: BankConnecti if (newFeed) { updateSelectedFeed(newFeed, policyID); } - Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID)); + Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID), {forceReplace: true}); } - }, [isNewFeedConnected, newFeed, policyID, url, feed, isFeedExpired, assignCard]); + }, [isNewFeedConnected, newFeed, policyID, url, feed, isFeedExpired, assignCard?.data?.dateOption]); const checkIfConnectionCompleted = (navState: WebViewNavigation) => { if (!navState.url.includes(ROUTES.BANK_CONNECTION_COMPLETE)) { diff --git a/src/pages/workspace/companyCards/BankConnection/index.tsx b/src/pages/workspace/companyCards/BankConnection/index.tsx index 4daa47271c25..2fc80d2c1225 100644 --- a/src/pages/workspace/companyCards/BankConnection/index.tsx +++ b/src/pages/workspace/companyCards/BankConnection/index.tsx @@ -140,13 +140,13 @@ function BankConnection({policyID: policyIDFromProps, feed, route}: BankConnecti updateSelectedFeed(newFeed, policyID); } Navigation.closeRHPFlow(); - Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID)); + Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID), {forceReplace: true}); return; } if (!shouldBlockWindowOpen) { customWindow = openBankConnection(url); } - }, [isNewFeedConnected, shouldBlockWindowOpen, newFeed, policyID, url, feed, isFeedExpired, isOffline, assignCard]); + }, [isNewFeedConnected, shouldBlockWindowOpen, newFeed, policyID, url, feed, isFeedExpired, isOffline, assignCard?.data?.dateOption]); return ( clearAssignCardStepAndData()); }, [assignCard, backTo, policyID]); From 4d86a2697bb2c735ad354786b7062fc83abf318c Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Fri, 6 Jun 2025 17:22:29 +0300 Subject: [PATCH 02/14] fix test --- tests/ui/AssignCardFeedPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/AssignCardFeedPage.tsx b/tests/ui/AssignCardFeedPage.tsx index b89bf503b9ce..c3a536e87fd8 100644 --- a/tests/ui/AssignCardFeedPage.tsx +++ b/tests/ui/AssignCardFeedPage.tsx @@ -233,7 +233,7 @@ describe('AssignCardFeedPage', () => { // Verify that we navigate to the company cards page as the card assignee has changed await waitFor(() => { - expect(navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policy.id)); + expect(navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policy.id), {forceReplace: true}); }); // Unmount the component after assertions to clean up. unmount(); From a1b444fa6140a8a1f9012cc05e730b915352b1ac Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Mon, 9 Jun 2025 15:00:03 +0300 Subject: [PATCH 03/14] fix bugs --- src/libs/CardUtils.ts | 11 ++++- .../companyCards/addNew/CardTypeStep.tsx | 6 +++ .../companyCards/addNew/SelectCountryStep.tsx | 12 +++-- .../companyCards/addNew/SelectFeedType.tsx | 3 +- .../members/WorkspaceMemberNewCardPage.tsx | 44 ++++++++++++------- 5 files changed, 54 insertions(+), 22 deletions(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index cfc4151c21ea..4d98987afa2b 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -411,8 +411,7 @@ function getCustomOrFormattedFeedName(feed?: CompanyCardFeed, companyCardNicknam return ''; } - const formattedFeedName = translateLocal('workspace.companyCards.feedName', {feedName: getBankName(feed)}); - return customFeedName ?? formattedFeedName; + return translateLocal('workspace.companyCards.feedName', {feedName: customFeedName ?? getBankName(feed)}); } function getPlaidInstitutionIconUrl(feedName?: string) { @@ -432,6 +431,13 @@ function getPlaidInstitutionId(feedName?: string) { return feed.at(1); } +function isPlaidCountrySupported(selectedCountry?: string) { + if (!selectedCountry) { + return false; + } + return CONST.PLAID_SUPPORT_COUNTRIES.includes(selectedCountry); +} + function getDomainOrWorkspaceAccountID(workspaceAccountID: number, cardFeedData: CardFeedData | undefined): number { return cardFeedData?.domainID ?? workspaceAccountID; } @@ -695,6 +701,7 @@ export { getCorrectStepForSelectedBank, getCustomOrFormattedFeedName, isCardClosed, + isPlaidCountrySupported, getFilteredCardList, hasOnlyOneCardToAssign, checkIfNewFeedConnected, diff --git a/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx b/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx index 5713a93859d9..562cc1d9385f 100644 --- a/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx +++ b/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx @@ -14,6 +14,7 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; +import {isPlaidCountrySupported} from '@libs/CardUtils'; import variables from '@styles/variables'; import {setAddNewCompanyCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; @@ -93,6 +94,7 @@ function CardTypeStep() { const {bankName, selectedBank, feedType} = addNewCard?.data ?? {}; const isOtherBankSelected = selectedBank === CONST.COMPANY_CARDS.BANKS.OTHER; const isNewCardTypeSelected = typeSelected !== feedType; + const isCountrySupportPlaid = isPlaidCountrySupported(addNewCard?.data.selectedCountry); const submit = () => { if (!typeSelected) { @@ -118,6 +120,10 @@ function CardTypeStep() { setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.SELECT_BANK}); return; } + if (isBetaEnabled(CONST.BETAS.PLAID_COMPANY_CARDS) && !isCountrySupportPlaid) { + setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.SELECT_COUNTRY}); + return; + } setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.SELECT_FEED_TYPE}); }; diff --git a/src/pages/workspace/companyCards/addNew/SelectCountryStep.tsx b/src/pages/workspace/companyCards/addNew/SelectCountryStep.tsx index 6ac983d33473..352fcc757dc1 100644 --- a/src/pages/workspace/companyCards/addNew/SelectCountryStep.tsx +++ b/src/pages/workspace/companyCards/addNew/SelectCountryStep.tsx @@ -12,6 +12,7 @@ import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; +import {isPlaidCountrySupported} from '@libs/CardUtils'; import searchOptions from '@libs/searchOptions'; import type {Option} from '@libs/searchOptions'; import StringUtils from '@libs/StringUtils'; @@ -46,14 +47,18 @@ function SelectCountryStep({policyID}: CountryStepProps) { const selectedCurrency = policy?.outputCurrency ? currencyList?.[policy.outputCurrency] : null; const countries = selectedCurrency?.countries; - if (policy?.outputCurrency === CONST.CURRENCY.EUR && countryByIp && countries?.includes(countryByIp)) { - return countryByIp; + if (policy?.outputCurrency === CONST.CURRENCY.EUR) { + if (countryByIp && countries?.includes(countryByIp)) { + return countryByIp; + } + return ''; } const country = countries?.[0]; return country ?? ''; }, [addNewCard?.data.selectedCountry, countryByIp, currencyList, policy?.outputCurrency]); const [currentCountry, setCurrentCountry] = useState(getCountry); const [hasError, setHasError] = useState(false); + const isCountrySupportPlaid = isPlaidCountrySupported(currentCountry); const submit = () => { if (!currentCountry) { @@ -63,9 +68,10 @@ function SelectCountryStep({policyID}: CountryStepProps) { clearAddNewCardFlow(); } setAddNewCompanyCardStepAndData({ - step: CONST.COMPANY_CARDS.STEP.SELECT_FEED_TYPE, + step: isCountrySupportPlaid ? CONST.COMPANY_CARDS.STEP.SELECT_FEED_TYPE : CONST.COMPANY_CARDS.STEP.CARD_TYPE, data: { selectedCountry: currentCountry, + selectedFeedType: isCountrySupportPlaid ? CONST.COMPANY_CARDS.FEED_TYPE.DIRECT : CONST.COMPANY_CARDS.FEED_TYPE.CUSTOM, }, isEditing: false, }); diff --git a/src/pages/workspace/companyCards/addNew/SelectFeedType.tsx b/src/pages/workspace/companyCards/addNew/SelectFeedType.tsx index 5e0296846dca..135ce878c70b 100644 --- a/src/pages/workspace/companyCards/addNew/SelectFeedType.tsx +++ b/src/pages/workspace/companyCards/addNew/SelectFeedType.tsx @@ -12,6 +12,7 @@ import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; +import {isPlaidCountrySupported} from '@libs/CardUtils'; import {setAddNewCompanyCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -23,7 +24,7 @@ function SelectFeedType() { const [typeSelected, setTypeSelected] = useState>(); const [hasError, setHasError] = useState(false); const {isBetaEnabled} = usePermissions(); - const isCountrySupportPlaid = addNewCard?.data?.selectedCountry ? CONST.PLAID_SUPPORT_COUNTRIES.includes(addNewCard.data.selectedCountry) : false; + const isCountrySupportPlaid = isPlaidCountrySupported(addNewCard?.data?.selectedCountry); const isUSCountry = addNewCard?.data?.selectedCountry === CONST.COUNTRY.US; const submit = () => { diff --git a/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx b/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx index c4595ab5a588..550eeb297703 100644 --- a/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx @@ -4,6 +4,7 @@ import ExpensifyCardImage from '@assets/images/expensify-card.svg'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Icon from '@components/Icon'; +import PlaidCardFeedIcon from '@components/PlaidCardFeedIcon'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; @@ -19,6 +20,7 @@ import { getCustomOrFormattedFeedName, getDomainOrWorkspaceAccountID, getFilteredCardList, + getPlaidInstitutionIconUrl, hasCardListObject, hasOnlyOneCardToAssign, isCustomFeed, @@ -125,22 +127,32 @@ function WorkspaceMemberNewCardPage({route, personalDetails}: WorkspaceMemberNew setShouldShowError(false); }; - const companyCardFeeds: CardFeedListItem[] = (Object.keys(companyFeeds) as CompanyCardFeed[]).map((key) => ({ - value: key, - text: getCustomOrFormattedFeedName(key, cardFeeds?.settings?.companyCardNicknames), - keyForList: key, - isDisabled: companyFeeds[key]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - pendingAction: companyFeeds[key]?.pendingAction, - isSelected: selectedFeed === key, - leftElement: ( - - ), - })); + const companyCardFeeds: CardFeedListItem[] = (Object.keys(companyFeeds) as CompanyCardFeed[]).map((key) => { + const plaidUrl = getPlaidInstitutionIconUrl(key); + + return { + value: key, + text: getCustomOrFormattedFeedName(key, cardFeeds?.settings?.companyCardNicknames), + keyForList: key, + isDisabled: companyFeeds[key]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + pendingAction: companyFeeds[key]?.pendingAction, + isSelected: selectedFeed === key, + + leftElement: plaidUrl ? ( + + ) : ( + + ), + }; + }); const feeds = shouldShowExpensifyCard ? [ From 16f47c6cd9130e59b4ef7381e256cc001555649c Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Mon, 9 Jun 2025 18:32:33 +0300 Subject: [PATCH 04/14] fix bugs --- src/libs/CardUtils.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 4d98987afa2b..cfc4151c21ea 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -411,7 +411,8 @@ function getCustomOrFormattedFeedName(feed?: CompanyCardFeed, companyCardNicknam return ''; } - return translateLocal('workspace.companyCards.feedName', {feedName: customFeedName ?? getBankName(feed)}); + const formattedFeedName = translateLocal('workspace.companyCards.feedName', {feedName: getBankName(feed)}); + return customFeedName ?? formattedFeedName; } function getPlaidInstitutionIconUrl(feedName?: string) { @@ -431,13 +432,6 @@ function getPlaidInstitutionId(feedName?: string) { return feed.at(1); } -function isPlaidCountrySupported(selectedCountry?: string) { - if (!selectedCountry) { - return false; - } - return CONST.PLAID_SUPPORT_COUNTRIES.includes(selectedCountry); -} - function getDomainOrWorkspaceAccountID(workspaceAccountID: number, cardFeedData: CardFeedData | undefined): number { return cardFeedData?.domainID ?? workspaceAccountID; } @@ -701,7 +695,6 @@ export { getCorrectStepForSelectedBank, getCustomOrFormattedFeedName, isCardClosed, - isPlaidCountrySupported, getFilteredCardList, hasOnlyOneCardToAssign, checkIfNewFeedConnected, From a074e838a6724f1e90534ac2177fc030ffe5f8bc Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Mon, 9 Jun 2025 18:38:28 +0300 Subject: [PATCH 05/14] fix bugs --- src/libs/CardUtils.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index cfc4151c21ea..4016dd7f5abc 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -432,6 +432,13 @@ function getPlaidInstitutionId(feedName?: string) { return feed.at(1); } +function isPlaidCountrySupported(selectedCountry?: string) { + if (!selectedCountry) { + return false; + } + return CONST.PLAID_SUPPORT_COUNTRIES.includes(selectedCountry); +} + function getDomainOrWorkspaceAccountID(workspaceAccountID: number, cardFeedData: CardFeedData | undefined): number { return cardFeedData?.domainID ?? workspaceAccountID; } @@ -695,6 +702,7 @@ export { getCorrectStepForSelectedBank, getCustomOrFormattedFeedName, isCardClosed, + isPlaidCountrySupported, getFilteredCardList, hasOnlyOneCardToAssign, checkIfNewFeedConnected, From d828c5793d511a7e2b707d199f90fafab2731700 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Tue, 10 Jun 2025 16:42:51 +0300 Subject: [PATCH 06/14] fix bugs --- .../workspace/companyCards/WorkspaceCompanyCardsPage.tsx | 3 ++- .../workspace/companyCards/addNew/AddNewCardPage.tsx | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx index 012220f81543..5aba928127b4 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx @@ -30,7 +30,7 @@ import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; import {isDeletedPolicyEmployee} from '@libs/PolicyUtils'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; -import {openPolicyCompanyCardsFeed, openPolicyCompanyCardsPage, setAssignCardStepAndData} from '@userActions/CompanyCards'; +import {clearAddNewCardFlow, openPolicyCompanyCardsFeed, openPolicyCompanyCardsPage, setAssignCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -142,6 +142,7 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { currentStep = institutionId ? CONST.COMPANY_CARD.STEP.PLAID_CONNECTION : CONST.COMPANY_CARD.STEP.BANK_CONNECTION; } + clearAddNewCardFlow(); setAssignCardStepAndData({data, currentStep}); Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD.getRoute(policyID, selectedFeed))); }; diff --git a/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx b/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx index e8b3eb596e26..1d40658064bd 100644 --- a/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx +++ b/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx @@ -8,7 +8,7 @@ import useWorkspaceAccountID from '@hooks/useWorkspaceAccountID'; import BankConnection from '@pages/workspace/companyCards/BankConnection'; import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; -import {openPolicyAddCardFeedPage} from '@userActions/CompanyCards'; +import {clearAddNewCardFlow, openPolicyAddCardFeedPage} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; @@ -33,6 +33,12 @@ function AddNewCardPage({policy}: WithPolicyAndFullscreenLoadingProps) { const isAddCardFeedLoading = isLoadingOnyxValue(addNewCardFeedMetadata); + useEffect(() => { + return () => { + clearAddNewCardFlow(); + }; + }, []); + useEffect(() => { // If the user only has a domain feed, a workspace account may not have been created yet. // However, adding a workspace feed requires a workspace account. From a53c094acb841049fdf3afe864ba5b4591488bf5 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Wed, 11 Jun 2025 09:51:34 +0300 Subject: [PATCH 07/14] fix namings --- src/libs/CardUtils.ts | 4 ++-- .../workspace/companyCards/addNew/CardTypeStep.tsx | 6 +++--- .../companyCards/addNew/SelectCountryStep.tsx | 8 ++++---- .../workspace/companyCards/addNew/SelectFeedType.tsx | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 4016dd7f5abc..b53cb95cc17f 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -432,7 +432,7 @@ function getPlaidInstitutionId(feedName?: string) { return feed.at(1); } -function isPlaidCountrySupported(selectedCountry?: string) { +function isPlaidSupportedCountry(selectedCountry?: string) { if (!selectedCountry) { return false; } @@ -702,7 +702,7 @@ export { getCorrectStepForSelectedBank, getCustomOrFormattedFeedName, isCardClosed, - isPlaidCountrySupported, + isPlaidSupportedCountry, getFilteredCardList, hasOnlyOneCardToAssign, checkIfNewFeedConnected, diff --git a/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx b/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx index 562cc1d9385f..3e7ea75471d0 100644 --- a/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx +++ b/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx @@ -14,7 +14,7 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; -import {isPlaidCountrySupported} from '@libs/CardUtils'; +import {isPlaidSupportedCountry} from '@libs/CardUtils'; import variables from '@styles/variables'; import {setAddNewCompanyCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; @@ -94,7 +94,7 @@ function CardTypeStep() { const {bankName, selectedBank, feedType} = addNewCard?.data ?? {}; const isOtherBankSelected = selectedBank === CONST.COMPANY_CARDS.BANKS.OTHER; const isNewCardTypeSelected = typeSelected !== feedType; - const isCountrySupportPlaid = isPlaidCountrySupported(addNewCard?.data.selectedCountry); + const doesCountrySupportPlaid = isPlaidSupportedCountry(addNewCard?.data.selectedCountry); const submit = () => { if (!typeSelected) { @@ -120,7 +120,7 @@ function CardTypeStep() { setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.SELECT_BANK}); return; } - if (isBetaEnabled(CONST.BETAS.PLAID_COMPANY_CARDS) && !isCountrySupportPlaid) { + if (isBetaEnabled(CONST.BETAS.PLAID_COMPANY_CARDS) && !doesCountrySupportPlaid) { setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.SELECT_COUNTRY}); return; } diff --git a/src/pages/workspace/companyCards/addNew/SelectCountryStep.tsx b/src/pages/workspace/companyCards/addNew/SelectCountryStep.tsx index 352fcc757dc1..8bc03c62eff1 100644 --- a/src/pages/workspace/companyCards/addNew/SelectCountryStep.tsx +++ b/src/pages/workspace/companyCards/addNew/SelectCountryStep.tsx @@ -12,7 +12,7 @@ import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; -import {isPlaidCountrySupported} from '@libs/CardUtils'; +import {isPlaidSupportedCountry} from '@libs/CardUtils'; import searchOptions from '@libs/searchOptions'; import type {Option} from '@libs/searchOptions'; import StringUtils from '@libs/StringUtils'; @@ -58,7 +58,7 @@ function SelectCountryStep({policyID}: CountryStepProps) { }, [addNewCard?.data.selectedCountry, countryByIp, currencyList, policy?.outputCurrency]); const [currentCountry, setCurrentCountry] = useState(getCountry); const [hasError, setHasError] = useState(false); - const isCountrySupportPlaid = isPlaidCountrySupported(currentCountry); + const doesCountrySupportPlaid = isPlaidSupportedCountry(currentCountry); const submit = () => { if (!currentCountry) { @@ -68,10 +68,10 @@ function SelectCountryStep({policyID}: CountryStepProps) { clearAddNewCardFlow(); } setAddNewCompanyCardStepAndData({ - step: isCountrySupportPlaid ? CONST.COMPANY_CARDS.STEP.SELECT_FEED_TYPE : CONST.COMPANY_CARDS.STEP.CARD_TYPE, + step: doesCountrySupportPlaid ? CONST.COMPANY_CARDS.STEP.SELECT_FEED_TYPE : CONST.COMPANY_CARDS.STEP.CARD_TYPE, data: { selectedCountry: currentCountry, - selectedFeedType: isCountrySupportPlaid ? CONST.COMPANY_CARDS.FEED_TYPE.DIRECT : CONST.COMPANY_CARDS.FEED_TYPE.CUSTOM, + selectedFeedType: doesCountrySupportPlaid ? CONST.COMPANY_CARDS.FEED_TYPE.DIRECT : CONST.COMPANY_CARDS.FEED_TYPE.CUSTOM, }, isEditing: false, }); diff --git a/src/pages/workspace/companyCards/addNew/SelectFeedType.tsx b/src/pages/workspace/companyCards/addNew/SelectFeedType.tsx index 135ce878c70b..ac9a66d19c36 100644 --- a/src/pages/workspace/companyCards/addNew/SelectFeedType.tsx +++ b/src/pages/workspace/companyCards/addNew/SelectFeedType.tsx @@ -12,7 +12,7 @@ import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; -import {isPlaidCountrySupported} from '@libs/CardUtils'; +import {isPlaidSupportedCountry} from '@libs/CardUtils'; import {setAddNewCompanyCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -24,7 +24,7 @@ function SelectFeedType() { const [typeSelected, setTypeSelected] = useState>(); const [hasError, setHasError] = useState(false); const {isBetaEnabled} = usePermissions(); - const isCountrySupportPlaid = isPlaidCountrySupported(addNewCard?.data?.selectedCountry); + const doesCountrySupportPlaid = isPlaidSupportedCountry(addNewCard?.data?.selectedCountry); const isUSCountry = addNewCard?.data?.selectedCountry === CONST.COUNTRY.US; const submit = () => { @@ -53,10 +53,10 @@ function SelectFeedType() { setTypeSelected(addNewCard?.data.selectedFeedType); return; } - if (isCountrySupportPlaid) { + if (doesCountrySupportPlaid) { setTypeSelected(CONST.COMPANY_CARDS.FEED_TYPE.DIRECT); } - }, [addNewCard?.data.selectedFeedType, isCountrySupportPlaid]); + }, [addNewCard?.data.selectedFeedType, doesCountrySupportPlaid]); const handleBackButtonPress = () => { setAddNewCompanyCardStepAndData({step: isBetaEnabled(CONST.BETAS.PLAID_COMPANY_CARDS) ? CONST.COMPANY_CARDS.STEP.SELECT_COUNTRY : CONST.COMPANY_CARDS.STEP.SELECT_BANK}); @@ -85,7 +85,7 @@ function SelectFeedType() { if (!isBetaEnabled(CONST.BETAS.PLAID_COMPANY_CARDS)) { return data; } - if (isCountrySupportPlaid) { + if (doesCountrySupportPlaid) { return data.reverse(); } From 626b19574ba02ddae9b85d691e2096ceea0bc7de Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Wed, 11 Jun 2025 10:52:41 +0300 Subject: [PATCH 08/14] fix broken connection --- src/libs/CardUtils.ts | 17 ++++++++++++++++- ...WorkspaceCompanyCardsListHeaderButtons.tsx | 19 ++++++++++++++++--- .../WorkspaceCompanyCardsPage.tsx | 12 +++++++++++- .../companyCards/addNew/SelectCountryStep.tsx | 14 +++----------- .../assignCard/ConfirmationStep.tsx | 15 +++++++++++++-- 5 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index e679a3bed594..59af4f3ad0b2 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -10,7 +10,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, ExpensifyCardSettings, PersonalDetailsList, Policy, WorkspaceCardsList} from '@src/types/onyx'; +import type {BankAccountList, Card, CardFeeds, CardList, CompanyCardFeed, CurrencyList, ExpensifyCardSettings, PersonalDetailsList, Policy, WorkspaceCardsList} from '@src/types/onyx'; import type {FilteredCardList} from '@src/types/onyx/Card'; import type {CardFeedData, CompanyCardFeedWithNumber, CompanyCardNicknames, CompanyFeeds, DirectCardFeedData} from '@src/types/onyx/CardFeeds'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -443,6 +443,20 @@ function getDomainOrWorkspaceAccountID(workspaceAccountID: number, cardFeedData: return cardFeedData?.domainID ?? workspaceAccountID; } +function getPlaidCountry(outputCurrency?: string, currencyList?: CurrencyList, countryByIp?: string) { + const selectedCurrency = outputCurrency ? currencyList?.[outputCurrency] : null; + const countries = selectedCurrency?.countries; + + if (outputCurrency === CONST.CURRENCY.EUR) { + if (countryByIp && countries?.includes(countryByIp)) { + return countryByIp; + } + return ''; + } + const country = countries?.[0]; + return country ?? ''; +} + // We will simplify the logic below once we have #50450 #50451 implemented const getCorrectStepForSelectedBank = (selectedBank: ValueOf) => { const banksWithFeedType = [ @@ -700,6 +714,7 @@ export { getBankCardDetailsImage, getSelectedFeed, getCorrectStepForSelectedBank, + getPlaidCountry, getCustomOrFormattedFeedName, isCardClosed, isPlaidSupportedCountry, diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx index a666c90aef1c..97ccd5792096 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx @@ -10,6 +10,7 @@ import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useCardFeeds from '@hooks/useCardFeeds'; import useLocalize from '@hooks/useLocalize'; +import usePolicy from '@hooks/usePolicy'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; @@ -25,12 +26,13 @@ import { getCompanyFeeds, getCustomOrFormattedFeedName, getDomainOrWorkspaceAccountID, + getPlaidCountry, getPlaidInstitutionIconUrl, getPlaidInstitutionId, isCustomFeed, } from '@libs/CardUtils'; import Navigation from '@navigation/Navigation'; -import {setAssignCardStepAndData} from '@userActions/CompanyCards'; +import {setAddNewCompanyCardStepAndData, setAssignCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -59,7 +61,10 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed, shouldS const {shouldUseNarrowLayout, isMediumScreenWidth} = useResponsiveLayout(); const workspaceAccountID = useWorkspaceAccountID(policyID); const [cardFeeds] = useCardFeeds(policyID); + const policy = usePolicy(policyID); const [allFeedsCards] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}`, {canBeMissing: false}); + const [currencyList = {}] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: true}); + const [countryByIp] = useOnyx(ONYXKEYS.COUNTRY, {canBeMissing: false}); const shouldChangeLayout = isMediumScreenWidth || shouldUseNarrowLayout; const formattedFeedName = getCustomOrFormattedFeedName(selectedFeed, cardFeeds?.settings?.companyCardNicknames); const isCommercialFeed = isCustomFeed(selectedFeed); @@ -69,12 +74,20 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed, shouldS const bankName = plaidUrl && formattedFeedName ? formattedFeedName : getBankName(selectedFeed); const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, currentFeedData); const filteredFeedCards = filterInactiveCards(allFeedsCards?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${domainOrWorkspaceAccountID}_${selectedFeed}`]); - const isSelectedFeedConnectionBroken = checkIfFeedConnectionIsBroken(filteredFeedCards); + const isSelectedFeedConnectionBroken = !checkIfFeedConnectionIsBroken(filteredFeedCards); const openBankConnection = () => { const institutionId = !!getPlaidInstitutionId(selectedFeed); if (institutionId) { - setAssignCardStepAndData({currentStep: CONST.COMPANY_CARD.STEP.PLAID_CONNECTION}); + const country = getPlaidCountry(policy?.outputCurrency, currencyList, countryByIp); + setAddNewCompanyCardStepAndData({ + data: { + selectedCountry: country, + }, + }); + setAssignCardStepAndData({ + currentStep: CONST.COMPANY_CARD.STEP.PLAID_CONNECTION, + }); Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD.getRoute(policyID, selectedFeed))); return; } diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx index 5aba928127b4..db11956240c5 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx @@ -17,6 +17,7 @@ import { getCompanyFeeds, getDomainOrWorkspaceAccountID, getFilteredCardList, + getPlaidCountry, getPlaidInstitutionId, getSelectedFeed, hasOnlyOneCardToAssign, @@ -30,7 +31,7 @@ import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; import {isDeletedPolicyEmployee} from '@libs/PolicyUtils'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; -import {clearAddNewCardFlow, openPolicyCompanyCardsFeed, openPolicyCompanyCardsPage, setAssignCardStepAndData} from '@userActions/CompanyCards'; +import {clearAddNewCardFlow, openPolicyCompanyCardsFeed, openPolicyCompanyCardsPage, setAddNewCompanyCardStepAndData, setAssignCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -56,6 +57,7 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { const selectedFeed = getSelectedFeed(lastSelectedFeed, cardFeeds); const [cardsList] = useCardsList(policyID, selectedFeed); const [countryByIp] = useOnyx(ONYXKEYS.COUNTRY, {canBeMissing: false}); + const [currencyList = {}] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: true}); const {isBetaEnabled} = usePermissions(); const hasNoAssignedCard = Object.keys(cardsList ?? {}).length === 0; @@ -139,6 +141,14 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { if (isFeedExpired) { const institutionId = !!getPlaidInstitutionId(selectedFeed); + if (institutionId) { + const country = getPlaidCountry(policy?.outputCurrency, currencyList, countryByIp); + setAddNewCompanyCardStepAndData({ + data: { + selectedCountry: country, + }, + }); + } currentStep = institutionId ? CONST.COMPANY_CARD.STEP.PLAID_CONNECTION : CONST.COMPANY_CARD.STEP.BANK_CONNECTION; } diff --git a/src/pages/workspace/companyCards/addNew/SelectCountryStep.tsx b/src/pages/workspace/companyCards/addNew/SelectCountryStep.tsx index 8bc03c62eff1..5d1b44c9e01d 100644 --- a/src/pages/workspace/companyCards/addNew/SelectCountryStep.tsx +++ b/src/pages/workspace/companyCards/addNew/SelectCountryStep.tsx @@ -12,7 +12,7 @@ import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; -import {isPlaidSupportedCountry} from '@libs/CardUtils'; +import {getPlaidCountry, isPlaidSupportedCountry} from '@libs/CardUtils'; import searchOptions from '@libs/searchOptions'; import type {Option} from '@libs/searchOptions'; import StringUtils from '@libs/StringUtils'; @@ -44,18 +44,10 @@ function SelectCountryStep({policyID}: CountryStepProps) { if (addNewCard?.data?.selectedCountry) { return addNewCard.data.selectedCountry; } - const selectedCurrency = policy?.outputCurrency ? currencyList?.[policy.outputCurrency] : null; - const countries = selectedCurrency?.countries; - if (policy?.outputCurrency === CONST.CURRENCY.EUR) { - if (countryByIp && countries?.includes(countryByIp)) { - return countryByIp; - } - return ''; - } - const country = countries?.[0]; - return country ?? ''; + return getPlaidCountry(policy?.outputCurrency, currencyList, countryByIp); }, [addNewCard?.data.selectedCountry, countryByIp, currencyList, policy?.outputCurrency]); + const [currentCountry, setCurrentCountry] = useState(getCountry); const [hasError, setHasError] = useState(false); const doesCountrySupportPlaid = isPlaidSupportedCountry(currentCountry); diff --git a/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx b/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx index 1d010f59823f..9f9528ab44ea 100644 --- a/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx @@ -11,10 +11,10 @@ import useCardFeeds from '@hooks/useCardFeeds'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; -import {getPlaidInstitutionId, isSelectedFeedExpired, lastFourNumbersFromCardName, maskCardNumber} from '@libs/CardUtils'; +import {getPlaidCountry, getPlaidInstitutionId, isSelectedFeedExpired, lastFourNumbersFromCardName, maskCardNumber} from '@libs/CardUtils'; import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; import Navigation from '@navigation/Navigation'; -import {assignWorkspaceCompanyCard, clearAssignCardStepAndData, setAssignCardStepAndData} from '@userActions/CompanyCards'; +import {assignWorkspaceCompanyCard, clearAssignCardStepAndData, setAddNewCompanyCardStepAndData, setAssignCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -36,6 +36,9 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) { const {isOffline} = useNetwork(); const [assignCard] = useOnyx(ONYXKEYS.ASSIGN_CARD, {canBeMissing: false}); + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: false}); + const [countryByIp] = useOnyx(ONYXKEYS.COUNTRY, {canBeMissing: false}); + const [currencyList = {}] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: true}); const feed = assignCard?.data?.bankName as CompanyCardFeed | undefined; const [cardFeeds] = useCardFeeds(policyID); @@ -64,6 +67,14 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) { const institutionId = !!getPlaidInstitutionId(feed); if (isFeedExpired) { + if (institutionId) { + const country = getPlaidCountry(policy?.outputCurrency, currencyList, countryByIp); + setAddNewCompanyCardStepAndData({ + data: { + selectedCountry: country, + }, + }); + } setAssignCardStepAndData({currentStep: institutionId ? CONST.COMPANY_CARD.STEP.PLAID_CONNECTION : CONST.COMPANY_CARD.STEP.BANK_CONNECTION}); return; } From 1bdca51ca5e7564c89a5ac798a198d8c57dcec6d Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Wed, 11 Jun 2025 11:59:41 +0300 Subject: [PATCH 09/14] fix broken connection --- .../companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx index 97ccd5792096..fd0e20092e96 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx @@ -74,7 +74,7 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed, shouldS const bankName = plaidUrl && formattedFeedName ? formattedFeedName : getBankName(selectedFeed); const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, currentFeedData); const filteredFeedCards = filterInactiveCards(allFeedsCards?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${domainOrWorkspaceAccountID}_${selectedFeed}`]); - const isSelectedFeedConnectionBroken = !checkIfFeedConnectionIsBroken(filteredFeedCards); + const isSelectedFeedConnectionBroken = checkIfFeedConnectionIsBroken(filteredFeedCards); const openBankConnection = () => { const institutionId = !!getPlaidInstitutionId(selectedFeed); From d5734c6c88dbc75885ea1dd579199cd2f974e283 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Wed, 11 Jun 2025 14:26:29 +0300 Subject: [PATCH 10/14] add image fallback --- src/components/PlaidCardFeedIcon.tsx | 47 +++++++++++++++++++++------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/src/components/PlaidCardFeedIcon.tsx b/src/components/PlaidCardFeedIcon.tsx index a585c8ff3000..1de37bcc23da 100644 --- a/src/components/PlaidCardFeedIcon.tsx +++ b/src/components/PlaidCardFeedIcon.tsx @@ -1,6 +1,7 @@ -import React from 'react'; +import React, {useEffect, useState} from 'react'; import {View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; +import useThemeIllustrations from '@hooks/useThemeIllustrations'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import Icon from './Icon'; @@ -14,19 +15,43 @@ type PlaidCardFeedIconProps = { }; function PlaidCardFeedIcon({plaidUrl, style, isLarge}: PlaidCardFeedIconProps) { + const [isBrokenImage, setIsBrokenImage] = useState(false); const styles = useThemeStyles(); + const illustrations = useThemeIllustrations(); + const width = isLarge ? variables.cardPreviewWidth : variables.cardIconWidth; + const height = isLarge ? variables.cardPreviewHeight : variables.cardIconHeight; + + useEffect(() => { + if (!plaidUrl) { + return; + } + setIsBrokenImage(false); + }, [plaidUrl]); + return ( - - + {isBrokenImage ? ( + + ) : ( + <> + setIsBrokenImage(true)} + /> + + + )} ); } From 9fd7e0089de19a672f648e836cd643681ec8b081 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Thu, 12 Jun 2025 10:56:49 +0300 Subject: [PATCH 11/14] add translation --- 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 54dec63bb5d6..7b249448820a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3953,7 +3953,7 @@ const translations = { companyCard: 'company card', chooseCardFeed: 'Choose card feed', ukRegulation: - 'Expensify Limited is an agent of Plaid Financial Ltd., an authorised payment institution regulated by the Financial Conduct Authority under the Payment Services Regulations 2017 (Firm Reference Number: 804718). Plaid provides you with regulated account information services through Expensify Limited as its agent.', + 'Expensify, Inc. is an agent of Plaid Financial Ltd., an authorised payment institution regulated by the Financial Conduct Authority under the Payment Services Regulations 2017 (Firm Reference Number: 804718). Plaid provides you with regulated account information services through Expensify Limited as its agent.', }, expensifyCard: { issueAndManageCards: 'Issue and manage your Expensify Cards', diff --git a/src/languages/es.ts b/src/languages/es.ts index d83758e7c7f4..e702e5254780 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3993,7 +3993,7 @@ const translations = { companyCard: 'tarjeta de empresa', chooseCardFeed: 'Elige feed de tarjetas', ukRegulation: - 'Expensify Limited es un agente de Plaid Financial Ltd., una institución de pago autorizada y regulada por la Financial Conduct Authority conforme al Reglamento de Servicios de Pago de 2017 (Número de Referencia de la Firma: 804718). Plaid te proporciona servicios regulados de información de cuentas a través de Expensify Limited como su agente.', + 'Expensify, Inc. es un agente de Plaid Financial Ltd., una institución de pago autorizada y regulada por la Financial Conduct Authority conforme al Reglamento de Servicios de Pago de 2017 (Número de Referencia de la Firma: 804718). Plaid te proporciona servicios regulados de información de cuentas a través de Expensify Limited como su agente.', }, expensifyCard: { issueAndManageCards: 'Emitir y gestionar Tarjetas Expensify', From 747dd4c7e7681bd5a9a2a1057c5ee4e2ad3883a6 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Fri, 13 Jun 2025 12:15:02 +0300 Subject: [PATCH 12/14] add loader for images --- src/components/PlaidCardFeedIcon.tsx | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/components/PlaidCardFeedIcon.tsx b/src/components/PlaidCardFeedIcon.tsx index 1de37bcc23da..407c43e895a0 100644 --- a/src/components/PlaidCardFeedIcon.tsx +++ b/src/components/PlaidCardFeedIcon.tsx @@ -1,6 +1,7 @@ import React, {useEffect, useState} from 'react'; -import {View} from 'react-native'; +import {ActivityIndicator, View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; +import useTheme from '@hooks/useTheme'; import useThemeIllustrations from '@hooks/useThemeIllustrations'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; @@ -18,8 +19,10 @@ function PlaidCardFeedIcon({plaidUrl, style, isLarge}: PlaidCardFeedIconProps) { const [isBrokenImage, setIsBrokenImage] = useState(false); const styles = useThemeStyles(); const illustrations = useThemeIllustrations(); + const theme = useTheme(); const width = isLarge ? variables.cardPreviewWidth : variables.cardIconWidth; const height = isLarge ? variables.cardPreviewHeight : variables.cardIconHeight; + const [loading, setLoading] = useState(true); useEffect(() => { if (!plaidUrl) { @@ -44,12 +47,22 @@ function PlaidCardFeedIcon({plaidUrl, style, isLarge}: PlaidCardFeedIconProps) { style={isLarge ? styles.plaidIcon : styles.plaidIconSmall} cachePolicy="memory-disk" onError={() => setIsBrokenImage(true)} + onLoadEnd={() => setLoading(false)} /> - + {loading ? ( + + + + ) : ( + + )} )} From 3aa0ef679d33644c8c0977a42b7d6449ef948e22 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Fri, 13 Jun 2025 15:36:50 +0300 Subject: [PATCH 13/14] add country to oauth connection --- src/libs/actions/getCompanyCardBankConnection/index.tsx | 6 ++++-- .../workspace/companyCards/BankConnection/index.native.tsx | 4 +++- src/pages/workspace/companyCards/BankConnection/index.tsx | 3 ++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/getCompanyCardBankConnection/index.tsx b/src/libs/actions/getCompanyCardBankConnection/index.tsx index 1deec2cd368d..a34865ce31f1 100644 --- a/src/libs/actions/getCompanyCardBankConnection/index.tsx +++ b/src/libs/actions/getCompanyCardBankConnection/index.tsx @@ -17,6 +17,7 @@ type CompanyCardPlaidConnection = { domainName: string; feedName: string; feed: string; + country: string; }; function getCompanyCardBankConnection(policyID?: string, bankName?: string) { @@ -47,8 +48,8 @@ function getCompanyCardBankConnection(policyID?: string, bankName?: string) { return `${commandURL}partners/banks/${bank}/oauth_callback.php?${new URLSearchParams(params).toString()}`; } -function getCompanyCardPlaidConnection(policyID?: string, publicToken?: string, feed?: string, feedName?: string) { - if (!policyID || !publicToken || !feed || !feedName) { +function getCompanyCardPlaidConnection(policyID?: string, publicToken?: string, feed?: string, feedName?: string, country?: string) { + if (!policyID || !publicToken || !feed || !feedName || !country) { return null; } const authToken = NetworkStore.getAuthToken(); @@ -57,6 +58,7 @@ function getCompanyCardPlaidConnection(policyID?: string, publicToken?: string, feed, feedName, publicToken, + country, domainName: PolicyUtils.getDomainNameForPolicy(policyID), }; diff --git a/src/pages/workspace/companyCards/BankConnection/index.native.tsx b/src/pages/workspace/companyCards/BankConnection/index.native.tsx index b6d0654ead22..f838acdf77c7 100644 --- a/src/pages/workspace/companyCards/BankConnection/index.native.tsx +++ b/src/pages/workspace/companyCards/BankConnection/index.native.tsx @@ -56,9 +56,11 @@ function BankConnection({policyID: policyIDFromProps, feed, route}: BankConnecti const plaidToken = addNewCard?.data?.publicToken ?? assignCard?.data?.plaidAccessToken; const plaidFeed = addNewCard?.data?.plaidConnectedFeed ?? assignCard?.data?.institutionId; const plaidFeedName = addNewCard?.data?.plaidConnectedFeedName ?? assignCard?.data?.plaidConnectedFeedName; + const country = addNewCard?.data?.selectedCountry; + const url = isBetaEnabled(CONST.BETAS.PLAID_COMPANY_CARDS) && plaidToken - ? getCompanyCardPlaidConnection(policyID, plaidToken, plaidFeed, plaidFeedName) + ? getCompanyCardPlaidConnection(policyID, plaidToken, plaidFeed, plaidFeedName, country) : getCompanyCardBankConnection(policyID, bankName); const [cardFeeds] = useCardFeeds(policyID); const [isConnectionCompleted, setConnectionCompleted] = useState(false); diff --git a/src/pages/workspace/companyCards/BankConnection/index.tsx b/src/pages/workspace/companyCards/BankConnection/index.tsx index 2fc80d2c1225..18faf890ecec 100644 --- a/src/pages/workspace/companyCards/BankConnection/index.tsx +++ b/src/pages/workspace/companyCards/BankConnection/index.tsx @@ -61,11 +61,12 @@ function BankConnection({policyID: policyIDFromProps, feed, route}: BankConnecti const plaidToken = addNewCard?.data?.publicToken ?? assignCard?.data?.plaidAccessToken; const plaidFeed = addNewCard?.data?.plaidConnectedFeed ?? assignCard?.data?.institutionId; const plaidFeedName = addNewCard?.data?.plaidConnectedFeedName ?? assignCard?.data?.plaidConnectedFeedName; + const country = addNewCard?.data?.selectedCountry; const {isBetaEnabled} = usePermissions(); const url = isBetaEnabled(CONST.BETAS.PLAID_COMPANY_CARDS) && plaidToken - ? getCompanyCardPlaidConnection(policyID, plaidToken, plaidFeed, plaidFeedName) + ? getCompanyCardPlaidConnection(policyID, plaidToken, plaidFeed, plaidFeedName, country) : getCompanyCardBankConnection(policyID, bankName); const isFeedExpired = feed ? isSelectedFeedExpired(cardFeeds?.settings?.oAuthAccountDetails?.[feed]) : false; const headerTitleAddCards = !backTo ? translate('workspace.companyCards.addCards') : undefined; From 8308f517d1f15a50b7a45aa270005a62ab26fac4 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Mon, 16 Jun 2025 09:34:29 +0300 Subject: [PATCH 14/14] update loader --- src/components/PlaidCardFeedIcon.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/PlaidCardFeedIcon.tsx b/src/components/PlaidCardFeedIcon.tsx index 407c43e895a0..058831594850 100644 --- a/src/components/PlaidCardFeedIcon.tsx +++ b/src/components/PlaidCardFeedIcon.tsx @@ -29,6 +29,7 @@ function PlaidCardFeedIcon({plaidUrl, style, isLarge}: PlaidCardFeedIconProps) { return; } setIsBrokenImage(false); + setLoading(true); }, [plaidUrl]); return (