Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
edf7692
Add correct name and image to Search card filter
narefyev91 Jun 23, 2025
dab0e2b
Merge branch 'refs/heads/main' into more-plaid-company-card-follow-ups
narefyev91 Jun 25, 2025
e739213
fix search based on feed
narefyev91 Jun 25, 2025
52f174f
fix border for images
narefyev91 Jun 25, 2025
09ca06e
Merge branch 'refs/heads/main' into more-plaid-company-card-follow-ups
narefyev91 Jun 26, 2025
192b342
fix visibility of the very small cards
narefyev91 Jun 26, 2025
9dae27b
add card name in reports
narefyev91 Jun 30, 2025
46cf2b0
Merge branch 'refs/heads/main' into more-plaid-company-card-follow-ups
narefyev91 Jun 30, 2025
b54821f
rollback card name
narefyev91 Jul 1, 2025
604ea21
Merge branch 'refs/heads/main' into more-plaid-company-card-follow-ups
narefyev91 Jul 2, 2025
eea47c0
Merge branch 'refs/heads/main' into more-plaid-company-card-follow-ups
narefyev91 Jul 3, 2025
de3ba9a
filters for removed feed
narefyev91 Jul 3, 2025
a1911b2
Merge branch 'refs/heads/main' into more-plaid-company-card-follow-ups
narefyev91 Jul 7, 2025
a46274a
filters for remove card
narefyev91 Jul 7, 2025
aa5d013
Merge branch 'refs/heads/main' into more-plaid-company-card-follow-ups
narefyev91 Jul 8, 2025
cb6809e
fix merge conflicts
narefyev91 Jul 8, 2025
235396f
fix broken feeds
narefyev91 Jul 8, 2025
a47e9ce
clean up
narefyev91 Jul 8, 2025
7dad697
fix navigation back from commercial feed
narefyev91 Jul 8, 2025
ddb94a5
fix navigation back from commercial feed
narefyev91 Jul 8, 2025
5c0bed6
Merge branch 'refs/heads/main' into more-plaid-company-card-follow-ups
narefyev91 Jul 10, 2025
085d974
add plaid import accounts when feed broken connection
narefyev91 Jul 10, 2025
c5eebd5
Merge branch 'refs/heads/main' into more-plaid-company-card-follow-ups
narefyev91 Jul 10, 2025
6ec74e3
Merge branch 'main' into more-plaid-company-card-follow-ups
koko57 Jul 11, 2025
9ba7cfa
Merge branch 'main' into more-plaid-company-card-follow-ups
koko57 Jul 15, 2025
95ea3bb
fix: show correct feed name in search query
koko57 Jul 16, 2025
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
2 changes: 1 addition & 1 deletion assets/images/companyCards/card-plaid.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion assets/images/companyCards/large/card-plaid-large.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 15 additions & 9 deletions src/components/PlaidCardFeedIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,21 @@ type PlaidCardFeedIconProps = {
plaidUrl: string;
style?: StyleProp<ViewStyle>;
isLarge?: boolean;
isSmall?: boolean;
};

