diff --git a/assets/images/companyCards/card-amex-blue.svg b/assets/images/companyCards/card-amex-blue.svg
new file mode 100644
index 000000000000..5282ca095760
--- /dev/null
+++ b/assets/images/companyCards/card-amex-blue.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/src/CONST.ts b/src/CONST.ts
index d0695b1e285f..6219a74ab5a9 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -715,6 +715,7 @@ const CONST = {
SAGE_INTACCT_HELP_LINK:
"https://help.expensify.com/articles/expensify-classic/connections/sage-intacct/Sage-Intacct-Troubleshooting#:~:text=First%20make%20sure%20that%20you,your%20company's%20Web%20Services%20authorizations.",
PRICING: `https://www.expensify.com/pricing`,
+ COMPANY_CARDS_HELP: 'https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Commercial-Card-Feeds',
CUSTOM_REPORT_NAME_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/spending-insights/Custom-Templates',
// Use Environment.getEnvironmentURL to get the complete URL with port number
DEV_NEW_EXPENSIFY_URL: 'https://dev.new.expensify.com:',
@@ -2444,6 +2445,23 @@ const CONST = {
},
CARD_TITLE_INPUT_LIMIT: 255,
},
+ COMPANY_CARDS: {
+ STEP: {
+ CARD_TYPE: 'CardType',
+ CARD_INSTRUCTIONS: 'CardInstructions',
+ CARD_NAME: 'CardName',
+ CARD_DETAILS: 'CardDetails',
+ },
+ CARD_TYPE: {
+ AMEX: 'amex',
+ VISA: 'visa',
+ MASTERCARD: 'mastercard',
+ },
+ DELETE_TRANSACTIONS: {
+ RESTRICT: 'corporate',
+ ALLOW: 'personal',
+ },
+ },
AVATAR_ROW_SIZE: {
DEFAULT: 4,
LARGE_SCREEN: 8,
@@ -2458,12 +2476,6 @@ const CONST = {
PAYPERUSE: 'monthly2018',
},
},
- COMPANY_CARDS: {
- DELETE_TRANSACTIONS: {
- RESTRICT: 'corporate',
- ALLOW: 'personal',
- },
- },
REGEX: {
SPECIAL_CHARS_WITHOUT_NEWLINE: /((?!\n)[()-\s\t])/g,
DIGITS_AND_PLUS: /^\+?[0-9]*$/,
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 45b9a8c68bbb..389f6796bda5 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -399,6 +399,9 @@ const ONYXKEYS = {
/** Stores the information about the state of issuing a new card */
ISSUE_NEW_EXPENSIFY_CARD: 'issueNewExpensifyCard',
+ /** Stores the information about the state of addint a new company card */
+ ADD_NEW_COMPANY_CARD: 'addNewCompanyCard',
+
/** Stores the information about the state of assigning a company card */
ASSIGN_CARD: 'assignCard',
@@ -625,6 +628,8 @@ const ONYXKEYS = {
SUBSCRIPTION_SIZE_FORM_DRAFT: 'subscriptionSizeFormDraft',
ISSUE_NEW_EXPENSIFY_CARD_FORM: 'issueNewExpensifyCard',
ISSUE_NEW_EXPENSIFY_CARD_FORM_DRAFT: 'issueNewExpensifyCardDraft',
+ ADD_NEW_CARD_FEED_FORM: 'addNewCardFeed',
+ ADD_NEW_CARD_FEED_FORM_DRAFT: 'addNewCardFeedDraft',
ASSIGN_CARD_FORM: 'assignCard',
ASSIGN_CARD_FORM_DRAFT: 'assignCardDraft',
EDIT_EXPENSIFY_CARD_NAME_FORM: 'editExpensifyCardName',
@@ -732,6 +737,7 @@ type OnyxFormValuesMapping = {
[ONYXKEYS.FORMS.NEW_CHAT_NAME_FORM]: FormTypes.NewChatNameForm;
[ONYXKEYS.FORMS.SUBSCRIPTION_SIZE_FORM]: FormTypes.SubscriptionSizeForm;
[ONYXKEYS.FORMS.ISSUE_NEW_EXPENSIFY_CARD_FORM]: FormTypes.IssueNewExpensifyCardForm;
+ [ONYXKEYS.FORMS.ADD_NEW_CARD_FEED_FORM]: FormTypes.AddNewCardFeedForm;
[ONYXKEYS.FORMS.ASSIGN_CARD_FORM]: FormTypes.AssignCardForm;
[ONYXKEYS.FORMS.EDIT_EXPENSIFY_CARD_NAME_FORM]: FormTypes.EditExpensifyCardNameForm;
[ONYXKEYS.FORMS.EDIT_EXPENSIFY_CARD_LIMIT_FORM]: FormTypes.EditExpensifyCardLimitForm;
@@ -935,6 +941,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.NVP_TRAVEL_SETTINGS]: OnyxTypes.TravelSettings;
[ONYXKEYS.REVIEW_DUPLICATES]: OnyxTypes.ReviewDuplicates;
[ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD]: OnyxTypes.IssueNewCard;
+ [ONYXKEYS.ADD_NEW_COMPANY_CARD]: OnyxTypes.AddNewCompanyCardFeed;
[ONYXKEYS.ASSIGN_CARD]: OnyxTypes.AssignCard;
[ONYXKEYS.MOBILE_SELECTION_MODE]: OnyxTypes.MobileSelectionMode;
[ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL]: string;
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index a28c2ef4fc57..e4eead74c85c 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -916,6 +916,14 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/reportFields/:reportFieldID/edit/initialValue',
getRoute: (policyID: string, reportFieldID: string) => `settings/workspaces/${policyID}/reportFields/${encodeURIComponent(reportFieldID)}/edit/initialValue` as const,
},
+ WORKSPACE_COMPANY_CARDS: {
+ route: 'settings/workspaces/:policyID/company-cards',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/company-cards` as const,
+ },
+ WORKSPACE_COMPANY_CARDS_ADD_NEW: {
+ route: 'settings/workspaces/:policyID/company-cards/add-card-feed',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/company-cards/add-card-feed` as const,
+ },
WORKSPACE_COMPANY_CARDS_SELECT_FEED: {
route: 'settings/workspaces/:policyID/company-cards/select-feed',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/company-cards/select-feed` as const,
@@ -924,10 +932,6 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/expensify-card',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card` as const,
},
- WORKSPACE_COMPANY_CARDS: {
- route: 'settings/workspaces/:policyID/company-cards',
- getRoute: (policyID: string) => `settings/workspaces/${policyID}/company-cards` as const,
- },
WORKSPACE_COMPANY_CARDS_ASSIGN_CARD: {
route: 'settings/workspaces/:policyID/company-cards/:feed/assign-card',
getRoute: (policyID: string, feed: string) => `settings/workspaces/${policyID}/company-cards/${feed}/assign-card` as const,
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 67ba5b84c9ec..c13a23c6c34a 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -371,6 +371,11 @@ const SCREENS = {
COMPANY_CARDS: 'Workspace_CompanyCards',
COMPANY_CARDS_ASSIGN_CARD: 'Workspace_CompanyCards_AssignCard',
COMPANY_CARDS_SELECT_FEED: 'Workspace_CompanyCards_Select_Feed',
+ COMPANY_CARDS_ADD_NEW: 'Workspace_CompanyCards_New',
+ COMPANY_CARDS_TYPE: 'Workspace_CompanyCards_Type',
+ COMPANY_CARDS_INSTRUCTIONS: 'Workspace_CompanyCards_Instructions',
+ COMPANY_CARDS_NAME: 'Workspace_CompanyCards_Name',
+ COMPANY_CARDS_DETAILS: 'Workspace_CompanyCards_Details',
COMPANY_CARDS_SETTINGS: 'Workspace_CompanyCards_Settings',
COMPANY_CARDS_SETTINGS_FEED_NAME: 'Workspace_CompanyCards_Settings_Feed_Name',
EXPENSIFY_CARD: 'Workspace_ExpensifyCard',
diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts
index bc12bc6c135b..446137c3749b 100644
--- a/src/components/Icon/Illustrations.ts
+++ b/src/components/Icon/Illustrations.ts
@@ -1,4 +1,5 @@
import AmexCompanyCards from '@assets/images/companyCards/amex.svg';
+import AmexBlueCompanyCards from '@assets/images/companyCards/card-amex-blue.svg';
import CompanyCardsEmptyState from '@assets/images/companyCards/emptystate__card-pos.svg';
import MasterCardCompanyCards from '@assets/images/companyCards/mastercard.svg';
import VisaCompanyCards from '@assets/images/companyCards/visa.svg';
@@ -235,5 +236,6 @@ export {
AmexCompanyCards,
MasterCardCompanyCards,
VisaCompanyCards,
+ AmexBlueCompanyCards,
TurtleInShell,
};
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 3d37787038ea..c1b600ffa22d 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -2802,6 +2802,43 @@ export default {
companyCards: {
addCompanyCards: 'Add company cards',
selectCardFeed: 'Select card feed',
+ addCardFeed: 'Add card feed',
+ addNewCard: {
+ cardProviders: {
+ amex: 'American Express Corporate Cards',
+ mastercard: 'Mastercard Commercial Cards',
+ visa: 'Visa Commercial Cards',
+ },
+ yourCardProvider: `Who's your card provider?`,
+ enableFeed: {
+ title: (provider: string) => `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:',
+ visa: `1. Visit [this help article](${CONST.COMPANY_CARDS_HELP}) for detailed instructionson how to set up your Visa Commercial Cards.\n\n2. [Contact your bank](${CONST.COMPANY_CARDS_HELP}) to verify they support a custom feed for your program, and ask them toenable it.\n\n3. *Once the feed is enabled and you have its details, continue to the next screen.*`,
+ amex: `1. Visit [this help article](${CONST.COMPANY_CARDS_HELP}) to find out if American Express can enable a custom feed for your program.\n\n2. Once the feed is enabled, Amex will send you a production letter.\n\n3. *Once you have the feed information, continue to the next screen.*`,
+ mastercard: `1. Visit [this help article](${CONST.COMPANY_CARDS_HELP}) for detailed instructions on how to set up your Mastercard Commercial Cards.\n\n 2. [Contact your bank](${CONST.COMPANY_CARDS_HELP}) to verify they support a custom feed for your program, and ask them to enable it.\n\n3. *Once the feed is enabled and you have its details, continue to the next screen.*`,
+ },
+ whatBankIssuesCard: 'What bank issues these cards?',
+ enterNameOfBank: 'Enter name of bank',
+ feedDetails: {
+ visa: {
+ title: 'What are the Visa feed details?',
+ processorLabel: 'Processor ID',
+ bankLabel: 'Financial institution (bank) ID',
+ companyLabel: 'Company ID',
+ },
+ amex: {
+ title: `What's the Amex delivery file name`,
+ fileNameLabel: 'Delivery file name',
+ },
+ mastercard: {
+ title: `What's the Mastercard distribution ID`,
+ distributionLabel: 'Distribution ID',
+ },
+ },
+ error: {
+ pleaseSelectProvider: 'Please select a card provider before continuing.',
+ },
+ },
assignCard: 'Assign card',
cardNumber: 'Card number',
customFeed: 'Custom feed',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index ff7650e9e02c..6b6b0ec0fe0d 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -2847,6 +2847,44 @@ export default {
companyCards: {
addCompanyCards: 'Agregar tarjetas de empresa',
selectCardFeed: 'Seleccionar feed de tarjetas',
+ addCardFeed: 'Añadir alimentación de tarjeta',
+ addNewCard: {
+ cardProviders: {
+ amex: 'Tarjetas de empresa American Express',
+ mastercard: 'Tarjetas comerciales Mastercard',
+ visa: 'Tarjetas comerciales Visa',
+ },
+ yourCardProvider: `¿Quién es su proveedor de tarjetas?`,
+ enableFeed: {
+ title: (provider: string) => `Habilita tu feed ${provider}`,
+ heading:
+ 'Tenemos una integración directa con el emisor de su tarjeta y podemos importar los datos de sus transacciones a Expensify de forma rápida y precisa.\n\nPara empezar, simplemente:',
+ visa: `1. Visite [este artículo de ayuda](${CONST.COMPANY_CARDS_HELP}) para obtener instrucciones detalladas sobre cómo configurar sus tarjetas comerciales Visa.\n\n2. [Póngase en contacto con su banco](${CONST.COMPANY_CARDS_HELP}) para comprobar que admiten un feed personalizado para su programa, y pídales que lo activen.\n\n3. *Una vez que el feed esté habilitado y tengas sus datos, pasa a la siguiente pantalla.*`,
+ amex: `1. Visite [este artículo de ayuda](${CONST.COMPANY_CARDS_HELP}) para saber si American Express puede habilitar un feed personalizado para su programa.\n\n2. Una vez activada la alimentación, Amex le enviará una carta de producción.\n\n3. *Una vez que tenga la información de alimentación, continúe con la siguiente pantalla.*`,
+ mastercard: `1. Visite [este artículo de ayuda](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS}) para obtener instrucciones detalladas sobre cómo configurar sus tarjetas comerciales Mastercard.\n\n 2. [Póngase en contacto con su banco](${CONST.COMPANY_CARDS_HELP}) para verificar que admiten un feed personalizado para su programa, y pídales que lo habiliten.\n\n3. *Una vez que el feed esté habilitado y tengas sus datos, pasa a la siguiente pantalla.*`,
+ },
+ whatBankIssuesCard: '¿Qué banco emite estas tarjetas?',
+ enterNameOfBank: 'Introduzca el nombre del banco',
+ feedDetails: {
+ visa: {
+ title: '¿Cuáles son los datos de alimentación de Visa?',
+ processorLabel: 'ID del procesador',
+ bankLabel: 'Identificación de la institución financiera (banco)',
+ companyLabel: 'Empresa ID',
+ },
+ amex: {
+ title: `¿Cuál es el nombre del archivo de entrega de Amex?`,
+ fileNameLabel: 'Nombre del archivo de entrega',
+ },
+ mastercard: {
+ title: `¿Cuál es el identificador de distribución de Mastercard?`,
+ distributionLabel: 'ID de distribución',
+ },
+ },
+ error: {
+ pleaseSelectProvider: 'Seleccione un proveedor de tarjetas antes de continuar.',
+ },
+ },
assignCard: 'Asignar tarjeta',
cardNumber: 'Número de la tarjeta',
customFeed: 'Fuente personalizada',
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
index 1b2390b17c39..f69057a4e207 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
@@ -426,6 +426,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/invoices/WorkspaceInvoicingDetailsWebsite').default,
[SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD]: () => require('../../../../pages/workspace/companyCards/assignCard/AssignCardFeedPage').default,
[SCREENS.WORKSPACE.COMPANY_CARDS_SELECT_FEED]: () => require('../../../../pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage').default,
+ [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW]: () => require('../../../../pages/workspace/companyCards/addNew/AddNewCardPage').default,
[SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW]: () => require('../../../../pages/workspace/expensifyCard/issueNew/IssueNewCardPage').default,
[SCREENS.WORKSPACE.EXPENSIFY_CARD_SETTINGS]: () => require('../../../../pages/workspace/expensifyCard/WorkspaceCardSettingsPage').default,
[SCREENS.WORKSPACE.EXPENSIFY_CARD_SETTINGS_ACCOUNT]: () => require('../../../../pages/workspace/expensifyCard/WorkspaceSettlementAccountPage').default,
diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
index ef634d9cb615..ccb5ad2f3f43 100755
--- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
+++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
@@ -164,6 +164,14 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = {
[SCREENS.WORKSPACE.INVOICES]: [SCREENS.WORKSPACE.INVOICES_COMPANY_NAME, SCREENS.WORKSPACE.INVOICES_COMPANY_WEBSITE],
[SCREENS.WORKSPACE.COMPANY_CARDS]: [
SCREENS.WORKSPACE.COMPANY_CARDS_SELECT_FEED,
+ SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW,
+ SCREENS.WORKSPACE.COMPANY_CARDS_TYPE,
+ SCREENS.WORKSPACE.COMPANY_CARDS_INSTRUCTIONS,
+ SCREENS.WORKSPACE.COMPANY_CARDS_NAME,
+ SCREENS.WORKSPACE.COMPANY_CARDS_DETAILS,
+ SCREENS.WORKSPACE.COMPANY_CARDS_SELECT_FEED,
+ SCREENS.WORKSPACE.COMPANY_CARDS_SETTINGS,
+ SCREENS.WORKSPACE.COMPANY_CARDS_SETTINGS_FEED_NAME,
SCREENS.WORKSPACE.COMPANY_CARDS_SETTINGS,
SCREENS.WORKSPACE.COMPANY_CARDS_SETTINGS_FEED_NAME,
SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD,
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 65fb05f8d008..a6c4164ef8e9 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -518,6 +518,9 @@ const config: LinkingOptions['config'] = {
[SCREENS.WORKSPACE.EXPENSIFY_CARD_DETAILS]: {
path: ROUTES.WORKSPACE_EXPENSIFY_CARD_DETAILS.route,
},
+ [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW]: {
+ path: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW.route,
+ },
[SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD]: {
path: ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD.route,
},
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index ee46cbd238ef..c7451e23dff2 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -1155,6 +1155,9 @@ type FullScreenNavigatorParamList = {
[SCREENS.WORKSPACE.COMPANY_CARDS]: {
policyID: string;
};
+ [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW]: {
+ policyID: string;
+ };
[SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD]: {
policyID: string;
feed: string;
diff --git a/src/libs/actions/CompanyCards.ts b/src/libs/actions/CompanyCards.ts
index 1e58ea3b6306..3c3e239d90d7 100644
--- a/src/libs/actions/CompanyCards.ts
+++ b/src/libs/actions/CompanyCards.ts
@@ -1,6 +1,18 @@
import Onyx from 'react-native-onyx';
import ONYXKEYS from '@src/ONYXKEYS';
import type {AssignCard} from '@src/types/onyx/AssignCard';
+import type {AddNewCardFeedData, AddNewCardFeedStep} from '@src/types/onyx/CardFeeds';
+
+type AddNewCompanyCardFlowData = {
+ /** Step to be set in Onyx */
+ step?: AddNewCardFeedStep;
+
+ /** Whether the user is editing step */
+ isEditing?: boolean;
+
+ /** Data required to be sent to issue a new card */
+ data?: Partial;
+};
function setAssignCardStepAndData({data, isEditing, currentStep}: Partial) {
Onyx.merge(ONYXKEYS.ASSIGN_CARD, {data, isEditing, currentStep});
@@ -10,4 +22,15 @@ function clearAssignCardStepAndData() {
Onyx.set(ONYXKEYS.ASSIGN_CARD, {});
}
-export {setAssignCardStepAndData, clearAssignCardStepAndData};
+function setAddNewCompanyCardStepAndData({data, isEditing, step}: AddNewCompanyCardFlowData) {
+ Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, {data, isEditing, currentStep: step});
+}
+
+function clearAddNewCardFlow() {
+ Onyx.set(ONYXKEYS.ADD_NEW_COMPANY_CARD, {
+ currentStep: null,
+ data: {},
+ });
+}
+
+export {setAddNewCompanyCardStepAndData, clearAddNewCardFlow, setAssignCardStepAndData, clearAssignCardStepAndData};
diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardPageEmptyState.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardPageEmptyState.tsx
index 0846c8b9e179..65f054c4e7ce 100644
--- a/src/pages/workspace/companyCards/WorkspaceCompanyCardPageEmptyState.tsx
+++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardPageEmptyState.tsx
@@ -6,8 +6,12 @@ import * as Illustrations from '@components/Icon/Illustrations';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
+import Navigation from '@libs/Navigation/Navigation';
import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading';
+import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading';
import colors from '@styles/theme/colors';
+import * as CompanyCards from '@userActions/CompanyCards';
+import ROUTES from '@src/ROUTES';
const companyCardFeatures: FeatureListItem[] = [
{
@@ -24,14 +28,15 @@ const companyCardFeatures: FeatureListItem[] = [
},
];
-function WorkspaceCompanyCardPageEmptyState() {
+function WorkspaceCompanyCardPageEmptyState({policy}: WithPolicyAndFullscreenLoadingProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const startFlow = useCallback(() => {
- // TODO: Add Card Feed Flow https://github.com/Expensify/App/issues/47376
- }, []);
+ CompanyCards.clearAddNewCardFlow();
+ Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW.getRoute(policy?.id ?? '-1'));
+ }, [policy]);
return (
diff --git a/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx b/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx
new file mode 100644
index 000000000000..8d42b3e8408e
--- /dev/null
+++ b/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx
@@ -0,0 +1,31 @@
+import React from 'react';
+import {useOnyx} from 'react-native-onyx';
+import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import CardInstructionsStep from './CardInstructionsStep';
+import CardNameStep from './CardNameStep';
+import CardTypeStep from './CardTypeStep';
+import DetailsStep from './DetailsStep';
+
+function AddNewCardPage() {
+ const [addNewCardFeed] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD);
+
+ 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 ;
+ }
+}
+
+AddNewCardPage.displayName = 'AddNewCardPage';
+export default withPolicyAndFullscreenLoading(AddNewCardPage);
diff --git a/src/pages/workspace/companyCards/addNew/CardInstructionsStep.tsx b/src/pages/workspace/companyCards/addNew/CardInstructionsStep.tsx
new file mode 100644
index 000000000000..ac1cff597a98
--- /dev/null
+++ b/src/pages/workspace/companyCards/addNew/CardInstructionsStep.tsx
@@ -0,0 +1,78 @@
+import {Str} from 'expensify-common';
+import React from 'react';
+import {View} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
+import Button from '@components/Button';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import RenderHTML from '@components/RenderHTML';
+import ScreenWrapper from '@components/ScreenWrapper';
+import ScrollView from '@components/ScrollView';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useNetwork from '@hooks/useNetwork';
+import useThemeStyles from '@hooks/useThemeStyles';
+import Parser from '@libs/Parser';
+import * as CompanyCards from '@userActions/CompanyCards';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+
+function CardInstructionsStep() {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const {isOffline} = useNetwork();
+
+ const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD);
+
+ const data = addNewCard?.data;
+ const feedProvider = data?.cardType;
+
+ const submit = () => {
+ CompanyCards.setAddNewCompanyCardStepAndData({
+ step: feedProvider === CONST.COMPANY_CARDS.CARD_TYPE.AMEX ? CONST.COMPANY_CARDS.STEP.CARD_DETAILS : CONST.COMPANY_CARDS.STEP.CARD_NAME,
+ });
+ };
+
+ const handleBackButtonPress = () => {
+ CompanyCards.setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.CARD_TYPE});
+ };
+
+ return (
+
+
+
+
+ {translate('workspace.companyCards.addNewCard.enableFeed.title', Str.recapitalize(feedProvider ?? ''))}
+
+ {translate('workspace.companyCards.addNewCard.enableFeed.heading')}
+
+
+
+
+
+
+
+
+ );
+}
+
+CardInstructionsStep.displayName = 'CardInstructionsStep';
+
+export default CardInstructionsStep;
diff --git a/src/pages/workspace/companyCards/addNew/CardNameStep.tsx b/src/pages/workspace/companyCards/addNew/CardNameStep.tsx
new file mode 100644
index 000000000000..d8d6fe10acf4
--- /dev/null
+++ b/src/pages/workspace/companyCards/addNew/CardNameStep.tsx
@@ -0,0 +1,79 @@
+import React from 'react';
+import {useOnyx} from 'react-native-onyx';
+import FormProvider from '@components/Form/FormProvider';
+import InputWrapper from '@components/Form/InputWrapper';
+import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import ScreenWrapper from '@components/ScreenWrapper';
+import Text from '@components/Text';
+import TextInput from '@components/TextInput';
+import useAutoFocusInput from '@hooks/useAutoFocusInput';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as ValidationUtils from '@libs/ValidationUtils';
+import * as CompanyCards from '@userActions/CompanyCards';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import INPUT_IDS from '@src/types/form/AddNewCardFeedForm';
+
+function CardNameStep() {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const {inputCallbackRef} = useAutoFocusInput();
+ const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD);
+
+ const validate = (values: FormOnyxValues): FormInputErrors => {
+ return ValidationUtils.getFieldRequiredErrors(values, [INPUT_IDS.CARD_TITLE]);
+ };
+
+ const submit = (values: FormOnyxValues) => {
+ CompanyCards.setAddNewCompanyCardStepAndData({
+ step: CONST.COMPANY_CARDS.STEP.CARD_DETAILS,
+ data: {
+ cardTitle: values.cardTitle,
+ },
+ isEditing: false,
+ });
+ };
+
+ const handleBackButtonPress = () => {
+ CompanyCards.setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.CARD_INSTRUCTIONS});
+ };
+
+ return (
+
+
+ {translate('workspace.companyCards.addNewCard.whatBankIssuesCard')}
+
+
+
+
+ );
+}
+
+CardNameStep.displayName = 'CardNameStep';
+
+export default CardNameStep;
diff --git a/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx b/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx
new file mode 100644
index 000000000000..7e469fa17093
--- /dev/null
+++ b/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx
@@ -0,0 +1,137 @@
+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 * as Illustrations from '@components/Icon/Illustrations';
+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 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 CardTypeStep() {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD);
+ const [typeSelected, setTypeSelected] = useState>();
+ const [isError, setIsError] = useState(false);
+
+ const submit = () => {
+ if (!typeSelected) {
+ setIsError(true);
+ } else {
+ CompanyCards.setAddNewCompanyCardStepAndData({
+ step: CONST.COMPANY_CARDS.STEP.CARD_INSTRUCTIONS,
+ data: {
+ cardType: typeSelected,
+ },
+ isEditing: false,
+ });
+ }
+ };
+
+ useEffect(() => {
+ setTypeSelected(addNewCard?.data.cardType);
+ }, [addNewCard?.data.cardType]);
+
+ const handleBackButtonPress = () => {
+ Navigation.goBack();
+ };
+
+ const data = [
+ {
+ value: CONST.COMPANY_CARDS.CARD_TYPE.AMEX,
+ text: translate('workspace.companyCards.addNewCard.cardProviders.amex'),
+ keyForList: CONST.COMPANY_CARDS.CARD_TYPE.AMEX,
+ isSelected: typeSelected === CONST.COMPANY_CARDS.CARD_TYPE.AMEX,
+ leftElement: (
+
+ ),
+ },
+ {
+ value: CONST.COMPANY_CARDS.CARD_TYPE.MASTERCARD,
+ text: translate('workspace.companyCards.addNewCard.cardProviders.mastercard'),
+ keyForList: CONST.COMPANY_CARDS.CARD_TYPE.MASTERCARD,
+ isSelected: typeSelected === CONST.COMPANY_CARDS.CARD_TYPE.MASTERCARD,
+ leftElement: (
+
+ ),
+ },
+ {
+ value: CONST.COMPANY_CARDS.CARD_TYPE.VISA,
+ text: translate('workspace.companyCards.addNewCard.cardProviders.visa'),
+ keyForList: CONST.COMPANY_CARDS.CARD_TYPE.VISA,
+ isSelected: typeSelected === CONST.COMPANY_CARDS.CARD_TYPE.VISA,
+ leftElement: (
+
+ ),
+ },
+ ];
+
+ return (
+
+
+
+ {translate('workspace.companyCards.addNewCard.yourCardProvider')}
+ {
+ setTypeSelected(value);
+ setIsError(false);
+ }}
+ sections={[{data}]}
+ shouldSingleExecuteRowSelect
+ initiallyFocusedOptionKey={addNewCard?.data.cardType}
+ shouldUpdateFocusedIndex
+ showConfirmButton
+ confirmButtonText={translate('common.next')}
+ onConfirm={submit}
+ >
+ {isError && (
+
+
+
+ )}
+
+
+ );
+}
+
+CardTypeStep.displayName = 'CardTypeStep';
+
+export default CardTypeStep;
diff --git a/src/pages/workspace/companyCards/addNew/DetailsStep.tsx b/src/pages/workspace/companyCards/addNew/DetailsStep.tsx
new file mode 100644
index 000000000000..93eccbb85a32
--- /dev/null
+++ b/src/pages/workspace/companyCards/addNew/DetailsStep.tsx
@@ -0,0 +1,162 @@
+import React, {useCallback} from 'react';
+import {useOnyx} from 'react-native-onyx';
+import FormProvider from '@components/Form/FormProvider';
+import InputWrapper from '@components/Form/InputWrapper';
+import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import ScreenWrapper from '@components/ScreenWrapper';
+import ScrollView from '@components/ScrollView';
+import Text from '@components/Text';
+import TextInput from '@components/TextInput';
+import useAutoFocusInput from '@hooks/useAutoFocusInput';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as ValidationUtils from '@libs/ValidationUtils';
+import * as CompanyCards from '@userActions/CompanyCards';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import INPUT_IDS from '@src/types/form/AddNewCardFeedForm';
+
+function DetailsStep() {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const {inputCallbackRef} = useAutoFocusInput();
+
+ const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD);
+ const feedProvider = addNewCard?.data.cardType;
+ const submit = () => {};
+
+ const handleBackButtonPress = () => {
+ if (feedProvider === CONST.COMPANY_CARDS.CARD_TYPE.AMEX) {
+ CompanyCards.setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.CARD_INSTRUCTIONS});
+ return;
+ }
+ CompanyCards.setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.CARD_NAME});
+ };
+
+ const validate = useCallback(
+ (values: FormOnyxValues): FormInputErrors => {
+ const errors = ValidationUtils.getFieldRequiredErrors(values, [INPUT_IDS.BANK_ID]);
+
+ switch (feedProvider) {
+ case CONST.COMPANY_CARDS.CARD_TYPE.VISA:
+ if (!values[INPUT_IDS.BANK_ID]) {
+ errors[INPUT_IDS.BANK_ID] = translate('common.error.fieldRequired');
+ }
+ if (!values[INPUT_IDS.PROCESSOR_ID]) {
+ errors[INPUT_IDS.PROCESSOR_ID] = translate('common.error.fieldRequired');
+ }
+ if (!values[INPUT_IDS.COMPANY_ID]) {
+ errors[INPUT_IDS.COMPANY_ID] = translate('common.error.fieldRequired');
+ }
+ break;
+ case CONST.COMPANY_CARDS.CARD_TYPE.MASTERCARD:
+ if (!values[INPUT_IDS.DISTRIBUTION_ID]) {
+ errors[INPUT_IDS.DISTRIBUTION_ID] = translate('common.error.fieldRequired');
+ }
+ break;
+ case CONST.COMPANY_CARDS.CARD_TYPE.AMEX:
+ if (!values[INPUT_IDS.DELIVERY_FILE_NAME]) {
+ errors[INPUT_IDS.DELIVERY_FILE_NAME] = translate('common.error.fieldRequired');
+ }
+ break;
+ default:
+ break;
+ }
+ return errors;
+ },
+ [feedProvider, translate],
+ );
+
+ const renderInputs = () => {
+ switch (feedProvider) {
+ case CONST.COMPANY_CARDS.CARD_TYPE.VISA:
+ return (
+ <>
+
+
+
+ >
+ );
+ case CONST.COMPANY_CARDS.CARD_TYPE.MASTERCARD:
+ return (
+
+ );
+ case CONST.COMPANY_CARDS.CARD_TYPE.AMEX:
+ return (
+
+ );
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
+
+
+ {feedProvider ? translate(`workspace.companyCards.addNewCard.feedDetails.${feedProvider}.title`) : ''}
+
+
+ {renderInputs()}
+
+
+
+ );
+}
+
+DetailsStep.displayName = 'DetailsStep';
+
+export default DetailsStep;
diff --git a/src/types/form/AddNewCardFeedForm.ts b/src/types/form/AddNewCardFeedForm.ts
new file mode 100644
index 000000000000..95c7496d8d5a
--- /dev/null
+++ b/src/types/form/AddNewCardFeedForm.ts
@@ -0,0 +1,28 @@
+import type {ValueOf} from 'type-fest';
+import type Form from './Form';
+
+const INPUT_IDS = {
+ CARD_TITLE: 'cardTitle',
+ PROCESSOR_ID: 'processorID',
+ BANK_ID: 'bankID',
+ COMPANY_ID: 'companyID',
+ DISTRIBUTION_ID: 'distributionID',
+ DELIVERY_FILE_NAME: 'deliveryFileName',
+} as const;
+
+type InputID = ValueOf;
+
+type AddNewCardFeedForm = Form<
+ InputID,
+ {
+ [INPUT_IDS.CARD_TITLE]: string;
+ [INPUT_IDS.PROCESSOR_ID]: string;
+ [INPUT_IDS.BANK_ID]: string;
+ [INPUT_IDS.COMPANY_ID]: string;
+ [INPUT_IDS.DISTRIBUTION_ID]: string;
+ [INPUT_IDS.DELIVERY_FILE_NAME]: string;
+ }
+>;
+
+export type {AddNewCardFeedForm};
+export default INPUT_IDS;
diff --git a/src/types/form/index.ts b/src/types/form/index.ts
index 23903526c89d..1fb253994795 100644
--- a/src/types/form/index.ts
+++ b/src/types/form/index.ts
@@ -12,6 +12,7 @@ export type {HomeAddressForm} from './HomeAddressForm';
export type {IKnowTeacherForm} from './IKnowTeacherForm';
export type {IntroSchoolPrincipalForm} from './IntroSchoolPrincipalForm';
export type {IssueNewExpensifyCardForm} from './IssueNewExpensifyCardForm';
+export type {AddNewCardFeedForm} from './AddNewCardFeedForm';
export type {EditExpensifyCardNameForm} from './EditExpensifyCardNameForm';
export type {LegalNameForm} from './LegalNameForm';
export type {MoneyRequestAmountForm} from './MoneyRequestAmountForm';
diff --git a/src/types/onyx/CardFeeds.ts b/src/types/onyx/CardFeeds.ts
index 59d93a192cf8..35f69ded32e9 100644
--- a/src/types/onyx/CardFeeds.ts
+++ b/src/types/onyx/CardFeeds.ts
@@ -1,3 +1,6 @@
+import type {ValueOf} from 'type-fest';
+import type CONST from '@src/CONST';
+
/** Card feed data */
type CardFeedData = {
/** Whether any actions are pending */
@@ -31,4 +34,32 @@ type CardFeeds = {
companyCardNicknames: Record;
};
+/** Data required to be sent to add a new card */
+type AddNewCardFeedData = {
+ /** The email address of the cardholder */
+ assigneeEmail: string;
+
+ /** Card type */
+ cardType: ValueOf;
+
+ /** Name of the card */
+ cardTitle: string;
+};
+
+/** Issue new card flow steps */
+type AddNewCardFeedStep = ValueOf;
+
+/** Model of Issue new card flow */
+type AddNewCompanyCardFeed = {
+ /** The current step of the flow */
+ currentStep: AddNewCardFeedStep;
+
+ /** Data required to be sent to issue a new card */
+ data: AddNewCardFeedData;
+
+ /** Whether the user is editing step */
+ isEditing: boolean;
+};
+
export default CardFeeds;
+export type {AddNewCardFeedStep, AddNewCompanyCardFeed, AddNewCardFeedData};
diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts
index c651d53b9cf8..a44aacfb679f 100644
--- a/src/types/onyx/index.ts
+++ b/src/types/onyx/index.ts
@@ -12,6 +12,7 @@ import type CancellationDetails from './CancellationDetails';
import type Card from './Card';
import type {CardList, IssueNewCard, WorkspaceCardsList} from './Card';
import type CardFeeds from './CardFeeds';
+import type {AddNewCompanyCardFeed} from './CardFeeds';
import type {CapturedLogs, Log} from './Console';
import type Credentials from './Credentials';
import type Currency from './Currency';
@@ -131,6 +132,7 @@ export type {
IntroSelected,
IOU,
IssueNewCard,
+ AddNewCompanyCardFeed,
LastExportMethod,
Locale,
Login,