Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2250,6 +2250,7 @@ const translations: TranslationDeepObject<typeof en> = {

${amount} für ${merchant} – ${date}`,
},
csvCardDescription: 'CSV-Import',
},
workflowsPage: {
workflowTitle: 'Ausgabe',
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1937,6 +1937,7 @@ const translations: TranslationDeepObject<typeof en> = {
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',
Expand Down
1 change: 1 addition & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2255,6 +2255,7 @@ const translations: TranslationDeepObject<typeof en> = {

${amount} pour ${merchant} - ${date}`,
},
csvCardDescription: 'Importation CSV',
},
workflowsPage: {
workflowTitle: 'Dépenses',
Expand Down
1 change: 1 addition & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2246,6 +2246,7 @@ const translations: TranslationDeepObject<typeof en> = {

${amount} per ${merchant} - ${date}`,
},
csvCardDescription: 'Importazione CSV',
},
workflowsPage: {
workflowTitle: 'Spese',
Expand Down
1 change: 1 addition & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2236,6 +2236,7 @@ const translations: TranslationDeepObject<typeof en> = {

${merchant} への ${amount}(${date})`,
},
csvCardDescription: 'CSVインポート',
},
workflowsPage: {
workflowTitle: '支出',
Expand Down
1 change: 1 addition & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2245,6 +2245,7 @@ const translations: TranslationDeepObject<typeof en> = {

${amount} voor ${merchant} - ${date}`,
},
csvCardDescription: 'CSV-import',
},
workflowsPage: {
workflowTitle: 'Uitgaven',
Expand Down
1 change: 1 addition & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2241,6 +2241,7 @@ const translations: TranslationDeepObject<typeof en> = {

${amount} dla ${merchant} - ${date}`,
},
csvCardDescription: 'Import CSV',
},
workflowsPage: {
workflowTitle: 'Wydatki',
Expand Down
1 change: 1 addition & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2238,6 +2238,7 @@ const translations: TranslationDeepObject<typeof en> = {

${amount} para ${merchant} - ${date}`,
},
csvCardDescription: 'Importar CSV',
},
workflowsPage: {
workflowTitle: 'Gastos',
Expand Down
1 change: 1 addition & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2209,6 +2209,7 @@ const translations: TranslationDeepObject<typeof en> = {

${amount},商户:${merchant} - ${date}`,
},
csvCardDescription: 'CSV 导入',
},
workflowsPage: {
workflowTitle: '支出',
Expand Down
8 changes: 7 additions & 1 deletion src/libs/CardUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a different name than 'upload' for the company CSV bank name? Else, this can misclassify company CSV cards as personal.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checked that CONST.COMPANY_CARDS.BANK_NAME.UPLOAD is also 'upload'. So this will be a problem? Unless we add another condition here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries. "Source" Company cards don't live in cardList, and assigned company cards should display and are identified as PersonalCards through the fundID, so all good

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Gonals Coming from this PR, isPersonalCard is used to filter-out(remove) the personal cards (that has fundID = '0') from cardList as mentioned here and here. Not quite sure of the context here but is it intentional to remove also cards that has bank value set to upload?

}

type SplitMaskedCardNumberResult = {
Expand Down
13 changes: 9 additions & 4 deletions src/pages/settings/Wallet/PaymentMethodList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Comment thread
Gonals marked this conversation as resolved.
Comment thread
Gonals marked this conversation as resolved.
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
10 changes: 10 additions & 0 deletions tests/unit/CardUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Loading