function PlaidCardFeedIcon({plaidUrl, style, isLarge}: PlaidCardFeedIconProps) {
function PlaidCardFeedIcon({plaidUrl, style, isLarge, isSmall}: PlaidCardFeedIconProps) {
const [isBrokenImage, setIsBrokenImage] = useState<boolean>(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<boolean>(true);
const plaidImageStyle = isLarge ? styles.plaidIcon : styles.plaidIconSmall;
const iconWidth = isSmall ? variables.cardMiniatureWidth : width;
const iconHeight = isSmall ? variables.cardMiniatureHeight : height;
const plaidLoadedStyle = isSmall ? styles.plaidIconExtraSmall : plaidImageStyle;

useEffect(() => {
if (!plaidUrl) {
Expand All @@ -37,31 +42,32 @@ function PlaidCardFeedIcon({plaidUrl, style, isLarge}: PlaidCardFeedIconProps) {
{isBrokenImage ? (
<Icon
src={illustrations.GenericCompanyCardLarge}
height={height}
width={width}
additionalStyles={styles.cardIcon}
height={iconHeight}
width={iconWidth}
additionalStyles={isSmall ? styles.cardMiniature : styles.cardIcon}
/>
) : (
<>
<Image
source={{uri: plaidUrl}}
style={isLarge ? styles.plaidIcon : styles.plaidIconSmall}
style={plaidLoadedStyle}
cachePolicy="memory-disk"
onError={() => setIsBrokenImage(true)}
onLoadEnd={() => setLoading(false)}
/>
{loading ? (
<View style={[styles.justifyContentCenter, {width, height}]}>
<View style={[styles.justifyContentCenter, {width: iconWidth, height: iconHeight}]}>
<ActivityIndicator
color={theme.spinner}
size={20}
size={isSmall ? 10 : 20}
/>
</View>
) : (
<Icon
src={isLarge ? Illustrations.PlaidCompanyCardDetailLarge : Illustrations.PlaidCompanyCardDetail}
height={height}
width={width}
height={iconHeight}
width={iconWidth}
additionalStyles={isSmall && styles.cardMiniature}
/>
)}
</>
Expand Down
48 changes: 35 additions & 13 deletions src/components/SelectionList/Search/CardListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Avatar from '@components/Avatar';
import Checkbox from '@components/Checkbox';
import Icon from '@components/Icon';
import {FallbackAvatar} from '@components/Icon/Expensicons';
import PlaidCardFeedIcon from '@components/PlaidCardFeedIcon';
import BaseListItem from '@components/SelectionList/BaseListItem';
import type {BaseListItemProps, ListItem} from '@components/SelectionList/types';
import TextWithTooltip from '@components/TextWithTooltip';
Expand All @@ -18,7 +19,15 @@ import CONST from '@src/CONST';
import type {PersonalDetails} from '@src/types/onyx';
import type {BankIcon} from '@src/types/onyx/Bank';

type AdditionalCardProps = {shouldShowOwnersAvatar?: boolean; cardOwnerPersonalDetails?: PersonalDetails; bankIcon?: BankIcon; lastFourPAN?: string; isVirtual?: boolean; cardName?: string};
type AdditionalCardProps = {
shouldShowOwnersAvatar?: boolean;
cardOwnerPersonalDetails?: PersonalDetails;
bankIcon?: BankIcon;
lastFourPAN?: string;
isVirtual?: boolean;
cardName?: string;
plaidUrl?: string;
};
type CardListItemProps<TItem extends ListItem> = BaseListItemProps<TItem & AdditionalCardProps>;

function CardListItem<TItem extends ListItem>({
Expand Down Expand Up @@ -102,21 +111,34 @@ function CardListItem<TItem extends ListItem>({
</View>
</UserDetailsTooltip>
<View style={[styles.cardItemSecondaryIconStyle, StyleUtils.getBorderColorStyle(theme.componentBG)]}>
<Icon
src={item.bankIcon.icon}
width={variables.cardMiniatureWidth}
height={variables.cardMiniatureHeight}
additionalStyles={styles.cardMiniature}
/>
{!!item?.plaidUrl && (
<PlaidCardFeedIcon
plaidUrl={item.plaidUrl}
isSmall
/>
)}
{!item?.plaidUrl && (
<Icon
src={item.bankIcon.icon}
width={variables.cardMiniatureWidth}
height={variables.cardMiniatureHeight}
additionalStyles={styles.cardMiniature}
/>
)}
</View>
</View>
) : (
<Icon
src={item.bankIcon.icon}
width={variables.cardIconWidth}
height={variables.cardIconHeight}
additionalStyles={styles.cardIcon}
/>
<>
{!!item?.plaidUrl && <PlaidCardFeedIcon plaidUrl={item.plaidUrl} />}
{!item?.plaidUrl && (
<Icon
src={item.bankIcon.icon}
width={variables.cardIconWidth}
height={variables.cardIconHeight}
additionalStyles={styles.cardIcon}
/>
)}
</>
)}
</View>
)}
Expand Down
2 changes: 2 additions & 0 deletions src/libs/API/parameters/OpenPlaidCompanyCardLoginParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ type OpenPlaidCompanyCardLoginParams = {
redirectURI: string | undefined;
androidPackage?: string;
country: string;
domain?: string;
feed?: string;
};

export default OpenPlaidCompanyCardLoginParams;
33 changes: 25 additions & 8 deletions src/libs/CardFeedUtils.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import type {OnyxCollection} from 'react-native-onyx/dist/types';
import type {OnyxCollection} from 'react-native-onyx';
import type {LocaleContextProps} from '@components/LocaleContextProvider';
import type {AdditionalCardProps} from '@components/SelectionList/Search/CardListItem';
import type IllustrationsType from '@styles/theme/illustrations/types';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Card, CardFeeds, CardList, CompanyCardFeed, PersonalDetailsList, WorkspaceCardsList} from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import {getBankName, getCardFeedIcon, getCompanyFeeds, getCustomOrFormattedFeedName, isCard, isCardClosed, isCardHiddenFromSearch} from './CardUtils';
import {
getBankName,
getCardFeedIcon,
getCompanyFeeds,
getCustomOrFormattedFeedName,
getPlaidInstitutionIconUrl,
getPlaidInstitutionId,
isCard,
isCardClosed,
isCardHiddenFromSearch,
} from './CardUtils';
import {getDescriptionForPolicyDomainCard, getPolicy} from './PolicyUtils';
import type {OptionData} from './ReportUtils';

type CardFilterItem = Partial<OptionData> & AdditionalCardProps & {isCardFeed?: boolean; correspondingCards?: string[]; cardFeedKey: string};
type CardFilterItem = Partial<OptionData> & AdditionalCardProps & {isCardFeed?: boolean; correspondingCards?: string[]; cardFeedKey: string; plaidUrl?: string};
type DomainFeedData = {bank: string; domainName: string; correspondingCardIDs: string[]; fundID?: string};
type ItemsGroupedBySelection = {selected: CardFilterItem[]; unselected: CardFilterItem[]};
type CardFeedNamesWithType = Record<string, {name: string; type: 'domain' | 'workspace'}>;
Expand Down Expand Up @@ -74,6 +84,7 @@ function createCardFilterItem(card: Card, personalDetailsList: PersonalDetailsLi
const icon = getCardFeedIcon(card?.bank as CompanyCardFeed, illustrations);
const cardName = card?.nameValuePairs?.cardTitle;
const text = personalDetails?.displayName ?? cardName;
const plaidUrl = getPlaidInstitutionIconUrl(card?.bank);

return {
lastFourPAN: card.lastFourPAN,
Expand All @@ -82,6 +93,7 @@ function createCardFilterItem(card: Card, personalDetailsList: PersonalDetailsLi
cardName,
cardOwnerPersonalDetails: personalDetails ?? undefined,
text,
plaidUrl,
keyForList: card.cardID.toString(),
isSelected,
bankIcon: {
Expand All @@ -101,7 +113,7 @@ function buildCardsData(
isClosedCards = false,
): ItemsGroupedBySelection {
// Filter condition to build different cards data for closed cards and individual cards based on the isClosedCards flag, we don't want to show closed cards in the individual cards section
const filterCondition = (card: Card) => (isClosedCards ? isCardClosed(card) : !isCardHiddenFromSearch(card) && !isCardClosed(card));
const filterCondition = (card: Card) => (isClosedCards ? isCardClosed(card) : !isCardHiddenFromSearch(card) && !isCardClosed(card) && isCard(card));
const userAssignedCards: CardFilterItem[] = Object.values(userCardList ?? {})
.filter((card) => filterCondition(card))
.map((card) => createCardFilterItem(card, personalDetailsList, selectedCards, illustrations));
Expand Down Expand Up @@ -170,21 +182,24 @@ function getWorkspaceCardFeedData(cardFeed: WorkspaceCardsList | undefined, repe
if (!representativeCard || !cardFeedArray.some((cardFeedItem) => isCard(cardFeedItem) && !isCardHiddenFromSearch(cardFeedItem))) {
return;
}
const {domainName, bank} = representativeCard;
const {domainName, bank, cardName} = representativeCard;
const isBankRepeating = repeatingBanks.includes(bank);
const policyID = domainName.match(CONST.REGEX.EXPENSIFY_POLICY_DOMAIN_NAME)?.[1] ?? '';
// This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
// eslint-disable-next-line deprecation/deprecation
const correspondingPolicy = getPolicy(policyID?.toUpperCase());
const cardFeedLabel = isBankRepeating ? correspondingPolicy?.name : undefined;
const cardFeedBankName = bank === CONST.EXPENSIFY_CARD.BANK ? translate('search.filters.card.expensify') : getBankName(bank as CompanyCardFeed);
const cardName =
const isPlaid = !!getPlaidInstitutionId(bank);
const companyCardBank = isPlaid && cardName ? cardName : getBankName(bank as CompanyCardFeed);

const cardFeedBankName = bank === CONST.EXPENSIFY_CARD.BANK ? translate('search.filters.card.expensify') : companyCardBank;
const fullCardName =
cardFeedBankName === CONST.COMPANY_CARDS.CARD_TYPE.CSV
? translate('search.filters.card.cardFeedNameCSV', {cardFeedLabel})
: translate('search.filters.card.cardFeedName', {cardFeedBankName, cardFeedLabel});

return {
cardName,
cardName: fullCardName,
bank,
label: cardFeedLabel,
type: 'workspace',
Expand Down Expand Up @@ -276,6 +291,7 @@ function createCardFeedItem({
illustrations: IllustrationsType;
}): CardFilterItem {
const isSelected = correspondingCardIDs.every((card) => selectedCards.includes(card));
const plaidUrl = getPlaidInstitutionIconUrl(bank);

const icon = getCardFeedIcon(bank as CompanyCardFeed, illustrations);
return {
Expand All @@ -286,6 +302,7 @@ function createCardFeedItem({
bankIcon: {
icon,
},
plaidUrl,
cardFeedKey,
isCardFeed: true,
correspondingCards: correspondingCardIDs,
Expand Down
Loading
Loading