diff --git a/src/CONST.ts b/src/CONST.ts index 84003710938a..c51864208eca 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2529,6 +2529,13 @@ const CONST = { VISA: 'vcf', AMEX: 'gl1025', STRIPE: 'stripe', + CITIBANK: 'oauth.citibank.com', + CAPITAL_ONE: 'oauth.capitalone.com', + BANK_OF_AMERICA: 'oauth.bankofamerica.com', + CHASE: 'oauth.chase.com', + BREX: 'oauth.brex.com', + WELLS_FARGO: 'oauth.wellsfargo.com', + AMEX_DIRECT: 'oauth.americanexpressfdx.com', }, STEP_NAMES: ['1', '2', '3', '4'], STEP: { diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 7c81c5c224c6..9fda616557a8 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -1,6 +1,6 @@ import groupBy from 'lodash/groupBy'; import Onyx from 'react-native-onyx'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import ExpensifyCardImage from '@assets/images/expensify-card.svg'; import * as Illustrations from '@src/components/Icon/Illustrations'; @@ -9,7 +9,6 @@ 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 Policy from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; import localeCompare from './LocaleCompare'; @@ -193,13 +192,35 @@ function getCompanyCardNumber(cardList: Record, lastFourPAN?: st return Object.keys(cardList).find((card) => card.endsWith(lastFourPAN)) ?? ''; } -function getCardFeedIcon(cardFeed: string): IconAsset { - if (cardFeed.startsWith(CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD)) { - return Illustrations.MasterCardCompanyCards; +function getCardFeedIcon(cardFeed: CompanyCardFeed | typeof CONST.EXPENSIFY_CARD.BANK): IconAsset { + const feedIcons = { + [CONST.COMPANY_CARD.FEED_BANK_NAME.VISA]: Illustrations.VisaCompanyCardDetail, + [CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX]: Illustrations.AmexCardCompanyCardDetail, + [CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD]: Illustrations.MasterCardCompanyCardDetail, + [CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX_DIRECT]: Illustrations.AmexCardCompanyCardDetail, + [CONST.COMPANY_CARD.FEED_BANK_NAME.BANK_OF_AMERICA]: Illustrations.BankOfAmericaCompanyCardDetail, + [CONST.COMPANY_CARD.FEED_BANK_NAME.CAPITAL_ONE]: Illustrations.CapitalOneCompanyCardDetail, + [CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE]: Illustrations.ChaseCompanyCardDetail, + [CONST.COMPANY_CARD.FEED_BANK_NAME.CITIBANK]: Illustrations.CitibankCompanyCardDetail, + [CONST.COMPANY_CARD.FEED_BANK_NAME.WELLS_FARGO]: Illustrations.WellsFargoCompanyCardDetail, + [CONST.COMPANY_CARD.FEED_BANK_NAME.BREX]: Illustrations.BrexCompanyCardDetail, + [CONST.COMPANY_CARD.FEED_BANK_NAME.STRIPE]: Illustrations.StripeCompanyCardDetail, + [CONST.EXPENSIFY_CARD.BANK]: ExpensifyCardImage, + }; + + if (cardFeed.startsWith(CONST.EXPENSIFY_CARD.BANK)) { + return ExpensifyCardImage; + } + + if (feedIcons[cardFeed]) { + return feedIcons[cardFeed]; } - if (cardFeed.startsWith(CONST.COMPANY_CARD.FEED_BANK_NAME.VISA)) { - return Illustrations.VisaCompanyCards; + // In existing OldDot setups other variations of feeds could exist, ex: vcf2, vcf3, cdfbmo + const feedKey = (Object.keys(feedIcons) as CompanyCardFeed[]).find((feed) => cardFeed.startsWith(feed)); + + if (feedKey) { + return feedIcons[feedKey]; } return Illustrations.AmexCompanyCards; @@ -211,46 +232,18 @@ function getCardFeedName(feedType: CompanyCardFeed): string { [CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD]: 'Mastercard', [CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX]: 'American Express', [CONST.COMPANY_CARD.FEED_BANK_NAME.STRIPE]: 'Stripe', + [CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX_DIRECT]: 'American Express', + [CONST.COMPANY_CARD.FEED_BANK_NAME.BANK_OF_AMERICA]: 'Bank of America', + [CONST.COMPANY_CARD.FEED_BANK_NAME.CAPITAL_ONE]: 'Capital One', + [CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE]: 'Chase', + [CONST.COMPANY_CARD.FEED_BANK_NAME.CITIBANK]: 'Citibank', + [CONST.COMPANY_CARD.FEED_BANK_NAME.WELLS_FARGO]: 'Wells Fargo', + [CONST.COMPANY_CARD.FEED_BANK_NAME.BREX]: 'Brex', }; return feedNamesMapping[feedType]; } -function getCardDetailsImage(cardFeed: string): IconAsset { - if (cardFeed.startsWith(CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD)) { - return Illustrations.MasterCardCompanyCardDetail; - } - - if (cardFeed.startsWith(CONST.COMPANY_CARD.FEED_BANK_NAME.VISA)) { - return Illustrations.VisaCompanyCardDetail; - } - - if (cardFeed.startsWith(CONST.EXPENSIFY_CARD.BANK)) { - return ExpensifyCardImage; - } - - return Illustrations.AmexCardCompanyCardDetail; -} - -function getMemberCards(policy: OnyxEntry, allCardsList: OnyxCollection, accountID?: number) { - const workspaceId = policy?.workspaceAccountID ? policy.workspaceAccountID.toString() : ''; - const cards: WorkspaceCardsList = {}; - Object.keys(allCardsList ?? {}) - .filter((key) => key !== `${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceId}_${CONST.EXPENSIFY_CARD.BANK}` && key.includes(workspaceId)) - .forEach((key) => { - const feedCards = allCardsList?.[key]; - if (feedCards && Object.keys(feedCards).length > 0) { - Object.keys(feedCards).forEach((feedCardKey) => { - if (feedCards?.[feedCardKey].accountID !== accountID) { - return; - } - cards[feedCardKey] = feedCards[feedCardKey]; - }); - } - }); - return cards; -} - const getBankCardDetailsImage = (bank: ValueOf): IconAsset => { const iconMap: Record, IconAsset> = { [CONST.COMPANY_CARDS.BANKS.AMEX]: Illustrations.AmexCardCompanyCardDetail, @@ -322,8 +315,6 @@ export { getCompanyCardNumber, getCardFeedIcon, getCardFeedName, - getCardDetailsImage, - getMemberCards, getBankCardDetailsImage, getSelectedFeed, getCorrectStepForSelectedBank, diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx index 726001e80146..e6036da6dc20 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx @@ -25,12 +25,15 @@ import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import {getConnectedIntegration} from '@libs/PolicyUtils'; import Navigation from '@navigation/Navigation'; +import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; +import type {CompanyCardFeed} from '@src/types/onyx'; +import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import {getExportMenuItem} from './utils'; type WorkspaceCompanyCardDetailsPageProps = StackScreenProps; @@ -49,9 +52,10 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag const connectedIntegration = getConnectedIntegration(policy, accountingIntegrations) ?? connectionSyncProgress?.connectionName; const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); - const [allBankCards] = useOnyx(`${ONYXKEYS.CARD_LIST}`); + const [allBankCards, allBankCardsMetadata] = useOnyx(`${ONYXKEYS.CARD_LIST}`); const card = allBankCards?.[cardID]; + const cardBank = card?.bank ?? ''; const cardholder = personalDetails?.[card?.accountID ?? -1]; const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(cardholder); const exportMenuItem = getExportMenuItem(connectedIntegration, policyID, translate, policy, card); @@ -66,6 +70,10 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag Policy.updateWorkspaceCompanyCard(workspaceAccountID, cardID, bank); }; + if (!card && !isLoadingOnyxValue(allBankCardsMetadata)) { + return ; + } + return ( ({ - value: feed as CompanyCardFeed, - text: cardFeeds?.settings?.companyCardNicknames?.[feed] ?? translate(`workspace.companyCards.addNewCard.cardProviders.${feed as CompanyCardFeed}`), + const feeds: CardFeedListItem[] = (Object.keys(cardFeeds?.settings?.companyCards ?? {}) as CompanyCardFeed[]).map((feed) => ({ + value: feed, + text: cardFeeds?.settings?.companyCardNicknames?.[feed] ?? CardUtils.getCardFeedName(feed), keyForList: feed, isSelected: feed === selectedFeed, brickRoadIndicator: cardFeeds?.settings?.companyCards?.[feed]?.errors ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx index 74e6593d6986..efd95ecb8980 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx @@ -16,6 +16,7 @@ import * as CardUtils from '@libs/CardUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import Navigation from '@navigation/Navigation'; import variables from '@styles/variables'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {CompanyCardFeed} from '@src/types/onyx'; @@ -36,7 +37,9 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed}: Worksp const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policyID); const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`); const shouldChangeLayout = isMediumScreenWidth || shouldUseNarrowLayout; - const feedName = cardFeeds?.settings?.companyCardNicknames?.[selectedFeed] ?? translate(`workspace.companyCards.addNewCard.cardProviders.${selectedFeed}`); + const feedName = cardFeeds?.settings?.companyCardNicknames?.[selectedFeed] ?? CardUtils.getCardFeedName(selectedFeed); + const isCustomFeed = + CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD === selectedFeed || CONST.COMPANY_CARD.FEED_BANK_NAME.VISA === selectedFeed || CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX === selectedFeed; return ( )} - {translate('workspace.companyCards.customFeed')} + {translate(isCustomFeed ? 'workspace.companyCards.customFeed' : 'workspace.companyCards.directFeed')} diff --git a/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx b/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx index b2f5f3f0c321..03dd2e9e63cd 100644 --- a/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx +++ b/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx @@ -19,12 +19,12 @@ import variables from '@styles/variables'; import * as CompanyCards from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {CompanyCardFeed} from '@src/types/onyx'; +import type {CardFeedProvider} from '@src/types/onyx/CardFeeds'; type AvailableCompanyCardTypes = { isAmexAvailable?: boolean; translate: LocaleContextProps['translate']; - typeSelected?: CompanyCardFeed; + typeSelected?: CardFeedProvider; styles: StyleProp; }; @@ -87,7 +87,7 @@ function CardTypeStep() { const {translate} = useLocalize(); const styles = useThemeStyles(); const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD); - const [typeSelected, setTypeSelected] = useState(); + const [typeSelected, setTypeSelected] = useState(); const {canUseDirectFeeds} = usePermissions(); const [isError, setIsError] = useState(false); const data = getAvailableCompanyCardTypes({isAmexAvailable: !canUseDirectFeeds, translate, typeSelected, styles: styles.mr3}); diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index 1ff46ddd8d38..822bef628799 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -39,7 +39,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {Card as MemberCard, PersonalDetails, PersonalDetailsList} from '@src/types/onyx'; +import type {CompanyCardFeed, Card as MemberCard, PersonalDetails, PersonalDetailsList} from '@src/types/onyx'; import type {ListItemType} from './WorkspaceMemberDetailsRoleSelectionModal'; import WorkspaceMemberDetailsRoleSelectionModal from './WorkspaceMemberDetailsRoleSelectionModal'; @@ -312,7 +312,7 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM ? CurrencyUtils.convertToDisplayString(memberCard.nameValuePairs?.unapprovedExpenseLimit) : '' } - icon={CardUtils.getCardDetailsImage(memberCard?.bank ?? '')} + icon={CardUtils.getCardFeedIcon(memberCard.bank as CompanyCardFeed)} displayInDefaultIconColor iconStyles={styles.cardIcon} contentFit="contain" diff --git a/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx b/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx index 390449cd8896..24a2b12cb4d0 100644 --- a/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx @@ -78,9 +78,9 @@ function WorkspaceMemberNewCardPage({route, personalDetails}: WorkspaceMemberNew setShouldShowError(false); }; - const companyCardFeeds: CardFeedListItem[] = Object.keys(cardFeeds?.settings?.companyCards ?? {}).map((key) => ({ + const companyCardFeeds: CardFeedListItem[] = (Object.keys(cardFeeds?.settings?.companyCards ?? {}) as CompanyCardFeed[]).map((key) => ({ value: key, - text: cardFeeds?.settings?.companyCardNicknames?.[key] ?? translate(`workspace.companyCards.addNewCard.cardProviders.${key as CompanyCardFeed}`), + text: cardFeeds?.settings?.companyCardNicknames?.[key] ?? CardUtils.getCardFeedName(key), keyForList: key, isSelected: selectedFeed === key, leftElement: ( diff --git a/src/types/onyx/CardFeeds.ts b/src/types/onyx/CardFeeds.ts index 4a3eb5632d83..10938f710b5c 100644 --- a/src/types/onyx/CardFeeds.ts +++ b/src/types/onyx/CardFeeds.ts @@ -2,9 +2,16 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type * as OnyxCommon from './OnyxCommon'; -/** Card feed provider */ +/** Card feed */ type CompanyCardFeed = ValueOf; +/** Card feed provider */ +type CardFeedProvider = + | typeof CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD + | typeof CONST.COMPANY_CARD.FEED_BANK_NAME.VISA + | typeof CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX + | typeof CONST.COMPANY_CARD.FEED_BANK_NAME.STRIPE; + /** Card feed data */ type CardFeedData = { /** Whether any actions are pending */ @@ -50,7 +57,7 @@ type CardFeeds = { /** Data required to be sent to add a new card */ type AddNewCardFeedData = { /** Card feed provider */ - feedType: CompanyCardFeed; + feedType: CardFeedProvider; /** Name of the card */ cardTitle: string; @@ -84,4 +91,4 @@ type AddNewCompanyCardFeed = { }; export default CardFeeds; -export type {AddNewCardFeedStep, AddNewCompanyCardFeed, AddNewCardFeedData, CardFeedData, CompanyCardFeed}; +export type {AddNewCardFeedStep, AddNewCompanyCardFeed, AddNewCardFeedData, CardFeedData, CompanyCardFeed, CardFeedProvider};