diff --git a/assets/images/simple-illustrations/emptystate__puzzlepieces.svg b/assets/images/simple-illustrations/emptystate__puzzlepieces.svg new file mode 100644 index 000000000000..d137ce5dcff2 --- /dev/null +++ b/assets/images/simple-illustrations/emptystate__puzzlepieces.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CONST.ts b/src/CONST.ts index ac42732f2325..cff89c5da642 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2581,6 +2581,11 @@ const CONST = { INDIVIDUAL: 'individual', NONE: 'none', }, + VERIFICATION_STATE: { + LOADING: 'loading', + VERIFIED: 'verified', + ON_WAITLIST: 'onWaitlist', + }, STATE: { STATE_NOT_ISSUED: 2, OPEN: 3, diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index 18ae1792686f..0efb65ed7a61 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -54,6 +54,7 @@ import ThreeLeggedLaptopWoman from '@assets/images/product-illustrations/three_l import ToddBehindCloud from '@assets/images/product-illustrations/todd-behind-cloud.svg'; import ToddWithPhones from '@assets/images/product-illustrations/todd-with-phones.svg'; import BigVault from '@assets/images/simple-illustrations/emptystate__big-vault.svg'; +import Puzzle from '@assets/images/simple-illustrations/emptystate__puzzlepieces.svg'; import Abacus from '@assets/images/simple-illustrations/simple-illustration__abacus.svg'; import Accounting from '@assets/images/simple-illustrations/simple-illustration__accounting.svg'; import Alert from '@assets/images/simple-illustrations/simple-illustration__alert.svg'; @@ -200,6 +201,7 @@ export { TrashCan, TeleScope, Profile, + Puzzle, PalmTree, LockClosed, Gears, diff --git a/src/languages/en.ts b/src/languages/en.ts index 5fb71704e9cd..d7f8623dcf15 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3212,6 +3212,16 @@ const translations = { issuedCardNoShippingDetails: ({assignee}: AssigneeParams) => `issued ${assignee} an Expensify Card! The card will be shipped once shipping details are added.`, issuedCardVirtual: ({assignee, link}: IssueVirtualCardParams) => `issued ${assignee} a virtual ${link}! The card can be used right away.`, addedShippingDetails: ({assignee}: AssigneeParams) => `${assignee} added shipping details. Expensify Card will arrive in 2-3 business days.`, + verifyingHeader: 'Verifying', + bankAccountVerifiedHeader: 'Bank account verified', + verifyingBankAccount: 'Verifying bank account...', + verifyingBankAccountDescription: 'Please wait while we confirm that this account can be used to issue Expensify Cards.', + bankAccountVerified: 'Bank account verified!', + bankAccountVerifiedDescription: 'You can now issue Expensify Cards to your workspace members.', + oneMoreStep: 'One more step...', + oneMoreStepDescription: 'Looks like we need to manually verify your bank account. Please head on over to Concierge where your instructions are waiting for you.', + gotIt: 'Got it', + goToConcierge: 'Go to Concierge', }, categories: { deleteCategories: 'Delete categories', diff --git a/src/languages/es.ts b/src/languages/es.ts index ae6e9551dd0e..7c690d2bf679 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3253,6 +3253,16 @@ const translations = { issuedCardNoShippingDetails: ({assignee}: AssigneeParams) => `¡emitió a ${assignee} una Tarjeta Expensify! La tarjeta se enviará una vez que se agreguen los detalles de envío.`, issuedCardVirtual: ({assignee, link}: IssueVirtualCardParams) => `¡emitió a ${assignee} una ${link} virtual! La tarjeta puede utilizarse inmediatamente.`, addedShippingDetails: ({assignee}: AssigneeParams) => `${assignee} agregó los detalles de envío. La Tarjeta Expensify llegará en 2-3 días hábiles.`, + verifyingHeader: 'Verificando', + bankAccountVerifiedHeader: 'Cuenta bancaria verificada', + verifyingBankAccount: 'Verificando cuenta bancaria...', + verifyingBankAccountDescription: 'Por favor, espere mientras confirmamos que esta cuenta se puede utilizar para emitir tarjetas Expensify.', + bankAccountVerified: '¡Cuenta bancaria verificada!', + bankAccountVerifiedDescription: 'Ahora puedes emitir tarjetas de Expensify para los miembros de tu espacio de trabajo.', + oneMoreStep: 'Un paso más', + oneMoreStepDescription: 'Parece que tenemos que verificar manualmente tu cuenta bancaria. Dirígete a Concierge, donde te esperan las instrucciones.', + gotIt: 'Entendido', + goToConcierge: 'Ir a Concierge', }, categories: { deleteCategories: 'Eliminar categorías', diff --git a/src/libs/actions/Card.ts b/src/libs/actions/Card.ts index d588e3195588..2a9faa02b261 100644 --- a/src/libs/actions/Card.ts +++ b/src/libs/actions/Card.ts @@ -300,6 +300,7 @@ function updateSettlementAccount(workspaceAccountID: number, policyID: string, s key: `${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`, value: { paymentBankAccountID: settlementBankAccountID, + isLoading: true, }, }, ]; @@ -310,6 +311,7 @@ function updateSettlementAccount(workspaceAccountID: number, policyID: string, s key: `${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`, value: { paymentBankAccountID: settlementBankAccountID, + isLoading: false, }, }, ]; @@ -320,6 +322,8 @@ function updateSettlementAccount(workspaceAccountID: number, policyID: string, s key: `${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`, value: { paymentBankAccountID: currentSettlementBankAccountID, + isLoading: false, + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), }, }, ]; @@ -614,12 +618,52 @@ function configureExpensifyCardsForPolicy(policyID: string, bankAccountID?: numb return; } + const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policyID); + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`, + value: { + isLoading: true, + isSuccess: false, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`, + value: { + isLoading: false, + isSuccess: true, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`, + value: { + isLoading: false, + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), + isSuccess: false, + }, + }, + ]; + const parameters = { policyID, bankAccountID, }; - API.write(WRITE_COMMANDS.CONFIGURE_EXPENSIFY_CARDS_FOR_POLICY, parameters); + API.write(WRITE_COMMANDS.CONFIGURE_EXPENSIFY_CARDS_FOR_POLICY, parameters, { + optimisticData, + successData, + failureData, + }); } function issueExpensifyCard(policyID: string, feedCountry: string, data?: IssueNewCardData) { diff --git a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardBankAccounts.tsx b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardBankAccounts.tsx index b8b8776df048..f296b16c13f6 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardBankAccounts.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardBankAccounts.tsx @@ -2,9 +2,13 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; +import BlockingView from '@components/BlockingViews/BlockingView'; +import Button from '@components/Button'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import getBankIcon from '@components/Icon/BankIcons'; import * as Expensicons from '@components/Icon/Expensicons'; +import * as Illustrations from '@components/Icon/Illustrations'; +import LottieAnimations from '@components/LottieAnimations'; import MenuItem from '@components/MenuItem'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; @@ -16,6 +20,7 @@ import * as PolicyUtils from '@libs/PolicyUtils'; import Navigation from '@navigation/Navigation'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import variables from '@styles/variables'; import * as Card from '@userActions/Card'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -35,14 +40,30 @@ function WorkspaceExpensifyCardBankAccounts({route}: WorkspaceExpensifyCardBankA const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policyID); + const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`); + const [cardOnWaitlist] = useOnyx(`${ONYXKEYS.COLLECTION.NVP_EXPENSIFY_ON_CARD_WAITLIST}${policyID}`); + + const getVerificationState = () => { + if (cardOnWaitlist) { + return CONST.EXPENSIFY_CARD.VERIFICATION_STATE.ON_WAITLIST; + } + if (cardSettings?.isSuccess) { + return CONST.EXPENSIFY_CARD.VERIFICATION_STATE.VERIFIED; + } + + if (cardSettings?.isLoading) { + return CONST.EXPENSIFY_CARD.VERIFICATION_STATE.LOADING; + } + + return ''; + }; + const handleAddBankAccount = () => { Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute('new', policyID, ROUTES.WORKSPACE_EXPENSIFY_CARD.getRoute(policyID))); }; const handleSelectBankAccount = (value?: number) => { Card.configureExpensifyCardsForPolicy(policyID, value); - Card.updateSettlementAccount(workspaceAccountID, policyID, value); - Navigation.navigate(ROUTES.WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW.getRoute(policyID)); }; const renderBankOptions = () => { @@ -75,6 +96,82 @@ function WorkspaceExpensifyCardBankAccounts({route}: WorkspaceExpensifyCardBankA }); }; + const verificationState = getVerificationState(); + const isInVerificationState = !!verificationState; + + const renderVerificationStateView = () => { + switch (verificationState) { + case CONST.EXPENSIFY_CARD.VERIFICATION_STATE.LOADING: + return ( + + ); + case CONST.EXPENSIFY_CARD.VERIFICATION_STATE.ON_WAITLIST: + return ( + <> + +