diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 411ce7eb0c68..94ac2aba413a 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -3487,6 +3487,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/languages/de.ts b/src/languages/de.ts index cb8825895951..955bd31f34c7 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -2250,6 +2250,7 @@ const translations: TranslationDeepObject = { ${amount} für ${merchant} – ${date}`, }, + csvCardDescription: 'CSV-Import', }, workflowsPage: { workflowTitle: 'Ausgabe', diff --git a/src/languages/en.ts b/src/languages/en.ts index 4a583cc1c0c0..ae45c44d368e 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2208,6 +2208,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 01c42783ffc0..6cf5861de1da 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1937,6 +1937,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/languages/fr.ts b/src/languages/fr.ts index 37cbe700d9c7..f1c735fe33b8 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -2255,6 +2255,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 ed81344a9314..01a1f35c5d32 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -2246,6 +2246,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 53da5dd99c36..a69874905092 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -2236,6 +2236,7 @@ const translations: TranslationDeepObject = { ${merchant} への ${amount}(${date})`, }, + csvCardDescription: 'CSVインポート', }, workflowsPage: { workflowTitle: '支出', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 6841f8dbb484..beb3b13d8bd5 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -2245,6 +2245,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 4f135c5c7a0f..c6486078eac0 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -2241,6 +2241,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 f1d837ef5707..d8f9b2d3606f 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -2238,6 +2238,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 0203fdfc7509..18747e3bd205 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -2209,6 +2209,7 @@ const translations: TranslationDeepObject = { ${amount},商户:${merchant} - ${date}`, }, + csvCardDescription: 'CSV 导入', }, workflowsPage: { workflowTitle: '支出', diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 1fb4da2007b2..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); @@ -828,7 +834,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; } type SplitMaskedCardNumberResult = { diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index 0eb1b3b2ba56..ecb042f476b7 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); @@ -220,13 +220,18 @@ 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; + 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, title: maskCardNumber(card.cardName, card.bank), - description: lastFourPAN - ? `${lastFourPAN} ${CONST.DOT_SEPARATOR} ${getDescriptionForPolicyDomainCard(card.domainName)}` - : getDescriptionForPolicyDomainCard(card.domainName), + description: isCSVImportCard ? translate('cardPage.csvCardDescription') : domainCardDescription, interactive: !isDisabled, disabled: isDisabled, shouldShowRightIcon, 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; 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', () => {