Skip to content

Commit 8575dc0

Browse files
authored
Merge pull request Expensify#83211 from callstack-internal/add-personal-card
Add personal card empty state, warning flow and upgrade flow
2 parents 498db0a + fea9c2e commit 8575dc0

40 files changed

Lines changed: 904 additions & 54 deletions

assets/images/simple-illustrations/simple-illustration__question-mark.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading

src/CONST/index.ts

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3642,13 +3642,8 @@ const CONST = {
36423642
LARGE_NORMAL: 'large-normal',
36433643
},
36443644

3645-
PERSONAL_CARD: {
3646-
BANK_NAME: {
3647-
CSV: 'upload',
3648-
},
3649-
},
3650-
36513645
COMPANY_CARD: {
3646+
// Mostly used for feed details
36523647
FEED_BANK_NAME: {
36533648
MASTER_CARD: 'cdf',
36543649
VISA: 'vcf',
@@ -3745,6 +3740,49 @@ const CONST = {
37453740
},
37463741
MANAGE_EXPENSIFY_CARDS_ARTICLE_LINK: 'https://help.expensify.com/articles/new-expensify/expensify-card/Manage-Expensify-Cards',
37473742
},
3743+
PERSONAL_CARDS: {
3744+
FEED_KEY_SEPARATOR: '#',
3745+
STEP: {
3746+
SELECT_BANK: 'SelectBank',
3747+
BANK_CONNECTION: 'BankConnection',
3748+
SELECT_COUNTRY: 'SelectCountry',
3749+
PLAID_CONNECTION: 'PlaidConnection',
3750+
SUCCESS: 'Success',
3751+
},
3752+
// Mostly used for get bank details
3753+
BANKS: {
3754+
AMEX: 'American Express',
3755+
BANK_OF_AMERICA: 'Bank of America',
3756+
CAPITAL_ONE: 'Capital One',
3757+
CHASE: 'Chase',
3758+
CITI_BANK: 'Citibank',
3759+
WELLS_FARGO: 'Wells Fargo',
3760+
MOCK_BANK: 'Mock Bank',
3761+
OTHER: 'Other',
3762+
},
3763+
// Mostly used for API calls
3764+
BANK_CONNECTIONS: {
3765+
WELLS_FARGO: 'wellsfargo',
3766+
BANK_OF_AMERICA: 'bankofamerica',
3767+
CHASE: 'chase',
3768+
CAPITAL_ONE: 'capitalone',
3769+
CITI_BANK: 'citibank',
3770+
AMEX: 'americanexpressfdx',
3771+
MOCK_BANK: 'mockbank',
3772+
},
3773+
// Mostly used for feed details
3774+
BANK_NAME: {
3775+
CITIBANK: 'oauth.citibank.com',
3776+
CAPITAL_ONE: 'oauth.capitalone.com',
3777+
BANK_OF_AMERICA: 'oauth.bankofamerica.com',
3778+
CHASE: 'oauth.chase.com',
3779+
PEX: 'admin.pexcard.com',
3780+
WELLS_FARGO: 'oauth.wellsfargo.com',
3781+
AMEX_DIRECT: 'oauth.americanexpressfdx.com',
3782+
AMEX_FILE_DOWNLOAD: 'americanexpressfd.us',
3783+
CSV: 'upload',
3784+
},
3785+
},
37483786
COMPANY_CARDS: {
37493787
BROKEN_CONNECTION_IGNORED_STATUSES: brokenConnectionScrapeStatuses,
37503788
CONNECTION_ERROR: 'connectionError',
@@ -3782,6 +3820,7 @@ const CONST = {
37823820
CUSTOM: 'customFeed',
37833821
DIRECT: 'directFeed',
37843822
},
3823+
// Mostly used for get bank details
37853824
BANKS: {
37863825
AMEX: 'American Express',
37873826
BANK_OF_AMERICA: 'Bank of America',
@@ -3797,6 +3836,7 @@ const CONST = {
37973836
NON_CONNECTABLE_BANKS: {
37983837
PEX: 'PEX',
37993838
},
3839+
// Mostly used for API calls
38003840
BANK_CONNECTIONS: {
38013841
WELLS_FARGO: 'wellsfargo',
38023842
BANK_OF_AMERICA: 'bankofamerica',
@@ -4729,7 +4769,7 @@ const CONST = {
47294769
},
47304770

47314771
PLAID_EXCLUDED_COUNTRIES: ['IR', 'CU', 'SY', 'UA', 'KP'] as string[],
4732-
PLAID_SUPPORT_COUNTRIES: ['US', 'CA', 'GB', 'AT', 'BE', 'DK', 'EE', 'FI', 'FR', 'DE', 'IE', 'IT', 'LV', 'LT', 'NL', 'NO', 'PL', 'PT', 'ES', 'SE'] as string[],
4772+
PLAID_SUPPORT_COUNTRIES: ['AT', 'BE', 'CA', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'IE', 'IT', 'LT', 'LV', 'NL', 'NO', 'PL', 'PT', 'SE', 'US'] as string[],
47334773

47344774
BBA_SUPPORTED_COUNTRIES: [
47354775
'AT',
@@ -9006,6 +9046,7 @@ const CONST = {
90069046
},
90079047
SETTINGS_WALLET: {
90089048
ADD_BANK_ACCOUNT: 'SettingsWallet-AddBankAccount',
9049+
ADD_PERSONAL_CARD: 'SettingsWallet-AddPersonalCard',
90099050
IMPORT_TRANSACTIONS: 'SettingsWallet-ImportTransactions',
90109051
TRANSFER_BALANCE: 'SettingsWallet-TransferBalance',
90119052
ENABLE_WALLET: 'SettingsWallet-EnableWallet',

src/ROUTES.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,8 @@ const ROUTES = {
458458
route: 'settings/wallet/:bankAccountID/share-bank-account',
459459
getRoute: (bankAccountID: number | undefined) => `settings/wallet/${bankAccountID}/share-bank-account` as const,
460460
},
461+
SETTINGS_WALLET_PERSONAL_CARD_UPGRADE: 'settings/wallet/add-personal-card/upgrade',
462+
SETTINGS_WALLET_PERSONAL_CARD_WARNING: 'settings/wallet/add-personal-card/warning',
461463
SETTINGS_WALLET_CARD_DIGITAL_DETAILS_UPDATE_ADDRESS: {
462464
route: 'settings/wallet/card/:domain/digital-details/update-address',
463465
getRoute: (domain: string) => `settings/wallet/card/${domain}/digital-details/update-address` as const,

src/SCREENS.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ const SCREENS = {
205205
PERSONAL_CARD_DETAILS: 'Settings_Wallet_Personal_Card_Details',
206206
PERSONAL_CARD_EDIT_NAME: 'Settings_Wallet_Personal_Card_Edit_Name',
207207
PERSONAL_CARD_EDIT_TRANSACTION_START_DATE: 'Settings_Wallet_Personal_Card_Edit_Transaction_Start_Date',
208+
PERSONAL_CARD_WARNING: 'Settings_Wallet_PersonalCard_Warning',
209+
PERSONAL_CARD_UPGRADE: 'Settings_Wallet_PersonalCard_Upgrade',
208210
},
209211

210212
EXIT_SURVEY: {

src/components/Icon/chunks/illustrations.chunk.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ import PiggyBank from '@assets/images/simple-illustrations/simple-illustration__
157157
import Pillow from '@assets/images/simple-illustrations/simple-illustration__pillow.svg';
158158
import Profile from '@assets/images/simple-illustrations/simple-illustration__profile.svg';
159159
import QRCode from '@assets/images/simple-illustrations/simple-illustration__qr-code.svg';
160+
import QuestionMark from '@assets/images/simple-illustrations/simple-illustration__question-mark.svg';
160161
import RealtimeReport from '@assets/images/simple-illustrations/simple-illustration__realtimereports.svg';
161162
import ReceiptLocationMarker from '@assets/images/simple-illustrations/simple-illustration__receipt-location-marker.svg';
162163
import ReceiptWrangler from '@assets/images/simple-illustrations/simple-illustration__receipt-wrangler.svg';
@@ -185,6 +186,7 @@ import Trophy1 from '@assets/images/simple-illustrations/simple-illustration__tr
185186
import Trophy from '@assets/images/simple-illustrations/simple-illustration__trophy.svg';
186187
import CompanyCard from '@assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg';
187188
import UserShield from '@assets/images/simple-illustrations/simple-illustration__user-shield.svg';
189+
import VerticalCreditCards from '@assets/images/simple-illustrations/simple-illustration__vertical-credit-cards.svg';
188190
import VirtualCard from '@assets/images/simple-illustrations/simple-illustration__virtualcard.svg';
189191
import WalletAlt2 from '@assets/images/simple-illustrations/simple-illustration__wallet-alt2.svg';
190192
import Workflows from '@assets/images/simple-illustrations/simple-illustration__workflows.svg';
@@ -260,9 +262,11 @@ const Illustrations = {
260262
ReceiptsStackedOnPin,
261263
RocketBlue,
262264
RocketDude,
265+
VerticalCreditCards,
263266
Safe,
264267
SaveTheWorldScale,
265268
SewerDino,
269+
QuestionMark,
266270
SmartScan,
267271
TeleScope,
268272
Telescope: TeleScope, // Alias for consistency

src/components/Section/index.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ type SectionProps = Partial<ChildrenProps> & {
6262
/** Customize the Icon container */
6363
iconContainerStyles?: StyleProp<ViewStyle>;
6464

65+
/** Customize the Central pane container */
66+
centralPaneContainerStyle?: StyleProp<ViewStyle>;
67+
6568
/** Whether the section is in the central pane of the layout */
6669
isCentralPane?: boolean;
6770

@@ -115,6 +118,7 @@ function Section({
115118
renderTitle,
116119
titleStyles,
117120
isCentralPane = false,
121+
centralPaneContainerStyle,
118122
illustration,
119123
illustrationBackgroundColor,
120124
illustrationContainerStyle,
@@ -176,7 +180,7 @@ function Section({
176180
{overlayContent?.()}
177181
</View>
178182
)}
179-
<View style={[styles.w100, isCentralPane && (shouldUseNarrowLayout ? styles.p5 : (contentPaddingOnLargeScreens ?? styles.p8))]}>
183+
<View style={[styles.w100, isCentralPane && (shouldUseNarrowLayout ? styles.p5 : (contentPaddingOnLargeScreens ?? styles.p8)), centralPaneContainerStyle]}>
180184
<View style={[styles.flexRow, styles.alignItemsCenter, styles.w100, cardLayout === CARD_LAYOUT.ICON_ON_TOP && styles.mh1]}>
181185
{cardLayout === CARD_LAYOUT.ICON_ON_LEFT && (
182186
<IconSection
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import {getBankLinkedPersonalCards} from '@selectors/Card';
2+
import ONYXKEYS from '@src/ONYXKEYS';
3+
import type {CardList} from '@src/types/onyx';
4+
import useOnyx from './useOnyx';
5+
6+
function useBankLinkedPersonalCards(): CardList {
7+
const [allCards] = useOnyx(ONYXKEYS.CARD_LIST);
8+
9+
return getBankLinkedPersonalCards(allCards);
10+
}
11+
12+
export default useBankLinkedPersonalCards;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type {OnyxCollection} from 'react-native-onyx';
2+
import {getCardFeedsForDisplayPerPolicy} from '@libs/CardFeedUtils';
3+
import {getActivePolicies, isPaidGroupPolicy} from '@libs/PolicyUtils';
4+
import ONYXKEYS from '@src/ONYXKEYS';
5+
import type {Policy} from '@src/types/onyx';
6+
import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
7+
import useFeedKeysWithAssignedCards from './useFeedKeysWithAssignedCards';
8+
import useLocalize from './useLocalize';
9+
import useOnyx from './useOnyx';
10+
11+
function useEligiblePoliciesSelector() {
12+
const {login: currentUserLogin} = useCurrentUserPersonalDetails();
13+
return (policies: OnyxCollection<Policy>) => {
14+
const activePolicies = getActivePolicies(policies, currentUserLogin);
15+
return Object.values(activePolicies ?? {}).reduce((policiesIDs, policy) => {
16+
if (isPaidGroupPolicy(policy) && policy?.areCompanyCardsEnabled) {
17+
policiesIDs.push(policy.id);
18+
}
19+
return policiesIDs;
20+
}, [] as string[]);
21+
};
22+
}
23+
24+
const useCardFeedsForActivePolicies = () => {
25+
const {translate} = useLocalize();
26+
const [allFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER);
27+
const feedKeysWithCards = useFeedKeysWithAssignedCards();
28+
const eligiblePoliciesSelector = useEligiblePoliciesSelector();
29+
const [eligiblePoliciesIDsArray] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {
30+
selector: eligiblePoliciesSelector,
31+
});
32+
33+
const allCardFeedsByPolicy = getCardFeedsForDisplayPerPolicy(allFeeds, translate, feedKeysWithCards);
34+
const eligiblePolicyIdsSet = new Set(eligiblePoliciesIDsArray ?? []);
35+
const cardFeedsByPolicy = Object.fromEntries(Object.entries(allCardFeedsByPolicy).filter(([policyID]) => eligiblePolicyIdsSet.has(policyID)));
36+
37+
return {cardFeedsByPolicy};
38+
};
39+
40+
export default useCardFeedsForActivePolicies;

src/languages/de.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2184,12 +2184,37 @@ const translations: TranslationDeepObject<typeof en> = {
21842184
},
21852185
},
21862186
personalCard: {
2187+
addPersonalCard: 'Persönliche Karte hinzufügen',
2188+
addCompanyCard: 'Firmenkarte hinzufügen',
2189+
lookingForCompanyCards: 'Müssen Sie Firmenkarten hinzufügen?',
2190+
lookingForCompanyCardsDescription: 'Verbinden Sie Ihre eigenen Karten von über 10.000 Banken weltweit.',
2191+
personalCardAdded: 'Persönliche Karte hinzugefügt!',
2192+
personalCardAddedDescription: 'Herzlichen Glückwunsch! Wir beginnen nun mit dem Import von Transaktionen Ihrer Karte.',
2193+
isPersonalCard: 'Ist dies eine private Karte?',
2194+
thisIsPersonalCard: 'Dies ist eine private Karte',
2195+
thisIsCompanyCard: 'Dies ist eine Firmenkarte',
2196+
askAdmin: 'Fragen Sie Ihren Administrator',
2197+
warningDescription: ({isAdmin}: {isAdmin?: boolean}) =>
2198+
`Wenn ja, super! Aber wenn es eine <strong>Firmen</strong>karte ist, weisen Sie sie bitte ${isAdmin ? 'stattdessen über Ihren Workspace zu.' : 'bitten Sie Ihren Administrator, sie Ihnen stattdessen über den Workspace zuzuweisen.'}`,
2199+
bankConnectionError: 'Bankverbindungsproblem',
2200+
bankConnectionDescription: 'Bitte versuchen Sie, Ihre Karten erneut hinzuzufügen. Andernfalls können Sie',
2201+
connectWithPlaid: 'eine Verbindung über Plaid herstellen.',
21872202
brokenConnection: 'Ihre Kartenverbindung ist unterbrochen.',
21882203
conciergeBrokenConnection: ({cardName, connectionLink}: ConciergeBrokenCardConnectionParams) =>
21892204
connectionLink
21902205
? `Die Verbindung Ihrer ${cardName}-Karte ist unterbrochen. <a href="${connectionLink}">Melden Sie sich bei Ihrer Bank an</a>, um die Karte zu reparieren.`
21912206
: `Die Verbindung Ihrer ${cardName}-Karte ist unterbrochen. Melden Sie sich bei Ihrer Bank an, um die Karte zu reparieren.`,
21922207
fixCard: 'Karte reparieren',
2208+
addAdditionalCards: 'Weitere Karten hinzufügen',
2209+
upgradeDescription: 'Müssen Sie weitere Karten hinzufügen? Erstellen Sie einen Workspace, um weitere persönliche Karten hinzuzufügen oder Firmenkarten dem gesamten Team zuzuweisen.',
2210+
onlyAvailableOnPlan: ({formattedPrice}: {formattedPrice: string}) =>
2211+
`<muted-text>Dies ist im Collect-Tarif verfügbar, der <strong>${formattedPrice}</strong> pro Mitglied und Monat kostet.</muted-text>`,
2212+
note: ({subscriptionLink}: WorkspaceUpgradeNoteParams) =>
2213+
`<muted-text>Erstellen Sie einen Workspace, um auf diese Funktion zuzugreifen, oder <a href="${subscriptionLink}">erfahren Sie mehr</a> über unsere Tarife und Preise.</muted-text>`,
2214+
workspaceCreated: 'Workspace erstellt',
2215+
newWorkspace: 'Sie haben einen Workspace erstellt!',
2216+
successMessage: ({subscriptionLink}: {subscriptionLink: string}) =>
2217+
`<centered-text>Sie können jetzt weitere Karten hinzufügen. <a href="${subscriptionLink}">Zeigen Sie Ihr Abonnement an</a> für weitere Details.</centered-text>`,
21932218
},
21942219
walletPage: {
21952220
balance: 'Saldo',

0 commit comments

Comments
 (0)