Skip to content

Commit be6876c

Browse files
authored
Merge pull request Expensify#64741 from callstack-internal/more-plaid-company-card-follow-ups
[Internal QA]: More Plaid import follow ups
2 parents 86e5505 + 95ea3bb commit be6876c

16 files changed

Lines changed: 167 additions & 45 deletions

File tree

Lines changed: 1 addition & 1 deletion
Loading
Lines changed: 1 addition & 1 deletion
Loading

src/components/PlaidCardFeedIcon.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,21 @@ type PlaidCardFeedIconProps = {
1313
plaidUrl: string;
1414
style?: StyleProp<ViewStyle>;
1515
isLarge?: boolean;
16+
isSmall?: boolean;
1617
};
1718

18-
function PlaidCardFeedIcon({plaidUrl, style, isLarge}: PlaidCardFeedIconProps) {
19+
function PlaidCardFeedIcon({plaidUrl, style, isLarge, isSmall}: PlaidCardFeedIconProps) {
1920
const [isBrokenImage, setIsBrokenImage] = useState<boolean>(false);
2021
const styles = useThemeStyles();
2122
const illustrations = useThemeIllustrations();
2223
const theme = useTheme();
2324
const width = isLarge ? variables.cardPreviewWidth : variables.cardIconWidth;
2425
const height = isLarge ? variables.cardPreviewHeight : variables.cardIconHeight;
2526
const [loading, setLoading] = useState<boolean>(true);
27+
const plaidImageStyle = isLarge ? styles.plaidIcon : styles.plaidIconSmall;
28+
const iconWidth = isSmall ? variables.cardMiniatureWidth : width;
29+
const iconHeight = isSmall ? variables.cardMiniatureHeight : height;
30+
const plaidLoadedStyle = isSmall ? styles.plaidIconExtraSmall : plaidImageStyle;
2631

2732
useEffect(() => {
2833
if (!plaidUrl) {
@@ -37,31 +42,32 @@ function PlaidCardFeedIcon({plaidUrl, style, isLarge}: PlaidCardFeedIconProps) {
3742
{isBrokenImage ? (
3843
<Icon
3944
src={illustrations.GenericCompanyCardLarge}
40-
height={height}
41-
width={width}
42-
additionalStyles={styles.cardIcon}
45+
height={iconHeight}
46+
width={iconWidth}
47+
additionalStyles={isSmall ? styles.cardMiniature : styles.cardIcon}
4348
/>
4449
) : (
4550
<>
4651
<Image
4752
source={{uri: plaidUrl}}
48-
style={isLarge ? styles.plaidIcon : styles.plaidIconSmall}
53+
style={plaidLoadedStyle}
4954
cachePolicy="memory-disk"
5055
onError={() => setIsBrokenImage(true)}
5156
onLoadEnd={() => setLoading(false)}
5257
/>
5358
{loading ? (
54-
<View style={[styles.justifyContentCenter, {width, height}]}>
59+
<View style={[styles.justifyContentCenter, {width: iconWidth, height: iconHeight}]}>
5560
<ActivityIndicator
5661
color={theme.spinner}
57-
size={20}
62+
size={isSmall ? 10 : 20}
5863
/>
5964
</View>
6065
) : (
6166
<Icon
6267
src={isLarge ? Illustrations.PlaidCompanyCardDetailLarge : Illustrations.PlaidCompanyCardDetail}
63-
height={height}
64-
width={width}
68+
height={iconHeight}
69+
width={iconWidth}
70+
additionalStyles={isSmall && styles.cardMiniature}
6571
/>
6672
)}
6773
</>

src/components/SelectionList/Search/CardListItem.tsx

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Avatar from '@components/Avatar';
55
import Checkbox from '@components/Checkbox';
66
import Icon from '@components/Icon';
77
import {FallbackAvatar} from '@components/Icon/Expensicons';
8+
import PlaidCardFeedIcon from '@components/PlaidCardFeedIcon';
89
import BaseListItem from '@components/SelectionList/BaseListItem';
910
import type {BaseListItemProps, ListItem} from '@components/SelectionList/types';
1011
import TextWithTooltip from '@components/TextWithTooltip';
@@ -18,7 +19,15 @@ import CONST from '@src/CONST';
1819
import type {PersonalDetails} from '@src/types/onyx';
1920
import type {BankIcon} from '@src/types/onyx/Bank';
2021

21-
type AdditionalCardProps = {shouldShowOwnersAvatar?: boolean; cardOwnerPersonalDetails?: PersonalDetails; bankIcon?: BankIcon; lastFourPAN?: string; isVirtual?: boolean; cardName?: string};
22+
type AdditionalCardProps = {
23+
shouldShowOwnersAvatar?: boolean;
24+
cardOwnerPersonalDetails?: PersonalDetails;
25+
bankIcon?: BankIcon;
26+
lastFourPAN?: string;
27+
isVirtual?: boolean;
28+
cardName?: string;
29+
plaidUrl?: string;
30+
};
2231
type CardListItemProps<TItem extends ListItem> = BaseListItemProps<TItem & AdditionalCardProps>;
2332

2433
function CardListItem<TItem extends ListItem>({
@@ -102,21 +111,34 @@ function CardListItem<TItem extends ListItem>({
102111
</View>
103112
</UserDetailsTooltip>
104113
<View style={[styles.cardItemSecondaryIconStyle, StyleUtils.getBorderColorStyle(theme.componentBG)]}>
105-
<Icon
106-
src={item.bankIcon.icon}
107-
width={variables.cardMiniatureWidth}
108-
height={variables.cardMiniatureHeight}
109-
additionalStyles={styles.cardMiniature}
110-
/>
114+
{!!item?.plaidUrl && (
115+
<PlaidCardFeedIcon
116+
plaidUrl={item.plaidUrl}
117+
isSmall
118+
/>
119+
)}
120+
{!item?.plaidUrl && (
121+
<Icon
122+
src={item.bankIcon.icon}
123+
width={variables.cardMiniatureWidth}
124+
height={variables.cardMiniatureHeight}
125+
additionalStyles={styles.cardMiniature}
126+
/>
127+
)}
111128
</View>
112129
</View>
113130
) : (
114-
<Icon
115-
src={item.bankIcon.icon}
116-
width={variables.cardIconWidth}
117-
height={variables.cardIconHeight}
118-
additionalStyles={styles.cardIcon}
119-
/>
131+
<>
132+
{!!item?.plaidUrl && <PlaidCardFeedIcon plaidUrl={item.plaidUrl} />}
133+
{!item?.plaidUrl && (
134+
<Icon
135+
src={item.bankIcon.icon}
136+
width={variables.cardIconWidth}
137+
height={variables.cardIconHeight}
138+
additionalStyles={styles.cardIcon}
139+
/>
140+
)}
141+
</>
120142
)}
121143
</View>
122144
)}

src/libs/API/parameters/OpenPlaidCompanyCardLoginParams.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ type OpenPlaidCompanyCardLoginParams = {
22
redirectURI: string | undefined;
33
androidPackage?: string;
44
country: string;
5+
domain?: string;
6+
feed?: string;
57
};
68

79
export default OpenPlaidCompanyCardLoginParams;

src/libs/CardFeedUtils.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
1-
import type {OnyxCollection} from 'react-native-onyx/dist/types';
1+
import type {OnyxCollection} from 'react-native-onyx';
22
import type {LocaleContextProps} from '@components/LocaleContextProvider';
33
import type {AdditionalCardProps} from '@components/SelectionList/Search/CardListItem';
44
import type IllustrationsType from '@styles/theme/illustrations/types';
55
import CONST from '@src/CONST';
66
import ONYXKEYS from '@src/ONYXKEYS';
77
import type {Card, CardFeeds, CardList, CompanyCardFeed, PersonalDetailsList, WorkspaceCardsList} from '@src/types/onyx';
88
import {isEmptyObject} from '@src/types/utils/EmptyObject';
9-
import {getBankName, getCardFeedIcon, getCompanyFeeds, getCustomOrFormattedFeedName, isCard, isCardClosed, isCardHiddenFromSearch} from './CardUtils';
9+
import {
10+
getBankName,
11+
getCardFeedIcon,
12+
getCompanyFeeds,
13+
getCustomOrFormattedFeedName,
14+
getPlaidInstitutionIconUrl,
15+
getPlaidInstitutionId,
16+
isCard,
17+
isCardClosed,
18+
isCardHiddenFromSearch,
19+
} from './CardUtils';
1020
import {getDescriptionForPolicyDomainCard, getPolicy} from './PolicyUtils';
1121
import type {OptionData} from './ReportUtils';
1222

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

7889
return {
7990
lastFourPAN: card.lastFourPAN,
@@ -82,6 +93,7 @@ function createCardFilterItem(card: Card, personalDetailsList: PersonalDetailsLi
8293
cardName,
8394
cardOwnerPersonalDetails: personalDetails ?? undefined,
8495
text,
96+
plaidUrl,
8597
keyForList: card.cardID.toString(),
8698
isSelected,
8799
bankIcon: {
@@ -101,7 +113,7 @@ function buildCardsData(
101113
isClosedCards = false,
102114
): ItemsGroupedBySelection {
103115
// 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
104-
const filterCondition = (card: Card) => (isClosedCards ? isCardClosed(card) : !isCardHiddenFromSearch(card) && !isCardClosed(card));
116+
const filterCondition = (card: Card) => (isClosedCards ? isCardClosed(card) : !isCardHiddenFromSearch(card) && !isCardClosed(card) && isCard(card));
105117
const userAssignedCards: CardFilterItem[] = Object.values(userCardList ?? {})
106118
.filter((card) => filterCondition(card))
107119
.map((card) => createCardFilterItem(card, personalDetailsList, selectedCards, illustrations));
@@ -170,21 +182,24 @@ function getWorkspaceCardFeedData(cardFeed: WorkspaceCardsList | undefined, repe
170182
if (!representativeCard || !cardFeedArray.some((cardFeedItem) => isCard(cardFeedItem) && !isCardHiddenFromSearch(cardFeedItem))) {
171183
return;
172184
}
173-
const {domainName, bank} = representativeCard;
185+
const {domainName, bank, cardName} = representativeCard;
174186
const isBankRepeating = repeatingBanks.includes(bank);
175187
const policyID = domainName.match(CONST.REGEX.EXPENSIFY_POLICY_DOMAIN_NAME)?.[1] ?? '';
176188
// This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
177189
// eslint-disable-next-line deprecation/deprecation
178190
const correspondingPolicy = getPolicy(policyID?.toUpperCase());
179191
const cardFeedLabel = isBankRepeating ? correspondingPolicy?.name : undefined;
180-
const cardFeedBankName = bank === CONST.EXPENSIFY_CARD.BANK ? translate('search.filters.card.expensify') : getBankName(bank as CompanyCardFeed);
181-
const cardName =
192+
const isPlaid = !!getPlaidInstitutionId(bank);
193+
const companyCardBank = isPlaid && cardName ? cardName : getBankName(bank as CompanyCardFeed);
194+
195+
const cardFeedBankName = bank === CONST.EXPENSIFY_CARD.BANK ? translate('search.filters.card.expensify') : companyCardBank;
196+
const fullCardName =
182197
cardFeedBankName === CONST.COMPANY_CARDS.CARD_TYPE.CSV
183198
? translate('search.filters.card.cardFeedNameCSV', {cardFeedLabel})
184199
: translate('search.filters.card.cardFeedName', {cardFeedBankName, cardFeedLabel});
185200

186201
return {
187-
cardName,
202+
cardName: fullCardName,
188203
bank,
189204
label: cardFeedLabel,
190205
type: 'workspace',
@@ -276,6 +291,7 @@ function createCardFeedItem({
276291
illustrations: IllustrationsType;
277292
}): CardFilterItem {
278293
const isSelected = correspondingCardIDs.every((card) => selectedCards.includes(card));
294+
const plaidUrl = getPlaidInstitutionIconUrl(bank);
279295

280296
const icon = getCardFeedIcon(bank as CompanyCardFeed, illustrations);
281297
return {
@@ -286,6 +302,7 @@ function createCardFeedItem({
286302
bankIcon: {
287303
icon,
288304
},
305+
plaidUrl,
289306
cardFeedKey,
290307
isCardFeed: true,
291308
correspondingCards: correspondingCardIDs,

0 commit comments

Comments
 (0)