diff --git a/assets/images/companyCards/card-bank_of_america.svg b/assets/images/companyCards/card-bank_of_america.svg deleted file mode 100644 index 684a6a0a28f5..000000000000 --- a/assets/images/companyCards/card-bank_of_america.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/assets/images/companyCards/card-other.svg b/assets/images/companyCards/card-other.svg deleted file mode 100644 index 11ff21285626..000000000000 --- a/assets/images/companyCards/card-other.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/CONST.ts b/src/CONST.ts index e7a5d10b81ae..c485517e41e2 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2518,6 +2518,7 @@ const CONST = { }, COMPANY_CARDS: { STEP: { + SELECT_BANK: 'SelectBank', CARD_TYPE: 'CardType', CARD_INSTRUCTIONS: 'CardInstructions', CARD_NAME: 'CardName', @@ -2528,6 +2529,17 @@ const CONST = { VISA: 'visa', MASTERCARD: 'mastercard', }, + BANKS: { + AMEX: 'American Express', + BANK_OF_AMERICA: 'Bank of America', + BREX: 'Brex', + CAPITAL_ONE: 'Capital One', + CHASE: 'Chase', + CITI_BANK: 'Citibank', + STRIPE: 'Stripe', + WELLS_FARGO: 'Wells Fargo', + OTHER: 'Other', + }, DELETE_TRANSACTIONS: { RESTRICT: 'corporate', ALLOW: 'personal', diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index e3604dc5a86e..bae8f6af1ab2 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -1,7 +1,15 @@ import AmexCompanyCards from '@assets/images/companyCards/amex.svg'; import AmexCardCompanyCardDetail from '@assets/images/companyCards/card-amex.svg'; +import BankOfAmericaCompanyCardDetail from '@assets/images/companyCards/card-bofa.svg'; +import BrexCompanyCardDetail from '@assets/images/companyCards/card-brex.svg'; +import CapitalOneCompanyCardDetail from '@assets/images/companyCards/card-capitalone.svg'; +import ChaseCompanyCardDetail from '@assets/images/companyCards/card-chase.svg'; +import CitibankCompanyCardDetail from '@assets/images/companyCards/card-citi.svg'; import MasterCardCompanyCardDetail from '@assets/images/companyCards/card-mastercard.svg'; +import StripeCompanyCardDetail from '@assets/images/companyCards/card-stripe.svg'; import VisaCompanyCardDetail from '@assets/images/companyCards/card-visa.svg'; +import WellsFargoCompanyCardDetail from '@assets/images/companyCards/card-wellsfargo.svg'; +import OtherCompanyCardDetail from '@assets/images/companyCards/card=-generic.svg'; import CompanyCardsEmptyState from '@assets/images/companyCards/emptystate__card-pos.svg'; import MasterCardCompanyCards from '@assets/images/companyCards/mastercard.svg'; import CompanyCardsPendingState from '@assets/images/companyCards/pendingstate_laptop-with-hourglass-and-cards.svg'; @@ -244,4 +252,12 @@ export { MasterCardCompanyCardDetail, AmexCardCompanyCardDetail, TurtleInShell, + BankOfAmericaCompanyCardDetail, + BrexCompanyCardDetail, + CapitalOneCompanyCardDetail, + ChaseCompanyCardDetail, + CitibankCompanyCardDetail, + OtherCompanyCardDetail, + StripeCompanyCardDetail, + WellsFargoCompanyCardDetail, }; diff --git a/src/languages/en.ts b/src/languages/en.ts index 27c0a1025651..e82907ce9915 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2953,12 +2953,14 @@ const translations = { selectCardFeed: 'Select card feed', addCardFeed: 'Add card feed', addNewCard: { + other: 'Other', cardProviders: { amex: 'American Express Corporate Cards', mastercard: 'Mastercard Commercial Cards', visa: 'Visa Commercial Cards', }, yourCardProvider: `Who's your card provider?`, + whoIsYourBankAccount: 'Who’s your bank?', enableFeed: { title: ({provider}: GoBackMessageParams) => `Enable your ${provider} feed`, heading: 'We have a direct integration with your card issuer and can import your transaction data into Expensify quickly and accurately.\n\nTo get started, simply:', @@ -2986,6 +2988,7 @@ const translations = { }, error: { pleaseSelectProvider: 'Please select a card provider before continuing.', + pleaseSelectBankAccount: 'Please select a bank account before continuing.', }, }, assignCard: 'Assign card', diff --git a/src/languages/es.ts b/src/languages/es.ts index 58316a37c523..1ed32df2a818 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2989,12 +2989,14 @@ const translations = { selectCardFeed: 'Seleccionar feed de tarjetas', addCardFeed: 'Añadir alimentación de tarjeta', addNewCard: { + other: 'Otros', cardProviders: { amex: 'Tarjetas de empresa American Express', mastercard: 'Tarjetas comerciales Mastercard', visa: 'Tarjetas comerciales Visa', }, yourCardProvider: `¿Quién es su proveedor de tarjetas?`, + whoIsYourBankAccount: '¿Cuál es tu banco?', enableFeed: { title: ({provider}: GoBackMessageParams) => `Habilita tu feed ${provider}`, heading: @@ -3023,6 +3025,7 @@ const translations = { }, error: { pleaseSelectProvider: 'Seleccione un proveedor de tarjetas antes de continuar.', + pleaseSelectBankAccount: 'Seleccione una cuenta bancaria antes de continuar.', }, }, assignCard: 'Asignar tarjeta', diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 07ed431a0bc0..bd8499fa168f 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -227,6 +227,21 @@ function getMemberCards(policy: OnyxEntry, allCardsList: OnyxCollection< return cards; } +const getBankCardDetailsImage = (bank: ValueOf): IconAsset => { + const iconMap: Record, IconAsset> = { + [CONST.COMPANY_CARDS.BANKS.AMEX]: Illustrations.AmexCardCompanyCardDetail, + [CONST.COMPANY_CARDS.BANKS.BANK_OF_AMERICA]: Illustrations.BankOfAmericaCompanyCardDetail, + [CONST.COMPANY_CARDS.BANKS.CAPITAL_ONE]: Illustrations.CapitalOneCompanyCardDetail, + [CONST.COMPANY_CARDS.BANKS.CHASE]: Illustrations.ChaseCompanyCardDetail, + [CONST.COMPANY_CARDS.BANKS.CITI_BANK]: Illustrations.CitibankCompanyCardDetail, + [CONST.COMPANY_CARDS.BANKS.WELLS_FARGO]: Illustrations.WellsFargoCompanyCardDetail, + [CONST.COMPANY_CARDS.BANKS.BREX]: Illustrations.BrexCompanyCardDetail, + [CONST.COMPANY_CARDS.BANKS.STRIPE]: Illustrations.StripeCompanyCardDetail, + [CONST.COMPANY_CARDS.BANKS.OTHER]: Illustrations.OtherCompanyCardDetail, + }; + return iconMap[bank]; +}; + export { isExpensifyCard, isCorporateCard, @@ -245,4 +260,5 @@ export { getCardFeedIcon, getCardDetailsImage, getMemberCards, + getBankCardDetailsImage, }; diff --git a/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx b/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx index 8d42b3e8408e..9b263299739b 100644 --- a/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx +++ b/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {useOnyx} from 'react-native-onyx'; +import usePermissions from '@hooks/usePermissions'; import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -7,23 +8,42 @@ import CardInstructionsStep from './CardInstructionsStep'; import CardNameStep from './CardNameStep'; import CardTypeStep from './CardTypeStep'; import DetailsStep from './DetailsStep'; +import SelectBankStep from './SelectBankStep'; function AddNewCardPage() { const [addNewCardFeed] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD); + const {canUseDirectFeeds} = usePermissions(); const {currentStep} = addNewCardFeed ?? {}; - switch (currentStep) { - case CONST.COMPANY_CARDS.STEP.CARD_TYPE: - return ; - case CONST.COMPANY_CARDS.STEP.CARD_INSTRUCTIONS: - return ; - case CONST.COMPANY_CARDS.STEP.CARD_NAME: - return ; - case CONST.COMPANY_CARDS.STEP.CARD_DETAILS: - return ; - default: - return ; + if (canUseDirectFeeds) { + switch (currentStep) { + case CONST.COMPANY_CARDS.STEP.SELECT_BANK: + return ; + case CONST.COMPANY_CARDS.STEP.CARD_TYPE: + return ; + case CONST.COMPANY_CARDS.STEP.CARD_INSTRUCTIONS: + return ; + case CONST.COMPANY_CARDS.STEP.CARD_NAME: + return ; + case CONST.COMPANY_CARDS.STEP.CARD_DETAILS: + return ; + default: + return ; + } + } else { + switch (currentStep) { + case CONST.COMPANY_CARDS.STEP.CARD_TYPE: + return ; + case CONST.COMPANY_CARDS.STEP.CARD_INSTRUCTIONS: + return ; + case CONST.COMPANY_CARDS.STEP.CARD_NAME: + return ; + case CONST.COMPANY_CARDS.STEP.CARD_DETAILS: + return ; + default: + return ; + } } } diff --git a/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx b/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx index 391799851f8c..28d6ac834918 100644 --- a/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx +++ b/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx @@ -11,6 +11,7 @@ import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; +import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@navigation/Navigation'; import variables from '@styles/variables'; @@ -23,6 +24,7 @@ function CardTypeStep() { const styles = useThemeStyles(); const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD); const [typeSelected, setTypeSelected] = useState>(); + const {canUseDirectFeeds} = usePermissions(); const [isError, setIsError] = useState(false); const submit = () => { @@ -44,7 +46,11 @@ function CardTypeStep() { }, [addNewCard?.data.cardType]); const handleBackButtonPress = () => { - Navigation.goBack(); + if (canUseDirectFeeds) { + CompanyCards.setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.SELECT_BANK}); + } else { + Navigation.goBack(); + } }; const data = [ diff --git a/src/pages/workspace/companyCards/addNew/SelectBankStep.tsx b/src/pages/workspace/companyCards/addNew/SelectBankStep.tsx new file mode 100644 index 000000000000..be0479327832 --- /dev/null +++ b/src/pages/workspace/companyCards/addNew/SelectBankStep.tsx @@ -0,0 +1,108 @@ +import React, {useEffect, useState} from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import FormHelpMessage from '@components/FormHelpMessage'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import Icon from '@components/Icon'; +import ScreenWrapper from '@components/ScreenWrapper'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as CardUtils from '@libs/CardUtils'; +import Navigation from '@navigation/Navigation'; +import variables from '@styles/variables'; +import * as CompanyCards from '@userActions/CompanyCards'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; + +function SelectBankStep() { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD); + const [bankSelected, setBankSelected] = useState>(); + const [hasError, setHasError] = useState(false); + + const submit = () => { + if (!bankSelected) { + setHasError(true); + } else { + // TODO: https://github.com/Expensify/App/issues/50447 - update the navigation when new screen exists + CompanyCards.setAddNewCompanyCardStepAndData({ + step: bankSelected === CONST.COMPANY_CARDS.BANKS.OTHER ? CONST.COMPANY_CARDS.STEP.CARD_TYPE : CONST.COMPANY_CARDS.STEP.CARD_TYPE, + data: { + selectedBank: bankSelected, + }, + isEditing: false, + }); + } + }; + + useEffect(() => { + setBankSelected(addNewCard?.data.selectedBank); + }, [addNewCard?.data.selectedBank]); + + const handleBackButtonPress = () => { + Navigation.goBack(); + }; + + const data = Object.values(CONST.COMPANY_CARDS.BANKS).map((bank) => ({ + value: bank, + text: bank === CONST.COMPANY_CARDS.BANKS.OTHER ? translate('workspace.companyCards.addNewCard.other') : bank, + keyForList: bank, + isSelected: bankSelected === bank, + leftElement: ( + + ), + })); + + return ( + + + + {translate('workspace.companyCards.addNewCard.whoIsYourBankAccount')} + { + setBankSelected(value); + setHasError(false); + }} + sections={[{data}]} + shouldSingleExecuteRowSelect + initiallyFocusedOptionKey={addNewCard?.data.selectedBank} + shouldUpdateFocusedIndex + showConfirmButton + confirmButtonText={translate('common.next')} + onConfirm={submit} + > + {hasError && ( + + + + )} + + + ); +} + +SelectBankStep.displayName = 'SelectBankStep'; + +export default SelectBankStep; diff --git a/src/types/onyx/CardFeeds.ts b/src/types/onyx/CardFeeds.ts index bd78d54db71b..8b80fabac8ae 100644 --- a/src/types/onyx/CardFeeds.ts +++ b/src/types/onyx/CardFeeds.ts @@ -45,6 +45,9 @@ type AddNewCardFeedData = { /** Card type */ cardType: ValueOf; + /** Selected bank */ + selectedBank: ValueOf; + /** Name of the card */ cardTitle: string; };