From 5420e83538b359e83ef4106034c648d05c76b4d0 Mon Sep 17 00:00:00 2001 From: alberto Date: Mon, 26 Jan 2026 15:25:49 -0800 Subject: [PATCH 1/8] Properly display CSV personal cards --- src/CONST/index.ts | 7 +++++++ src/libs/CardUtils.ts | 2 +- src/pages/settings/Wallet/PaymentMethodList.tsx | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index ba2f5a4d872a..bd54370813b6 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -3455,6 +3455,13 @@ const CONST = { SMALL_NORMAL: 'small-normal', LARGE_NORMAL: 'large-normal', }, + + PERSONAL_CARD: { + BANK_NAME: { + CSV: 'upload', + }, + }, + COMPANY_CARD: { FEED_BANK_NAME: { MASTER_CARD: 'cdf', diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index ca1fad5c377d..fc8838100163 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -828,7 +828,7 @@ function getCompanyCardFeed(feedWithDomainID: string | undefined): CompanyCardFe * @returns true if the card is a personal card, false otherwise */ function isPersonalCard(card?: Card) { - return !!card?.fundID && card.fundID !== '0'; + return (!!card?.fundID && card.fundID !== '0') || (card?.bank === CONST.PERSONAL_CARD.BANK_NAME.CSV); } /** diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index 2ddcf6dd0788..1053266cb0d6 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -188,7 +188,7 @@ function PaymentMethodList({ if (shouldShowAssignedCards) { const assignedCards = Object.values(isLoadingCardList ? {} : (cardList ?? {})) // Filter by active cards associated with a domain - .filter((card) => !!card.domainName && CONST.EXPENSIFY_CARD.ACTIVE_STATES.includes(card.state ?? 0)); + .filter((card) => (!!card.domainName || card.bank === CONST.PERSONAL_CARD.BANK_NAME.CSV) && CONST.EXPENSIFY_CARD.ACTIVE_STATES.includes(card.state ?? 0)); const assignedCardsSorted = lodashSortBy(assignedCards, getAssignedCardSortKey); From 936ab76bfc267dd78e148411a6d3f4078899815c Mon Sep 17 00:00:00 2001 From: alberto Date: Mon, 26 Jan 2026 16:03:06 -0800 Subject: [PATCH 2/8] Display name for personal cards --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/pages/settings/Wallet/PaymentMethodList.tsx | 9 ++++++--- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index c5633771ff1a..c6b40783a547 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2162,6 +2162,7 @@ const translations = { suspiciousBannerTitle: 'Suspicious transaction', suspiciousBannerDescription: 'We noticed suspicious transactions on your card. Tap below to review.', cardLocked: "Your card is temporarily locked while our team reviews your company's account.", + csvCardDescription: "CSV Import", cardDetails: { cardNumber: 'Virtual card number', expiration: 'Expiration', diff --git a/src/languages/es.ts b/src/languages/es.ts index 0838c71d6a79..fec6cf23cca5 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1889,6 +1889,7 @@ const translations: TranslationDeepObject = { suspiciousBannerTitle: 'Transacción sospechosa', suspiciousBannerDescription: 'Hemos detectado una transacción sospechosa en la tarjeta. Haz click abajo para revisarla.', cardLocked: 'La tarjeta está temporalmente bloqueada mientras nuestro equipo revisa la cuenta de tu empresa.', + csvCardDescription: "Importación CSV", cardDetails: { cardNumber: 'Número de tarjeta virtual', expiration: 'Expiración', diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index 1053266cb0d6..e0da29f58802 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -220,13 +220,16 @@ function PaymentMethodList({ const pressHandler = onPress as CardPressHandler; const lastFourPAN = lastFourNumbersFromCardName(card.cardName); const plaidUrl = getPlaidInstitutionIconUrl(card.bank); + const isCSVImportCard = card.bank === CONST.COMPANY_CARDS.BANK_NAME.UPLOAD; assignedCardsGrouped.push({ key: card.cardID.toString(), plaidUrl, title: maskCardNumber(card.cardName, card.bank), - description: lastFourPAN - ? `${lastFourPAN} ${CONST.DOT_SEPARATOR} ${getDescriptionForPolicyDomainCard(card.domainName)}` - : getDescriptionForPolicyDomainCard(card.domainName), + description: isCSVImportCard + ? translate('cardPage.csvCardDescription') + : lastFourPAN + ? `${lastFourPAN} ${CONST.DOT_SEPARATOR} ${getDescriptionForPolicyDomainCard(card.domainName)}` + : getDescriptionForPolicyDomainCard(card.domainName), interactive: !isDisabled, disabled: isDisabled, shouldShowRightIcon, From 284818c5201daa10faad886c78e45cc45148c1d4 Mon Sep 17 00:00:00 2001 From: alberto Date: Mon, 26 Jan 2026 16:03:16 -0800 Subject: [PATCH 3/8] and for company cards --- .../WorkspaceCompanyCardsTableHeaderButtons.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/WorkspaceCompanyCardsTableHeaderButtons.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/WorkspaceCompanyCardsTableHeaderButtons.tsx index a3efa4417519..ddde5e92d8f3 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/WorkspaceCompanyCardsTableHeaderButtons.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/WorkspaceCompanyCardsTableHeaderButtons.tsx @@ -105,10 +105,11 @@ function WorkspaceCompanyCardsTableHeaderButtons({policyID, feedName, isLoading, }, ]; + const isCsvFeed = feedName?.includes(CONST.COMPANY_CARD.FEED_BANK_NAME.CSV); const firstPart = translate(isCommercialFeed ? 'workspace.companyCards.commercialFeed' : 'workspace.companyCards.directFeed'); const domainName = domain?.email ? Str.extractEmailDomain(domain.email) : undefined; const secondPart = ` (${domainName ?? policy?.name})`; - const supportingText = `${firstPart}${secondPart}`; + const supportingText = isCsvFeed ? translate('cardPage.csvCardDescription') : `${firstPart}${secondPart}`; const shouldShowNarrowLayout = shouldUseNarrowLayout || isMediumScreenWidth; From bce0ce0daa9b93ec54680e2fed6f97f7c385f0c0 Mon Sep 17 00:00:00 2001 From: alberto Date: Mon, 26 Jan 2026 16:18:59 -0800 Subject: [PATCH 4/8] automated translations --- src/languages/de.ts | 1 + src/languages/fr.ts | 1 + src/languages/it.ts | 1 + src/languages/ja.ts | 1 + src/languages/nl.ts | 1 + src/languages/pl.ts | 1 + src/languages/pt-BR.ts | 1 + src/languages/zh-hans.ts | 1 + 8 files changed, 8 insertions(+) diff --git a/src/languages/de.ts b/src/languages/de.ts index c2fad2c1d6bc..6089ded52cc8 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -2210,6 +2210,7 @@ const translations: TranslationDeepObject = { ${amount} für ${merchant} – ${date}`, }, + csvCardDescription: 'CSV-Import', }, workflowsPage: { workflowTitle: 'Ausgabe', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 6a5c7bb3fb6f..46a922845205 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -2214,6 +2214,7 @@ const translations: TranslationDeepObject = { ${amount} pour ${merchant} - ${date}`, }, + csvCardDescription: 'Importation CSV', }, workflowsPage: { workflowTitle: 'Dépenses', diff --git a/src/languages/it.ts b/src/languages/it.ts index 18d15db14ba3..307476db5c56 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -2206,6 +2206,7 @@ const translations: TranslationDeepObject = { ${amount} per ${merchant} - ${date}`, }, + csvCardDescription: 'Importazione CSV', }, workflowsPage: { workflowTitle: 'Spese', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 03405dbb2bf8..bee8e4244c38 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -2197,6 +2197,7 @@ const translations: TranslationDeepObject = { ${merchant} への ${amount}(${date})`, }, + csvCardDescription: 'CSVインポート', }, workflowsPage: { workflowTitle: '支出', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 21ea94899f81..852f40d3fa03 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -2205,6 +2205,7 @@ const translations: TranslationDeepObject = { ${amount} voor ${merchant} - ${date}`, }, + csvCardDescription: 'CSV-import', }, workflowsPage: { workflowTitle: 'Uitgaven', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 9321009dd6a8..ba62819f1980 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -2200,6 +2200,7 @@ const translations: TranslationDeepObject = { ${amount} dla ${merchant} - ${date}`, }, + csvCardDescription: 'Import CSV', }, workflowsPage: { workflowTitle: 'Wydatki', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index f9b0df02834a..f1b7bf77a99b 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -2198,6 +2198,7 @@ const translations: TranslationDeepObject = { ${amount} para ${merchant} - ${date}`, }, + csvCardDescription: 'Importar CSV', }, workflowsPage: { workflowTitle: 'Gastos', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 61517c31c488..152e6c06d327 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -2170,6 +2170,7 @@ const translations: TranslationDeepObject = { ${amount},商户:${merchant} - ${date}`, }, + csvCardDescription: 'CSV 导入', }, workflowsPage: { workflowTitle: '支出', From 33a0d5783404ccc20e75b5683329819d2728caea Mon Sep 17 00:00:00 2001 From: alberto Date: Mon, 26 Jan 2026 16:20:25 -0800 Subject: [PATCH 5/8] prettier --- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- src/libs/CardUtils.ts | 2 +- src/pages/settings/Wallet/PaymentMethodList.tsx | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index c6b40783a547..ff07cfac4651 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2162,7 +2162,7 @@ const translations = { suspiciousBannerTitle: 'Suspicious transaction', suspiciousBannerDescription: 'We noticed suspicious transactions on your card. Tap below to review.', cardLocked: "Your card is temporarily locked while our team reviews your company's account.", - csvCardDescription: "CSV Import", + csvCardDescription: 'CSV Import', cardDetails: { cardNumber: 'Virtual card number', expiration: 'Expiration', diff --git a/src/languages/es.ts b/src/languages/es.ts index fec6cf23cca5..51806997d5be 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1889,7 +1889,7 @@ const translations: TranslationDeepObject = { suspiciousBannerTitle: 'Transacción sospechosa', suspiciousBannerDescription: 'Hemos detectado una transacción sospechosa en la tarjeta. Haz click abajo para revisarla.', cardLocked: 'La tarjeta está temporalmente bloqueada mientras nuestro equipo revisa la cuenta de tu empresa.', - csvCardDescription: "Importación CSV", + csvCardDescription: 'Importación CSV', cardDetails: { cardNumber: 'Número de tarjeta virtual', expiration: 'Expiración', diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index fc8838100163..9ac11c255baa 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -828,7 +828,7 @@ function getCompanyCardFeed(feedWithDomainID: string | undefined): CompanyCardFe * @returns true if the card is a personal card, false otherwise */ function isPersonalCard(card?: Card) { - return (!!card?.fundID && card.fundID !== '0') || (card?.bank === CONST.PERSONAL_CARD.BANK_NAME.CSV); + return (!!card?.fundID && card.fundID !== '0') || card?.bank === CONST.PERSONAL_CARD.BANK_NAME.CSV; } /** diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index e0da29f58802..4d5b37ec34d6 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -228,8 +228,8 @@ function PaymentMethodList({ description: isCSVImportCard ? translate('cardPage.csvCardDescription') : lastFourPAN - ? `${lastFourPAN} ${CONST.DOT_SEPARATOR} ${getDescriptionForPolicyDomainCard(card.domainName)}` - : getDescriptionForPolicyDomainCard(card.domainName), + ? `${lastFourPAN} ${CONST.DOT_SEPARATOR} ${getDescriptionForPolicyDomainCard(card.domainName)}` + : getDescriptionForPolicyDomainCard(card.domainName), interactive: !isDisabled, disabled: isDisabled, shouldShowRightIcon, From 16724573177c3413edbc3f50354966d5df67d7de Mon Sep 17 00:00:00 2001 From: alberto Date: Mon, 26 Jan 2026 16:24:44 -0800 Subject: [PATCH 6/8] lint --- src/pages/settings/Wallet/PaymentMethodList.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index 4d5b37ec34d6..6e5d7a53beae 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -221,15 +221,14 @@ function PaymentMethodList({ const lastFourPAN = lastFourNumbersFromCardName(card.cardName); const plaidUrl = getPlaidInstitutionIconUrl(card.bank); const isCSVImportCard = card.bank === CONST.COMPANY_CARDS.BANK_NAME.UPLOAD; + const domainCardDescription = lastFourPAN + ? `${lastFourPAN} ${CONST.DOT_SEPARATOR} ${getDescriptionForPolicyDomainCard(card.domainName)}` + : getDescriptionForPolicyDomainCard(card.domainName); assignedCardsGrouped.push({ key: card.cardID.toString(), plaidUrl, title: maskCardNumber(card.cardName, card.bank), - description: isCSVImportCard - ? translate('cardPage.csvCardDescription') - : lastFourPAN - ? `${lastFourPAN} ${CONST.DOT_SEPARATOR} ${getDescriptionForPolicyDomainCard(card.domainName)}` - : getDescriptionForPolicyDomainCard(card.domainName), + description: isCSVImportCard ? translate('cardPage.csvCardDescription') : domainCardDescription, interactive: !isDisabled, disabled: isDisabled, shouldShowRightIcon, From 1594b0a3461dd23856e60644b3619ffa97717e60 Mon Sep 17 00:00:00 2001 From: alberto Date: Wed, 28 Jan 2026 09:51:00 -0800 Subject: [PATCH 7/8] slightly better performance --- src/pages/settings/Wallet/PaymentMethodList.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index e1c317760ac0..ecb042f476b7 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -221,9 +221,12 @@ function PaymentMethodList({ const lastFourPAN = lastFourNumbersFromCardName(card.cardName); const plaidUrl = getPlaidInstitutionIconUrl(card.bank); const isCSVImportCard = card.bank === CONST.COMPANY_CARDS.BANK_NAME.UPLOAD; - const domainCardDescription = lastFourPAN - ? `${lastFourPAN} ${CONST.DOT_SEPARATOR} ${getDescriptionForPolicyDomainCard(card.domainName)}` - : getDescriptionForPolicyDomainCard(card.domainName); + let domainCardDescription = translate('cardPage.csvCardDescription'); + if (!isCSVImportCard) { + domainCardDescription = lastFourPAN + ? `${lastFourPAN} ${CONST.DOT_SEPARATOR} ${getDescriptionForPolicyDomainCard(card.domainName)}` + : getDescriptionForPolicyDomainCard(card.domainName); + } assignedCardsGrouped.push({ key: card.cardID.toString(), plaidUrl, From 42deb392af109ef62f91148b0c4b3c550dca5973 Mon Sep 17 00:00:00 2001 From: alberto Date: Wed, 28 Jan 2026 12:23:50 -0800 Subject: [PATCH 8/8] FIx name formatting --- src/libs/CardUtils.ts | 6 ++++++ tests/unit/CardUtilsTest.ts | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 950119043a31..8ae5d2303a59 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -234,6 +234,12 @@ function maskCardNumber(cardName?: string, feed?: string, showOriginalName?: boo if (!cardName || cardName === '') { return ''; } + + // CSV imported cards use user-provided display names, not card numbers - return as-is + if (feed === CONST.COMPANY_CARDS.BANK_NAME.UPLOAD) { + return cardName; + } + const hasSpace = /\s/.test(cardName); const maskedString = cardName.replaceAll('X', '•'); const isAmexBank = [CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX, CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX_DIRECT].some((value) => value === feed); diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index 421aaa6abdfe..27e18e6a75c5 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -638,6 +638,16 @@ describe('CardUtils', () => { const maskedCardNumber = maskCardNumber('Business Card Cash - 3001', undefined); expect(maskedCardNumber).toBe('Business Card Cash'); }); + + it('Should return CSV import card display name without 4-character formatting', () => { + const maskedCardNumber = maskCardNumber('Checking', CONST.COMPANY_CARDS.BANK_NAME.UPLOAD); + expect(maskedCardNumber).toBe('Checking'); + }); + + it('Should return CSV import card display name as-is for longer names', () => { + const maskedCardNumber = maskCardNumber('JustChecking', CONST.COMPANY_CARDS.BANK_NAME.UPLOAD); + expect(maskedCardNumber).toBe('JustChecking'); + }); }); describe('getCardFeedName', () => {