From abe46716a818ea700173115c9cd73efffd65a10d Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 11 Nov 2024 23:55:26 +0530 Subject: [PATCH 01/54] Implemented International Bank Account flow --- src/CONST.ts | 44 ++++ src/ONYXKEYS.ts | 7 + src/ROUTES.ts | 1 + src/SCREENS.ts | 1 + src/components/CurrencyPicker.tsx | 81 ++++++++ .../SelectionList/BaseSelectionList.tsx | 4 +- src/components/SelectionList/types.ts | 3 + .../useInternationalBankAccountFormSubmit.ts | 27 +++ src/hooks/useSubStep/index.ts | 49 +++-- src/hooks/useSubStep/types.ts | 6 + .../GetCorpayBankAccountFieldsParams.ts | 6 + src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + .../ModalStackNavigators/index.tsx | 3 +- src/libs/Navigation/linkingConfig/config.ts | 4 + src/libs/Navigation/types.ts | 1 + src/libs/actions/BankAccounts.ts | 36 ++++ .../InternationalDepositAccountContent.tsx | 132 ++++++++++++ .../InternationalDepositAccount/index.tsx | 35 ++++ .../substeps/AccountHolderInformation.tsx | 120 +++++++++++ .../substeps/AccountType.tsx | 71 +++++++ .../substeps/BankAccountDetails.tsx | 99 +++++++++ .../substeps/BankInformation.tsx | 115 +++++++++++ .../substeps/Confirmation.tsx | 98 +++++++++ .../substeps/CountrySelection.tsx | 90 ++++++++ .../substeps/Success.tsx | 22 ++ .../InternationalDepositAccount/types.ts | 20 ++ .../InternationalDepositAccount/utils.ts | 103 +++++++++ .../form/InternationalBankAccountForm.ts | 6 + src/types/form/index.ts | 1 + src/types/onyx/BankAccount.ts | 6 + src/types/onyx/CorpayFields.ts | 135 ++++++++++++ src/types/onyx/index.ts | 3 + tests/unit/useSubStepTest.tsx | 195 ++++++++++++++++++ 34 files changed, 1513 insertions(+), 14 deletions(-) create mode 100644 src/components/CurrencyPicker.tsx create mode 100644 src/hooks/useInternationalBankAccountFormSubmit.ts create mode 100644 src/libs/API/parameters/GetCorpayBankAccountFieldsParams.ts create mode 100644 src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx create mode 100644 src/pages/settings/Wallet/InternationalDepositAccount/index.tsx create mode 100644 src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx create mode 100644 src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountType.tsx create mode 100644 src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx create mode 100644 src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx create mode 100644 src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx create mode 100644 src/pages/settings/Wallet/InternationalDepositAccount/substeps/CountrySelection.tsx create mode 100644 src/pages/settings/Wallet/InternationalDepositAccount/substeps/Success.tsx create mode 100644 src/pages/settings/Wallet/InternationalDepositAccount/types.ts create mode 100644 src/pages/settings/Wallet/InternationalDepositAccount/utils.ts create mode 100644 src/types/form/InternationalBankAccountForm.ts create mode 100644 src/types/onyx/CorpayFields.ts diff --git a/src/CONST.ts b/src/CONST.ts index d9c3b72d2d1a..ed6ebe21c45f 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -6230,6 +6230,50 @@ const CONST = { PAID_ADOPTION: 'paid_adoption', }, }, + + CORPAY_FIELDS: { + BANK_ACCOUNT_DETAILS_FIELDS: ['accountNumber', 'localAccountNumber', 'routingCode', 'localRoutingCode', 'swiftBicCode'], + ACCOUNT_TYPE_KEY: 'BeneficiaryAccountType', + BANK_INFORMATION_FIELDS: ['bankName', 'bankRegion', 'bankCity', 'bankAddressLine1', 'bankAddressLine2', 'bankPostal', 'BeneficiaryBankBranchName'], + ACCOUNT_HOLDER_FIELDS: [ + 'accountHolderName', + 'accountHolderCountry', + 'accountHolderRegion', + 'accountHolderAddress1', + 'accountHolderAddress2', + 'accountHolderCity', + 'accountHolderPostal', + 'accountHolderPhoneNumber', + 'accountHolderEmail', + 'ContactName', + 'BeneficiaryCPF', + 'BeneficiaryRUT', + 'BeneficiaryCedulaID', + 'BeneficiaryTaxID', + ], + SPECIAL_LIST_REGION_KEYS: ['bankRegion', 'accountHolderRegion'] as string[], + SPECIAL_LIST_ADDRESS_KEYS: ['bankAddressLine1', 'accountHolderAddress1'] as string[], + STEPS_NAME: { + COUNTRY_SELECTOR: 'CountrySelector', + BANK_ACCOUNT_DETAILS: 'BankAccountDetails', + ACCOUNT_TYPE: 'AccountType', + BANK_INFORMATION: 'BankInformation', + ACCOUNT_HOLDER_INFORMATION: 'AccountHolderInformation', + CONFIRMATION: 'Confirmation', + SUCCESS: 'Success', + }, + INDEXES: { + MAPPING: { + COUNTRY_SELECTOR: 0, + BANK_ACCOUNT_DETAILS: 1, + ACCOUNT_TYPE: 2, + BANK_INFORMATION: 3, + ACCOUNT_HOLDER_INFORMATION: 4, + CONFIRMATION: 5, + SUCCESS: 6, + }, + }, + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index c5ec21b8b1c2..5728fcfb685f 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -458,6 +458,9 @@ const ONYXKEYS = { /** The user's Concierge reportID */ CONCIERGE_REPORT_ID: 'conciergeReportID', + /** Corpay Fields while doing international bank account connection */ + CORPAY_FIELDS: 'corpayFields', + /** Collection Keys */ COLLECTION: { DOWNLOAD: 'download_', @@ -594,6 +597,8 @@ const ONYXKEYS = { HOME_ADDRESS_FORM_DRAFT: 'homeAddressFormDraft', PERSONAL_DETAILS_FORM: 'personalDetailsForm', PERSONAL_DETAILS_FORM_DRAFT: 'personalDetailsFormDraft', + INTERNATIONAL_BANK_ACCOUNT_FORM: 'internationalBankAccountForm', + INTERNATIONAL_BANK_ACCOUNT_FORM_DRAFT: 'internationalBankAccountFormDraft', NEW_ROOM_FORM: 'newRoomForm', NEW_ROOM_FORM_DRAFT: 'newRoomFormDraft', ROOM_SETTINGS_FORM: 'roomSettingsForm', @@ -817,6 +822,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.DEBUG_REPORT_PAGE_FORM]: FormTypes.DebugReportForm; [ONYXKEYS.FORMS.DEBUG_REPORT_ACTION_PAGE_FORM]: FormTypes.DebugReportActionForm; [ONYXKEYS.FORMS.DEBUG_DETAILS_FORM]: FormTypes.DebugReportForm | FormTypes.DebugReportActionForm; + [ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM]: FormTypes.InternationalBankAccountForm; }; type OnyxFormDraftValuesMapping = { @@ -1029,6 +1035,7 @@ type OnyxValuesMapping = { [ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP]: boolean; [ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES]: Record; [ONYXKEYS.CONCIERGE_REPORT_ID]: string; + [ONYXKEYS.CORPAY_FIELDS]: OnyxTypes.CorpayFields; }; type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index cd94035e0fff..dcfeeda6a184 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -191,6 +191,7 @@ const ROUTES = { }, SETTINGS_ADD_DEBIT_CARD: 'settings/wallet/add-debit-card', SETTINGS_ADD_BANK_ACCOUNT: 'settings/wallet/add-bank-account', + SETTINGS_ADD_US_BANK_ACCOUNT: 'settings/wallet/add-us-bank-account', SETTINGS_ENABLE_PAYMENTS: 'settings/wallet/enable-payments', SETTINGS_WALLET_CARD_DIGITAL_DETAILS_UPDATE_ADDRESS: { route: 'settings/wallet/card/:domain/digital-details/update-address', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 9b8fe54111cf..6ccada6d86d2 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -62,6 +62,7 @@ const SCREENS = { ADD_DEBIT_CARD: 'Settings_Add_Debit_Card', ADD_PAYMENT_CARD_CHANGE_CURRENCY: 'Settings_Add_Payment_Card_Change_Currency', ADD_BANK_ACCOUNT: 'Settings_Add_Bank_Account', + ADD_US_BANK_ACCOUNT: 'Settings_Add_US_Bank_Account', CLOSE: 'Settings_Close', TWO_FACTOR_AUTH: 'Settings_TwoFactorAuth', REPORT_CARD_LOST_OR_DAMAGED: 'Settings_ReportCardLostOrDamaged', diff --git a/src/components/CurrencyPicker.tsx b/src/components/CurrencyPicker.tsx new file mode 100644 index 000000000000..56735d5b3505 --- /dev/null +++ b/src/components/CurrencyPicker.tsx @@ -0,0 +1,81 @@ +import React, {useState} from 'react'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import CONST from '@src/CONST'; +import CurrencySelectionList from './CurrencySelectionList'; +import type {CurrencyListItem} from './CurrencySelectionList/types'; +import HeaderWithBackButton from './HeaderWithBackButton'; +import MenuItemWithTopDescription from './MenuItemWithTopDescription'; +import Modal from './Modal'; +import ScreenWrapper from './ScreenWrapper'; + +type CurrencyPickerProps = { + /** Current value of the selected item */ + value?: string; + + /** Callback when the list item is selected */ + onInputChange?: (value: string, key?: string) => void; + + /** Form Error description */ + errorText?: string; +}; + +function CurrencyPicker({value, errorText, onInputChange = () => {}}: CurrencyPickerProps) { + const {translate} = useLocalize(); + const [isPickerVisible, setIsPickerVisible] = useState(false); + const styles = useThemeStyles(); + + const hidePickerModal = () => { + setIsPickerVisible(false); + }; + + const updateInput = (item: CurrencyListItem) => { + onInputChange?.(item.currencyCode); + hidePickerModal(); + }; + + return ( + <> + setIsPickerVisible(true)} + brickRoadIndicator={errorText ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + errorText={errorText} + /> + + + + + + + + ); +} + +CurrencyPicker.displayName = 'CurrencyPicker'; +export default CurrencyPicker; diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index b7bef18896d1..72b136c15619 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -67,6 +67,7 @@ function BaseSelectionList( showScrollIndicator = true, showLoadingPlaceholder = false, showConfirmButton = false, + isConfirmButtonDisabled = false, shouldPreventDefaultFocusOnSelectRow = false, containerStyle, sectionListStyle, @@ -744,7 +745,7 @@ function BaseSelectionList( { captureOnInputs: true, shouldBubble: !flattenedSections.allOptions.at(focusedIndex) || focusedIndex === -1, - isActive: !disableKeyboardShortcuts && isFocused, + isActive: !disableKeyboardShortcuts && isFocused && !isConfirmButtonDisabled, }, ); @@ -826,6 +827,7 @@ function BaseSelectionList( onPress={onConfirm} pressOnEnter enterKeyEventListenerPriority={1} + isDisabled={isConfirmButtonDisabled} /> )} diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 4c7fd330ec18..b4bdd63e5521 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -476,6 +476,9 @@ type BaseSelectionListProps = Partial & { /** Whether to show the default confirm button */ showConfirmButton?: boolean; + /** Whether to show the default confirm button disabled */ + isConfirmButtonDisabled?: boolean; + /** Whether tooltips should be shown */ shouldShowTooltips?: boolean; diff --git a/src/hooks/useInternationalBankAccountFormSubmit.ts b/src/hooks/useInternationalBankAccountFormSubmit.ts new file mode 100644 index 000000000000..6042bd165070 --- /dev/null +++ b/src/hooks/useInternationalBankAccountFormSubmit.ts @@ -0,0 +1,27 @@ +import type {FormOnyxKeys} from '@components/Form/types'; +import type {OnyxFormKey} from '@src/ONYXKEYS'; +import ONYXKEYS from '@src/ONYXKEYS'; +import useStepFormSubmit from './useStepFormSubmit'; +import type {SubStepProps} from './useSubStep/types'; + +type UseInternationalBankAccountFormSubmitParams = Pick & { + formId?: OnyxFormKey; + fieldIds: Array>; + shouldSaveDraft: boolean; +}; + +/** + * Hook for handling submit method in Missing Personal Details substeps. + * When user is in editing mode, we should save values only when user confirms the change + * @param onNext - callback + * @param fieldIds - field IDs for particular step + * @param shouldSaveDraft - if we should save draft values + */ +export default function useInternationalBankAccountFormSubmit({onNext, fieldIds, shouldSaveDraft}: UseInternationalBankAccountFormSubmitParams) { + return useStepFormSubmit({ + formId: ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM, + onNext, + fieldIds, + shouldSaveDraft, + }); +} diff --git a/src/hooks/useSubStep/index.ts b/src/hooks/useSubStep/index.ts index e59e18cf85b5..a8518060b3b5 100644 --- a/src/hooks/useSubStep/index.ts +++ b/src/hooks/useSubStep/index.ts @@ -1,48 +1,72 @@ import type {ComponentType} from 'react'; -import {useCallback, useRef, useState} from 'react'; +import {useCallback, useMemo, useRef, useState} from 'react'; import type {SubStepProps, UseSubStep} from './types'; +function calculateLastIndex(bodyContentLength: number, skipSteps: number[] = []) { + let lastIndex = bodyContentLength - 1; + while (skipSteps.includes(lastIndex)) { + lastIndex -= 1; + } + + return lastIndex; +} + /** * This hook ensures uniform handling of components across different screens, enabling seamless integration and navigation through sub steps of the VBBA flow. * @param bodyContent - array of components to display in particular step * @param onFinished - callback triggered after finish last step * @param startFrom - initial index for bodyContent array * @param onNextSubStep - callback triggered after finish each step + * @param skipSteps - array of indexes to skip */ -export default function useSubStep({bodyContent, onFinished, startFrom = 0, onNextSubStep = () => {}}: UseSubStep) { +export default function useSubStep({bodyContent, onFinished, startFrom = 0, skipSteps = [], onNextSubStep = () => {}}: UseSubStep) { const [screenIndex, setScreenIndex] = useState(startFrom); const isEditing = useRef(false); + if (bodyContent.length === skipSteps.length) { + throw new Error('All steps are skipped'); + } + + const lastScreenIndex = useMemo(() => calculateLastIndex(bodyContent.length, skipSteps), [bodyContent.length, skipSteps]); + const prevScreen = useCallback(() => { - const prevScreenIndex = screenIndex - 1; + let decrementNuber = 1; + while (screenIndex - decrementNuber >= 0 && skipSteps.includes(screenIndex - decrementNuber)) { + decrementNuber += 1; + } + const prevScreenIndex = screenIndex - decrementNuber; if (prevScreenIndex < 0) { return; } setScreenIndex(prevScreenIndex); - }, [screenIndex]); + }, [screenIndex, skipSteps]); const nextScreen = useCallback( (finishData?: unknown) => { if (isEditing.current) { isEditing.current = false; - setScreenIndex(bodyContent.length - 1); + setScreenIndex(lastScreenIndex); return; } - const nextScreenIndex = screenIndex + 1; + let incrementNumber = 1; + while (screenIndex + incrementNumber < lastScreenIndex && skipSteps.includes(screenIndex + incrementNumber)) { + incrementNumber += 1; + } + const nextScreenIndex = screenIndex + incrementNumber; - if (nextScreenIndex === bodyContent.length) { + if (nextScreenIndex === lastScreenIndex + 1) { onFinished(finishData); } else { onNextSubStep(); setScreenIndex(nextScreenIndex); } }, - [screenIndex, bodyContent.length, onFinished, onNextSubStep], + [screenIndex, lastScreenIndex, skipSteps, onFinished, onNextSubStep], ); const moveTo = useCallback((step: number) => { @@ -50,14 +74,15 @@ export default function useSubStep({bodyContent, on setScreenIndex(step); }, []); - const resetScreenIndex = useCallback(() => { - setScreenIndex(0); + const resetScreenIndex = useCallback((newScreenIndex = 0) => { + isEditing.current = false; + setScreenIndex(newScreenIndex); }, []); const goToTheLastStep = useCallback(() => { isEditing.current = false; - setScreenIndex(bodyContent.length - 1); - }, [bodyContent]); + setScreenIndex(lastScreenIndex); + }, [lastScreenIndex]); // eslint-disable-next-line react-compiler/react-compiler return { diff --git a/src/hooks/useSubStep/types.ts b/src/hooks/useSubStep/types.ts index 603534e68c15..a4d28265b7f3 100644 --- a/src/hooks/useSubStep/types.ts +++ b/src/hooks/useSubStep/types.ts @@ -15,6 +15,9 @@ type SubStepProps = { /** moves user to previous sub step */ prevScreen?: () => void; + + /** resets screen index to passed value */ + resetScreenIndex?: (index?: number) => void; }; type UseSubStep = { @@ -29,6 +32,9 @@ type UseSubStep = { /** index of initial sub step to display */ startFrom?: number; + + /** array of indexes to skip */ + skipSteps?: number[]; }; export type {SubStepProps, UseSubStep}; diff --git a/src/libs/API/parameters/GetCorpayBankAccountFieldsParams.ts b/src/libs/API/parameters/GetCorpayBankAccountFieldsParams.ts new file mode 100644 index 000000000000..fbce6c1529a7 --- /dev/null +++ b/src/libs/API/parameters/GetCorpayBankAccountFieldsParams.ts @@ -0,0 +1,6 @@ +type GetCorpayBankAccountFieldsParams = { + countryISO: string; + currency?: string; +}; + +export default GetCorpayBankAccountFieldsParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index fb5558fb0350..2f8ef1182e7a 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -349,3 +349,4 @@ export type {default as UpdateQuickbooksDesktopCompanyCardExpenseAccountTypePara export type {default as TogglePolicyPerDiemParams} from './TogglePolicyPerDiemParams'; export type {default as OpenPolicyPerDiemRatesPageParams} from './OpenPolicyPerDiemRatesPageParams'; export type {default as TogglePlatformMuteParams} from './TogglePlatformMuteParams'; +export type {default as GetCorpayBankAccountFieldsParams} from './GetCorpayBankAccountFieldsParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index b8b4bb749701..ccd808fa14ea 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -438,6 +438,7 @@ const WRITE_COMMANDS = { SELF_TOUR_VIEWED: 'SelfTourViewed', UPDATE_INVOICE_COMPANY_NAME: 'UpdateInvoiceCompanyName', UPDATE_INVOICE_COMPANY_WEBSITE: 'UpdateInvoiceCompanyWebsite', + GET_CORPAY_BANK_ACCOUNT_FIELDS: 'GetCorpayBankAccountFields', } as const; type WriteCommand = ValueOf; @@ -762,6 +763,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UPDATE_SUBSCRIPTION_ADD_NEW_USERS_AUTOMATICALLY]: Parameters.UpdateSubscriptionAddNewUsersAutomaticallyParams; [WRITE_COMMANDS.UPDATE_SUBSCRIPTION_SIZE]: Parameters.UpdateSubscriptionSizeParams; [WRITE_COMMANDS.REQUEST_TAX_EXEMPTION]: null; + [WRITE_COMMANDS.GET_CORPAY_BANK_ACCOUNT_FIELDS]: Parameters.GetCorpayBankAccountFieldsParams; [WRITE_COMMANDS.DELETE_MONEY_REQUEST_ON_SEARCH]: Parameters.DeleteMoneyRequestOnSearchParams; [WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH]: Parameters.HoldMoneyRequestOnSearchParams; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 8a64424c8f7d..a4f09dbe0d7c 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -245,7 +245,8 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/EnablePayments/EnablePayments').default, [SCREENS.SETTINGS.WALLET.VERIFY_ACCOUNT]: () => require('../../../../pages/settings/Wallet/VerifyAccountPage').default, [SCREENS.SETTINGS.ADD_DEBIT_CARD]: () => require('../../../../pages/settings/Wallet/AddDebitCardPage').default, - [SCREENS.SETTINGS.ADD_BANK_ACCOUNT]: () => require('../../../../pages/AddPersonalBankAccountPage').default, + [SCREENS.SETTINGS.ADD_BANK_ACCOUNT]: () => require('../../../../pages/settings/Wallet/InternationalDepositAccount').default, + [SCREENS.SETTINGS.ADD_US_BANK_ACCOUNT]: () => require('../../../../pages/AddPersonalBankAccountPage').default, [SCREENS.SETTINGS.PROFILE.STATUS]: () => require('../../../../pages/settings/Profile/CustomStatus/StatusPage').default, [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER]: () => require('../../../../pages/settings/Profile/CustomStatus/StatusClearAfterPage').default, [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_DATE]: () => require('../../../../pages/settings/Profile/CustomStatus/SetDatePage').default, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 7a5b31489764..1dc0696f1f24 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -224,6 +224,10 @@ const config: LinkingOptions['config'] = { path: ROUTES.SETTINGS_ADD_BANK_ACCOUNT, exact: true, }, + [SCREENS.SETTINGS.ADD_US_BANK_ACCOUNT]: { + path: ROUTES.SETTINGS_ADD_US_BANK_ACCOUNT, + exact: true, + }, [SCREENS.SETTINGS.PROFILE.PRONOUNS]: { path: ROUTES.SETTINGS_PRONOUNS, exact: true, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index ba859efff944..73c7989ce95c 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -174,6 +174,7 @@ type SettingsNavigatorParamList = { }; [SCREENS.SETTINGS.ADD_DEBIT_CARD]: undefined; [SCREENS.SETTINGS.ADD_BANK_ACCOUNT]: undefined; + [SCREENS.SETTINGS.ADD_US_BANK_ACCOUNT]: undefined; [SCREENS.SETTINGS.PROFILE.STATUS]: undefined; [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER]: undefined; [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_DATE]: undefined; diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 6679a6e4b9ea..42210e9267ec 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -544,6 +544,41 @@ function validatePlaidSelection(values: FormOnyxValues): Form return errorFields; } +function fetchCorpayFields(bankCountry: string, bankCurrency?: string) { + API.write( + WRITE_COMMANDS.GET_CORPAY_BANK_ACCOUNT_FIELDS, + {countryISO: bankCountry, currency: bankCurrency}, + { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_BANK_ACCOUNT, + value: { + isLoading: true, + }, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM_DRAFT, + value: { + bankCountry, + bankCurrency: bankCurrency ?? null, + }, + }, + ], + finallyData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_BANK_ACCOUNT, + value: { + isLoading: false, + }, + }, + ], + }, + ); +} + export { acceptACHContractForBankAccount, addBusinessWebsiteForDraft, @@ -572,6 +607,7 @@ export { updateAddPersonalBankAccountDraft, clearPersonalBankAccountSetupType, validatePlaidSelection, + fetchCorpayFields, }; export type {BusinessAddress, PersonalAddress}; diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx new file mode 100644 index 000000000000..74687a56eb00 --- /dev/null +++ b/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx @@ -0,0 +1,132 @@ +import React, {useCallback, useMemo} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useSubStep from '@hooks/useSubStep'; +import * as FormActions from '@libs/actions/FormActions'; +import Navigation from '@libs/Navigation/Navigation'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {InternationalBankAccountForm} from '@src/types/form'; +import type {BankAccountList, CorpayFields, PrivatePersonalDetails} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import AccountHolderInformation from './substeps/AccountHolderInformation'; +import AccountType from './substeps/AccountType'; +import BankAccountDetails from './substeps/BankAccountDetails'; +import BankInformation from './substeps/BankInformation'; +import Confirmation from './substeps/Confirmation'; +import CountrySelection from './substeps/CountrySelection'; +import Success from './substeps/Success'; +import type {CustomSubStepProps} from './types'; +import {getFieldsMap, getInitialSubstep, getSubstepValues, testValidation} from './utils'; + +type InternationalDepositAccountContentProps = { + privatePersonalDetails: OnyxEntry; + corpayFields: OnyxEntry; + bankAccountList: OnyxEntry; + draftValues: OnyxEntry; + country: OnyxEntry; +}; + +const formSteps = [CountrySelection, BankAccountDetails, AccountType, BankInformation, AccountHolderInformation, Confirmation, Success]; + +function getSkippedSteps(skipAccountTypeStep: boolean, skipAccountHolderInformationStep: boolean) { + const skippedSteps = []; + if (skipAccountTypeStep) { + skippedSteps.push(CONST.CORPAY_FIELDS.INDEXES.MAPPING.ACCOUNT_TYPE); + } + if (skipAccountHolderInformationStep) { + skippedSteps.push(CONST.CORPAY_FIELDS.INDEXES.MAPPING.ACCOUNT_HOLDER_INFORMATION); + } + return skippedSteps; +} + +function InternationalDepositAccountContent({privatePersonalDetails, corpayFields, bankAccountList, draftValues, country}: InternationalDepositAccountContentProps) { + const {translate} = useLocalize(); + + const values = useMemo( + () => getSubstepValues(privatePersonalDetails, corpayFields, bankAccountList, draftValues, country), + [privatePersonalDetails, corpayFields, bankAccountList, draftValues, country], + ); + + const fieldsMap = useMemo(() => getFieldsMap(corpayFields), [corpayFields]); + + const startFrom = useMemo(() => getInitialSubstep(values, fieldsMap), [fieldsMap, values]); + + const skipAccountTypeStep = isEmptyObject(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_TYPE]); + + const skipAccountHolderInformationStep = testValidation(values, fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]); + + const skippedSteps = getSkippedSteps(skipAccountTypeStep, skipAccountHolderInformationStep); + + const handleFinishStep = useCallback(() => { + FormActions.clearDraftValues(ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM); + Navigation.goBack(); + }, []); + + const { + componentToRender: SubStep, + isEditing, + nextScreen, + prevScreen, + screenIndex, + moveTo, + resetScreenIndex, + } = useSubStep({bodyContent: formSteps, startFrom, onFinished: handleFinishStep, skipSteps: skippedSteps}); + + const handleBackButtonPress = () => { + if (isEditing) { + resetScreenIndex(CONST.CORPAY_FIELDS.INDEXES.MAPPING.CONFIRMATION); + return; + } + + // Clicking back on the first screen should dismiss the modal + if (screenIndex === CONST.CORPAY_FIELDS.INDEXES.MAPPING.COUNTRY_SELECTOR) { + FormActions.clearDraftValues(ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM); + Navigation.goBack(); + return; + } + + // Clicking back on the success screen should dismiss the modal + if (screenIndex === CONST.CORPAY_FIELDS.INDEXES.MAPPING.SUCCESS) { + Navigation.goBack(); + return; + } + prevScreen(); + }; + + const handleNextScreen = useCallback(() => { + if (isEditing) { + resetScreenIndex(CONST.CORPAY_FIELDS.INDEXES.MAPPING.CONFIRMATION); + return; + } + nextScreen(); + }, [resetScreenIndex, isEditing, nextScreen]); + + return ( + + + + + ); +} + +InternationalDepositAccountContent.displayName = 'InternationalDepositAccountContent'; + +export default InternationalDepositAccountContent; diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/index.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/index.tsx new file mode 100644 index 000000000000..792fbfeea568 --- /dev/null +++ b/src/pages/settings/Wallet/InternationalDepositAccount/index.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import {useOnyx} from 'react-native-onyx'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import ONYXKEYS from '@src/ONYXKEYS'; +import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; +import InternationalDepositAccountContent from './InternationalDepositAccountContent'; + +function InternationalDepositAccount() { + const [privatePersonalDetails, privatePersonalDetailsMetadata] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS); + const [corpayFields, corpayFieldsMetadata] = useOnyx(ONYXKEYS.CORPAY_FIELDS); + const [bankAccountList, bankAccountListMetadata] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); + const [draftValues, draftValuesMetadata] = useOnyx(ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM_DRAFT); + const [country, countryMetadata] = useOnyx(ONYXKEYS.COUNTRY); + const [isAccountLoading, isLoadingMetadata] = useOnyx(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {selector: (personalBankAccount) => personalBankAccount?.isLoading}); + + const isLoading = isLoadingOnyxValue(privatePersonalDetailsMetadata, corpayFieldsMetadata, bankAccountListMetadata, draftValuesMetadata, countryMetadata, isLoadingMetadata); + + if (isLoading || isAccountLoading) { + return ; + } + + return ( + + ); +} + +InternationalDepositAccount.displayName = 'InternationalDepositAccount'; + +export default InternationalDepositAccount; diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx new file mode 100644 index 000000000000..77deaa99a0f9 --- /dev/null +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx @@ -0,0 +1,120 @@ +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import AddressSearch from '@components/AddressSearch'; +import CountryPicker from '@components/CountryPicker'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import TextInput from '@components/TextInput'; +import ValuePicker from '@components/ValuePicker'; +import useInternationalBankAccountFormSubmit from '@hooks/useInternationalBankAccountFormSubmit'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import type {CustomSubStepProps} from '@pages/settings/Wallet/InternationalDepositAccount/types'; +import Text from '@src/components/Text'; +import CONST from '@src/CONST'; +import type {Country} from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {CorpayFormField} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; + +function getInputComponent(field: CorpayFormField) { + if ((field.valueSet ?? []).length > 0) { + return ValuePicker; + } + if (CONST.CORPAY_FIELDS.SPECIAL_LIST_REGION_KEYS.includes(field.id)) { + return ValuePicker; + } + if (CONST.CORPAY_FIELDS.SPECIAL_LIST_ADDRESS_KEYS.includes(field.id)) { + return AddressSearch; + } + if (field.id === 'accountHolderCountry') { + return CountryPicker; + } + return TextInput; +} + +function getItems(field: CorpayFormField) { + if ((field.valueSet ?? []).length > 0) { + return (field.valueSet ?? []).map(({id, text}) => ({value: id, label: text})); + } + return (field.links?.[0]?.content.regions ?? []).map(({name, code}) => ({value: code, label: name})); +} + +function AccountHolderInformation({isEditing, onNext, formValues, fieldsMap}: CustomSubStepProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const handleSubmit = useInternationalBankAccountFormSubmit({ + fieldIds: Object.keys(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]), + onNext, + shouldSaveDraft: true, + }); + + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = {}; + const fields = fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]; + for (const fieldName in fields) { + if (!fieldName) { + // eslint-disable-next-line no-continue + continue; + } + if (fields[fieldName].isRequired && values[fieldName] === '') { + ErrorUtils.addErrorMessage(errors, fieldName, translate('common.error.fieldRequired')); + } + if (!values[fieldName]) { + fields[fieldName].validationRules.forEach((rule) => { + const regExpCheck = new RegExp(rule.regEx); + if (!regExpCheck.test(values[fieldName])) { + ErrorUtils.addErrorMessage(errors, fieldName, rule.errorMessage); + } + }); + } + } + return errors; + }, + [fieldsMap, translate], + ); + + return ( + + + {translate('privatePersonalDetails.enterLegalName')} + {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]).map((field) => ( + + ))} + + + ); +} + +AccountHolderInformation.displayName = 'AccountHolderInformation'; + +export default AccountHolderInformation; diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountType.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountType.tsx new file mode 100644 index 000000000000..242d8f75059e --- /dev/null +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountType.tsx @@ -0,0 +1,71 @@ +import React, {useCallback, useMemo, useState} from 'react'; +import {View} from 'react-native'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import type {Option} from '@libs/searchOptions'; +import type {CustomSubStepProps} from '@pages/settings/Wallet/InternationalDepositAccount/types'; +import * as FormActions from '@userActions/FormActions'; +import Text from '@src/components/Text'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; + +function AccountType({isEditing, onNext, formValues, fieldsMap}: CustomSubStepProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const [currentAccountType, setCurrentAccountType] = useState(formValues[CONST.CORPAY_FIELDS.ACCOUNT_TYPE_KEY]); + + const fieldData = fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_TYPE]?.[CONST.CORPAY_FIELDS.ACCOUNT_TYPE_KEY] ?? {}; + + const onAccountTypeSelected = useCallback(() => { + if (isEditing && formValues[CONST.CORPAY_FIELDS.ACCOUNT_TYPE_KEY] === currentAccountType) { + onNext(); + return; + } + FormActions.setDraftValues(ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM, {[CONST.CORPAY_FIELDS.ACCOUNT_TYPE_KEY]: currentAccountType}); + onNext(); + }, [currentAccountType, formValues, isEditing, onNext]); + + const onSelectionChange = useCallback((country: Option) => { + setCurrentAccountType(country.value); + }, []); + + const options = useMemo( + () => + (fieldData.valueSet ?? []).map((item) => { + return { + value: item.id, + keyForList: item.id, + text: item.text, + isSelected: currentAccountType === item.id, + searchValue: item.text, + }; + }), + [fieldData.valueSet, currentAccountType], + ); + + return ( + <> + + {translate('privatePersonalDetails.enterLegalName')} + + + + ); +} + +AccountType.displayName = 'AccountType'; + +export default AccountType; diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx new file mode 100644 index 000000000000..ccbbd4a185aa --- /dev/null +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx @@ -0,0 +1,99 @@ +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import CurrencyPicker from '@components/CurrencyPicker'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import TextInput from '@components/TextInput'; +import ValuePicker from '@components/ValuePicker'; +import useInternationalBankAccountFormSubmit from '@hooks/useInternationalBankAccountFormSubmit'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import type {CustomSubStepProps} from '@pages/settings/Wallet/InternationalDepositAccount/types'; +import * as BankAccounts from '@userActions/BankAccounts'; +import Text from '@src/components/Text'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; + +function BankAccountDetails({isEditing, onNext, resetScreenIndex, formValues, fieldsMap}: CustomSubStepProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const handleSubmit = useInternationalBankAccountFormSubmit({ + fieldIds: Object.keys(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS]), + onNext, + shouldSaveDraft: true, + }); + + const onCurrencySelected = useCallback( + (value: string) => { + if (formValues.bankCurrency === value) { + return; + } + BankAccounts.fetchCorpayFields(formValues.bankCountry, value); + resetScreenIndex?.(CONST.CORPAY_FIELDS.INDEXES.MAPPING.BANK_ACCOUNT_DETAILS); + }, + [formValues.bankCountry, formValues.bankCurrency, resetScreenIndex], + ); + + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = {}; + const fields = fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS]; + for (const fieldName in fields) { + if (!fieldName) { + // eslint-disable-next-line no-continue + continue; + } + if (fields[fieldName].isRequired && values[fieldName] === '') { + ErrorUtils.addErrorMessage(errors, fieldName, translate('common.error.fieldRequired')); + } + if (!values[fieldName]) { + fields[fieldName].validationRules.forEach((rule) => { + const regExpCheck = new RegExp(rule.regEx); + if (!regExpCheck.test(values[fieldName])) { + ErrorUtils.addErrorMessage(errors, fieldName, rule.errorMessage); + } + }); + } + } + return errors; + }, + [fieldsMap, translate], + ); + + return ( + + + {translate('privatePersonalDetails.enterLegalName')} + + {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS]).map((field) => ( + 0 ? ValuePicker : TextInput} + inputID={field.id} + key={field.id} + defaultValue={formValues[field.id]} + label={field.label} + items={(field.valueSet ?? []).map(({id, text}) => ({value: id, label: text}))} + /> + ))} + + + ); +} + +BankAccountDetails.displayName = 'BankAccountDetails'; + +export default BankAccountDetails; diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx new file mode 100644 index 000000000000..ec82873f4f26 --- /dev/null +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx @@ -0,0 +1,115 @@ +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import AddressSearch from '@components/AddressSearch'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import TextInput from '@components/TextInput'; +import ValuePicker from '@components/ValuePicker'; +import useInternationalBankAccountFormSubmit from '@hooks/useInternationalBankAccountFormSubmit'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import type {CustomSubStepProps} from '@pages/settings/Wallet/InternationalDepositAccount/types'; +import Text from '@src/components/Text'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {CorpayFormField} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; + +function getInputComponent(field: CorpayFormField) { + if ((field.valueSet ?? []).length > 0) { + return ValuePicker; + } + if (CONST.CORPAY_FIELDS.SPECIAL_LIST_REGION_KEYS.includes(field.id)) { + return ValuePicker; + } + if (CONST.CORPAY_FIELDS.SPECIAL_LIST_ADDRESS_KEYS.includes(field.id)) { + return AddressSearch; + } + return TextInput; +} + +function getItems(field: CorpayFormField) { + if ((field.valueSet ?? []).length > 0) { + return (field.valueSet ?? []).map(({id, text}) => ({value: id, label: text})); + } + return (field.links?.[0]?.content.regions ?? []).map(({name, code}) => ({value: code, label: name})); +} + +function BankInformation({isEditing, onNext, formValues, fieldsMap}: CustomSubStepProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const handleSubmit = useInternationalBankAccountFormSubmit({ + fieldIds: Object.keys(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION]), + onNext, + shouldSaveDraft: true, + }); + + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = {}; + const fields = fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION]; + for (const fieldName in fields) { + if (!fieldName) { + // eslint-disable-next-line no-continue + continue; + } + if (fields[fieldName].isRequired && values[fieldName] === '') { + ErrorUtils.addErrorMessage(errors, fieldName, translate('common.error.fieldRequired')); + } + if (!values[fieldName]) { + fields[fieldName].validationRules.forEach((rule) => { + const regExpCheck = new RegExp(rule.regEx); + if (!regExpCheck.test(values[fieldName])) { + ErrorUtils.addErrorMessage(errors, fieldName, rule.errorMessage); + } + }); + } + } + return errors; + }, + [fieldsMap, translate], + ); + + return ( + + + {translate('privatePersonalDetails.enterLegalName')} + {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION]).map((field) => ( + + ))} + + + ); +} + +BankInformation.displayName = 'BankInformation'; + +export default BankInformation; diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx new file mode 100644 index 000000000000..a567f3d664fa --- /dev/null +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import ConfirmationStep from '@components/SubStepForms/ConfirmationStep'; +import useLocalize from '@hooks/useLocalize'; +import type {CustomSubStepProps} from '@pages/settings/Wallet/InternationalDepositAccount/types'; +import CONST from '@src/CONST'; + +const STEP_INDEXES = CONST.CORPAY_FIELDS.INDEXES.MAPPING; + +function Confirmation({onNext, onMove, isEditing, formValues, fieldsMap}: CustomSubStepProps) { + const {translate} = useLocalize(); + + const getDataAndGoToNextStep = () => { + console.log('getDataAndGoToNextStep', formValues); + onNext(); + }; + + const summaryItems = [ + { + description: translate('common.country'), + title: formValues.bankCountry, + shouldShowRightIcon: true, + onPress: () => { + onMove(STEP_INDEXES.COUNTRY_SELECTOR); + }, + }, + { + description: translate('common.currency'), + title: formValues.bankCurrency, + shouldShowRightIcon: true, + onPress: () => { + onMove(STEP_INDEXES.BANK_ACCOUNT_DETAILS); + }, + }, + ]; + + // eslint-disable-next-line guard-for-in + for (const fieldName in fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS]) { + summaryItems.push({ + description: fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS][fieldName].label, + title: formValues[fieldName], + shouldShowRightIcon: true, + onPress: () => { + onMove(STEP_INDEXES.BANK_ACCOUNT_DETAILS); + }, + }); + } + + // eslint-disable-next-line guard-for-in + for (const fieldName in fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_TYPE]) { + summaryItems.push({ + description: fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_TYPE][fieldName].label, + title: formValues[fieldName], + shouldShowRightIcon: true, + onPress: () => { + onMove(STEP_INDEXES.ACCOUNT_TYPE); + }, + }); + } + + // eslint-disable-next-line guard-for-in + for (const fieldName in fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION]) { + summaryItems.push({ + description: fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION][fieldName].label, + title: formValues[fieldName], + shouldShowRightIcon: true, + onPress: () => { + onMove(STEP_INDEXES.BANK_INFORMATION); + }, + }); + } + + // eslint-disable-next-line guard-for-in + for (const fieldName in fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]) { + summaryItems.push({ + description: fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION][fieldName].label, + title: formValues[fieldName], + shouldShowRightIcon: true, + onPress: () => { + onMove(STEP_INDEXES.ACCOUNT_HOLDER_INFORMATION); + }, + }); + } + + return ( + + ); +} + +Confirmation.displayName = 'Confirmation'; + +export default Confirmation; diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/CountrySelection.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/CountrySelection.tsx new file mode 100644 index 000000000000..d44e5ca20010 --- /dev/null +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/CountrySelection.tsx @@ -0,0 +1,90 @@ +import React, {useCallback, useMemo, useState} from 'react'; +import {View} from 'react-native'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import useDebouncedState from '@hooks/useDebouncedState'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import searchOptions from '@libs/searchOptions'; +import type {Option} from '@libs/searchOptions'; +import StringUtils from '@libs/StringUtils'; +import type {CustomSubStepProps} from '@pages/settings/Wallet/InternationalDepositAccount/types'; +import * as BankAccounts from '@userActions/BankAccounts'; +import Text from '@src/components/Text'; +import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import ROUTES from '@src/ROUTES'; + +function CountrySelection({isEditing, onNext, formValues}: CustomSubStepProps) { + const {translate} = useLocalize(); + const {isOffline} = useNetwork(); + const styles = useThemeStyles(); + const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState(''); + const [currentCountry, setCurrentCountry] = useState(formValues.bankCountry); + + const onCountrySelected = useCallback(() => { + if (currentCountry === CONST.COUNTRY.US) { + Navigation.navigate(ROUTES.SETTINGS_ADD_US_BANK_ACCOUNT); + return; + } + if (isEditing && formValues.bankCountry === currentCountry) { + onNext(); + return; + } + BankAccounts.fetchCorpayFields(currentCountry, formValues.bankCurrency); + onNext(); + }, [currentCountry, formValues.bankCountry, formValues.bankCurrency, isEditing, onNext]); + + const onSelectionChange = useCallback((country: Option) => { + setCurrentCountry(country.value); + }, []); + + const countries = useMemo( + () => + Object.keys(CONST.ALL_COUNTRIES).map((countryISO) => { + const countryName = translate(`allCountries.${countryISO}` as TranslationPaths); + return { + value: countryISO, + keyForList: countryISO, + text: countryName, + isSelected: currentCountry === countryISO, + searchValue: StringUtils.sanitizeString(`${countryISO}${countryName}`), + }; + }), + [translate, currentCountry], + ); + + const searchResults = searchOptions(debouncedSearchValue, countries); + const headerMessage = debouncedSearchValue.trim() && !searchResults.length ? translate('common.noResultsFound') : ''; + + return ( + <> + + {translate('privatePersonalDetails.enterLegalName')} + + + + ); +} + +CountrySelection.displayName = 'CountrySelection'; + +export default CountrySelection; diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Success.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Success.tsx new file mode 100644 index 000000000000..3a0731010c82 --- /dev/null +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Success.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import ConfirmationPage from '@components/ConfirmationPage'; +import useLocalize from '@hooks/useLocalize'; +import type {CustomSubStepProps} from '@pages/settings/Wallet/InternationalDepositAccount/types'; + +function Confirmation({onNext}: CustomSubStepProps) { + const {translate} = useLocalize(); + + return ( + + ); +} + +Confirmation.displayName = 'Confirmation'; + +export default Confirmation; diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/types.ts b/src/pages/settings/Wallet/InternationalDepositAccount/types.ts new file mode 100644 index 000000000000..2adfc7774c37 --- /dev/null +++ b/src/pages/settings/Wallet/InternationalDepositAccount/types.ts @@ -0,0 +1,20 @@ +import type {ValueOf} from 'type-fest'; +import type {SubStepProps} from '@hooks/useSubStep/types'; +import type CONST from '@src/CONST'; +import type {InternationalBankAccountForm} from '@src/types/form'; +import type {CorpayFieldsMap} from '@src/types/onyx/CorpayFields'; + +type CustomSubStepProps = SubStepProps & { + /** User's form values */ + formValues: InternationalBankAccountForm; + + /** Fields map for the step rendering */ + fieldsMap: Record, CorpayFieldsMap>; +}; + +type CountryZipRegex = { + regex?: RegExp; + samples?: string; +}; + +export type {CustomSubStepProps, CountryZipRegex}; diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts new file mode 100644 index 000000000000..9624dad447c4 --- /dev/null +++ b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts @@ -0,0 +1,103 @@ +import lodashSortBy from 'lodash/sortBy'; +import type {OnyxEntry} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; +import CONST from '@src/CONST'; +import type {InternationalBankAccountForm} from '@src/types/form'; +import type {BankAccount, BankAccountList, CorpayFields, PrivatePersonalDetails} from '@src/types/onyx'; +import type {CorpayFieldsMap} from '@src/types/onyx/CorpayFields'; + +function getFieldsMap(corpayFields: OnyxEntry): Record, CorpayFieldsMap> { + return (corpayFields?.formFields ?? []).reduce((acc, field) => { + if (!field.id) { + return acc; + } + if (field.id === CONST.CORPAY_FIELDS.ACCOUNT_TYPE_KEY) { + acc[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_TYPE] = {[field.id]: field}; + } else if (field.id in CONST.CORPAY_FIELDS.ACCOUNT_HOLDER_FIELDS) { + acc[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION] = acc[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION] ?? {}; + acc[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION][field.id] = field; + } else if (field.id in CONST.CORPAY_FIELDS.BANK_INFORMATION_FIELDS) { + acc[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION] = acc[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION] ?? {}; + acc[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION][field.id] = field; + } else { + acc[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS] = acc[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS] ?? {}; + acc[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS][field.id] = field; + } + return acc; + }, {} as Record, CorpayFieldsMap>); +} + +function getLatestCreatedBankAccount(bankAccountList: OnyxEntry): BankAccount | undefined { + return lodashSortBy(Object.values(bankAccountList ?? {}), 'accountData.created').pop(); +} + +function getSubstepValues( + privatePersonalDetails: OnyxEntry, + corpayFields: OnyxEntry, + bankAccountList: OnyxEntry, + internationalBankAccountDraft: OnyxEntry, + country: OnyxEntry, +): InternationalBankAccountForm { + const address = PersonalDetailsUtils.getCurrentAddress(privatePersonalDetails); + const {street} = address ?? {}; + const [street1, street2] = street ? street.split('\n') : [undefined, undefined]; + const firstName = privatePersonalDetails?.legalFirstName ?? ''; + const lastName = privatePersonalDetails?.legalLastName ?? ''; + const fullName = `${firstName} ${lastName}`.trim(); + const latestBankAccount = getLatestCreatedBankAccount(bankAccountList); + return { + ...internationalBankAccountDraft, + bankCountry: internationalBankAccountDraft?.bankCountry ?? corpayFields?.bankCountry ?? address?.country ?? latestBankAccount?.bankCountry ?? country ?? '', + bankCurrency: internationalBankAccountDraft?.bankCurrency ?? corpayFields?.bankCurrency, + accountHolderName: internationalBankAccountDraft?.accountHolderName ?? fullName, + accountHolderAddress1: internationalBankAccountDraft?.accountHolderAddress1 ?? street1 ?? '', + accountHolderAddress2: internationalBankAccountDraft?.accountHolderAddress2 ?? street2 ?? '', + accountHolderCity: internationalBankAccountDraft?.accountHolderCity ?? address?.city ?? '', + accountHolderCountry: internationalBankAccountDraft?.accountHolderCountry ?? address?.country ?? '', + accountHolderPostal: internationalBankAccountDraft?.accountHolderPostal ?? address?.zip ?? '', + accountHolderPhoneNumber: internationalBankAccountDraft?.accountHolderPhoneNumber ?? privatePersonalDetails?.phoneNumber ?? '', + } as unknown as InternationalBankAccountForm; +} + +function testValidation(values: InternationalBankAccountForm, fieldsMap: CorpayFieldsMap = {}) { + for (const fieldName in fieldsMap) { + if (!fieldName) { + // eslint-disable-next-line no-continue + continue; + } + if (fieldsMap[fieldName].isRequired && values[fieldName] === '') { + return false; + } + if (!values[fieldName]) { + fieldsMap[fieldName].validationRules.forEach((rule) => { + const regExpCheck = new RegExp(rule.regEx); + if (!regExpCheck.test(values[fieldName])) { + return false; + } + }); + } + } + return true; +} + +function getInitialSubstep(values: InternationalBankAccountForm, fieldsMap: Record, CorpayFieldsMap>) { + if (values.bankCountry === '') { + return CONST.CORPAY_FIELDS.INDEXES.MAPPING.COUNTRY_SELECTOR; + } + if (values.bankCurrency === '' || !testValidation(values, fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS])) { + return CONST.CORPAY_FIELDS.INDEXES.MAPPING.BANK_ACCOUNT_DETAILS; + } + if (!testValidation(values, fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_TYPE])) { + return CONST.CORPAY_FIELDS.INDEXES.MAPPING.ACCOUNT_TYPE; + } + if (!testValidation(values, fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION])) { + return CONST.CORPAY_FIELDS.INDEXES.MAPPING.BANK_INFORMATION; + } + if (!testValidation(values, fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION])) { + return CONST.CORPAY_FIELDS.INDEXES.MAPPING.ACCOUNT_HOLDER_INFORMATION; + } + return CONST.CORPAY_FIELDS.INDEXES.MAPPING.CONFIRMATION; +} + +export {getFieldsMap, getSubstepValues, getInitialSubstep, testValidation}; diff --git a/src/types/form/InternationalBankAccountForm.ts b/src/types/form/InternationalBankAccountForm.ts new file mode 100644 index 000000000000..f7c981b6a5c4 --- /dev/null +++ b/src/types/form/InternationalBankAccountForm.ts @@ -0,0 +1,6 @@ +import type {BaseForm} from './Form'; + +type InternationalBankAccountForm = BaseForm & Record; + +// eslint-disable-next-line import/prefer-default-export +export type {InternationalBankAccountForm}; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index ddecc5cd634e..c6869ec2961f 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -85,3 +85,4 @@ export type {WorkspaceCompanyCardFeedName} from './WorkspaceCompanyCardFeedName' export type {SearchSavedSearchRenameForm} from './SearchSavedSearchRenameForm'; export type {WorkspaceCompanyCardEditName} from './WorkspaceCompanyCardEditName'; export type {PersonalDetailsForm} from './PersonalDetailsForm'; +export type {InternationalBankAccountForm} from './InternationalBankAccountForm'; diff --git a/src/types/onyx/BankAccount.ts b/src/types/onyx/BankAccount.ts index 6da03440fb2e..cd44e6256db6 100644 --- a/src/types/onyx/BankAccount.ts +++ b/src/types/onyx/BankAccount.ts @@ -59,6 +59,12 @@ type BankAccount = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** All data related to the bank account */ accountData?: AccountData; + /** Currency code related to the bank account */ + bankCurrency: string; + + /** Country code related to the bank account */ + bankCountry: string; + /** Any additional error message to show */ errors?: OnyxCommon.Errors; }>; diff --git a/src/types/onyx/CorpayFields.ts b/src/types/onyx/CorpayFields.ts new file mode 100644 index 000000000000..cdcdb4b3dbaf --- /dev/null +++ b/src/types/onyx/CorpayFields.ts @@ -0,0 +1,135 @@ +/** + * + */ +type CorpayFormField = { + /** + * + */ + errorMessage: string; + /** + * + */ + id: string; + /** + * + */ + isRequired: boolean; + /** + * + */ + isRequiredInValueSet: boolean; + /** + * + */ + label: string; + /** + * + */ + regEx: string; + /** + * + */ + validationRules: Array<{ + /** + * + */ + errorMessage: string; + /** + * + */ + regEx: string; + }>; + /** + * + */ + valueSet?: Array<{ + /** + * + */ + id: string; + /** + * + */ + text: string; + }>; + /** + * + */ + links?: Array<{ + /** + * + */ + content: { + /** + * + */ + isCompleteList: boolean; + /** + * + */ + regions: Array<{ + /** + * + */ + code: string; + /** + * + */ + country: string; + /** + * + */ + countryName: string; + /** + * + */ + id: string; + /** + * + */ + name: string; + }>; + }; + }>; +}; + +/** + * + */ +type CorpayFields = { + /** + * + */ + bankCountry: string; + /** + * + */ + bankCurrency: string; + /** + * + */ + classification: string; + /** + * + */ + destinationCountry: string; + /** + * + */ + paymentMethods: string[]; + /** + * + */ + preferredMethod: string; + /** + * + */ + formFields: CorpayFormField[]; +}; + +/** + * + */ +type CorpayFieldsMap = Record; + +export type {CorpayFields, CorpayFormField, CorpayFieldsMap}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 7702da678105..6db9682643ce 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -15,6 +15,7 @@ import type CardFeeds from './CardFeeds'; import type {AddNewCompanyCardFeed, CompanyCardFeed} from './CardFeeds'; import type CardOnWaitlist from './CardOnWaitlist'; import type {CapturedLogs, Log} from './Console'; +import type {CorpayFields, CorpayFormField} from './CorpayFields'; import type Credentials from './Credentials'; import type Currency from './Currency'; import type {CurrencyList} from './Currency'; @@ -237,4 +238,6 @@ export type { RecentSearchItem, ImportedSpreadsheet, ValidateMagicCodeAction, + CorpayFields, + CorpayFormField, }; diff --git a/tests/unit/useSubStepTest.tsx b/tests/unit/useSubStepTest.tsx index 7a5577005d4a..6e3da7ba2e7f 100644 --- a/tests/unit/useSubStepTest.tsx +++ b/tests/unit/useSubStepTest.tsx @@ -9,8 +9,15 @@ function MockSubStepComponent({screenIndex}: SubStepProps) { function MockSubStepComponent2({screenIndex}: SubStepProps) { return {screenIndex}; } +function MockSubStepComponent3({screenIndex}: SubStepProps) { + return {screenIndex}; +} +function MockSubStepComponent4({screenIndex}: SubStepProps) { + return {screenIndex}; +} const mockOnFinished = jest.fn(); +const mockOnFinished2 = jest.fn(); describe('useSubStep hook', () => { it('returns componentToRender, isEditing, currentIndex, prevScreen, nextScreen, moveTo', () => { @@ -93,3 +100,191 @@ describe('useSubStep hook', () => { expect(newComponentToRender).toBe(MockSubStepComponent2); }); }); + +describe('useSubStep hook with skipSteps', () => { + it('calls onFinished when it is the second last step (last step is skipped)', () => { + const {result} = renderHook(() => useSubStep({bodyContent: [MockSubStepComponent, MockSubStepComponent2], onFinished: mockOnFinished2, startFrom: 0, skipSteps: [1]})); + + const {nextScreen} = result.current; + + act(() => { + nextScreen(); + }); + + expect(mockOnFinished2).toHaveBeenCalledTimes(1); + }); + + it('returns component at requested substep when calling moveTo even though the step is marked as skipped', () => { + const {result, rerender} = renderHook(() => + useSubStep({bodyContent: [MockSubStepComponent2, MockSubStepComponent3, MockSubStepComponent], onFinished: mockOnFinished, startFrom: 2, skipSteps: [1]}), + ); + + const {moveTo} = result.current; + + act(() => { + moveTo(1); + }); + + rerender({}); + + const {componentToRender} = result.current; + + expect(componentToRender).toBe(MockSubStepComponent3); + }); + + it('returns substep component at the previous index when calling prevScreen (if possible)', () => { + const {result, rerender} = renderHook(() => + useSubStep({ + bodyContent: [MockSubStepComponent, MockSubStepComponent2, MockSubStepComponent3, MockSubStepComponent4], + onFinished: mockOnFinished, + startFrom: 3, + skipSteps: [0, 2], + }), + ); + + const {prevScreen, screenIndex} = result.current; + + expect(screenIndex).toBe(3); + + act(() => { + prevScreen(); + }); + + rerender({}); + + const {componentToRender, screenIndex: newScreenIndex} = result.current; + expect(newScreenIndex).toBe(1); + + expect(componentToRender).toBe(MockSubStepComponent2); + }); + + it('stays on the first substep component when calling prevScreen on the second screen if the first screen is skipped', () => { + const {result, rerender} = renderHook(() => + useSubStep({bodyContent: [MockSubStepComponent, MockSubStepComponent2, MockSubStepComponent3], onFinished: mockOnFinished, startFrom: 1, skipSteps: [0]}), + ); + + const {componentToRender, prevScreen, screenIndex} = result.current; + + expect(screenIndex).toBe(1); + expect(componentToRender).toBe(MockSubStepComponent2); + + act(() => { + prevScreen(); + }); + + rerender({}); + + const {componentToRender: newComponentToRender, screenIndex: newScreenIndex} = result.current; + + expect(newScreenIndex).toBe(1); + expect(newComponentToRender).toBe(MockSubStepComponent2); + }); + + it('skips step which are marked as skipped when using nextScreen', () => { + const {result, rerender} = renderHook(() => + useSubStep({ + bodyContent: [MockSubStepComponent, MockSubStepComponent2, MockSubStepComponent3, MockSubStepComponent4], + onFinished: mockOnFinished, + startFrom: 0, + skipSteps: [1, 2], + }), + ); + + const {componentToRender, nextScreen, screenIndex} = result.current; + + expect(screenIndex).toBe(0); + expect(componentToRender).toBe(MockSubStepComponent); + + act(() => { + nextScreen(); + }); + + rerender({}); + + const {componentToRender: newComponentToRender, screenIndex: newScreenIndex} = result.current; + + expect(newScreenIndex).toBe(3); + expect(newComponentToRender).toBe(MockSubStepComponent4); + }); + + it('nextScreen works correctly when called from skipped screen', () => { + const {result, rerender} = renderHook(() => + useSubStep({ + bodyContent: [MockSubStepComponent, MockSubStepComponent2, MockSubStepComponent3, MockSubStepComponent4], + onFinished: mockOnFinished, + startFrom: 1, + skipSteps: [1, 2], + }), + ); + + const {componentToRender, nextScreen, screenIndex} = result.current; + + expect(screenIndex).toBe(1); + expect(componentToRender).toBe(MockSubStepComponent2); + + act(() => { + nextScreen(); + }); + + rerender({}); + + const {componentToRender: newComponentToRender, screenIndex: newScreenIndex} = result.current; + + expect(newScreenIndex).toBe(3); + expect(newComponentToRender).toBe(MockSubStepComponent4); + }); + + it('skips step which are marked as skipped when using prevScreen', () => { + const {result, rerender} = renderHook(() => + useSubStep({ + bodyContent: [MockSubStepComponent, MockSubStepComponent2, MockSubStepComponent3, MockSubStepComponent4], + onFinished: mockOnFinished, + startFrom: 3, + skipSteps: [1, 2], + }), + ); + + const {componentToRender, prevScreen, screenIndex} = result.current; + + expect(screenIndex).toBe(3); + expect(componentToRender).toBe(MockSubStepComponent4); + + act(() => { + prevScreen(); + }); + + rerender({}); + + const {componentToRender: newComponentToRender, screenIndex: newScreenIndex} = result.current; + + expect(newScreenIndex).toBe(0); + expect(newComponentToRender).toBe(MockSubStepComponent); + }); + + it('prevScreen works correctly when called from skipped screen', () => { + const {result, rerender} = renderHook(() => + useSubStep({ + bodyContent: [MockSubStepComponent, MockSubStepComponent2, MockSubStepComponent3, MockSubStepComponent4], + onFinished: mockOnFinished, + startFrom: 2, + skipSteps: [1, 2], + }), + ); + + const {componentToRender, prevScreen, screenIndex} = result.current; + + expect(screenIndex).toBe(2); + expect(componentToRender).toBe(MockSubStepComponent3); + + act(() => { + prevScreen(); + }); + + rerender({}); + + const {componentToRender: newComponentToRender, screenIndex: newScreenIndex} = result.current; + + expect(newScreenIndex).toBe(0); + expect(newComponentToRender).toBe(MockSubStepComponent); + }); +}); From 21bd4677b28aeefb35642b20ef7ca952c2b6f977 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 12 Nov 2024 00:03:29 +0530 Subject: [PATCH 02/54] Fix edge case --- src/pages/settings/Wallet/InternationalDepositAccount/utils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts index 9624dad447c4..0a4d7ee26ecd 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts +++ b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts @@ -6,6 +6,7 @@ import CONST from '@src/CONST'; import type {InternationalBankAccountForm} from '@src/types/form'; import type {BankAccount, BankAccountList, CorpayFields, PrivatePersonalDetails} from '@src/types/onyx'; import type {CorpayFieldsMap} from '@src/types/onyx/CorpayFields'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; function getFieldsMap(corpayFields: OnyxEntry): Record, CorpayFieldsMap> { return (corpayFields?.formFields ?? []).reduce((acc, field) => { @@ -82,7 +83,7 @@ function testValidation(values: InternationalBankAccountForm, fieldsMap: CorpayF } function getInitialSubstep(values: InternationalBankAccountForm, fieldsMap: Record, CorpayFieldsMap>) { - if (values.bankCountry === '') { + if (values.bankCountry === '' || isEmptyObject(fieldsMap)) { return CONST.CORPAY_FIELDS.INDEXES.MAPPING.COUNTRY_SELECTOR; } if (values.bankCurrency === '' || !testValidation(values, fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS])) { From bf6cca4d59ed84a5e134b8542ba26e95e9415779 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 12 Nov 2024 00:15:26 +0530 Subject: [PATCH 03/54] Fix step calc function --- src/CONST.ts | 6 +++--- .../settings/Wallet/InternationalDepositAccount/utils.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index ed6ebe21c45f..6f9a2483881f 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -6232,9 +6232,9 @@ const CONST = { }, CORPAY_FIELDS: { - BANK_ACCOUNT_DETAILS_FIELDS: ['accountNumber', 'localAccountNumber', 'routingCode', 'localRoutingCode', 'swiftBicCode'], + BANK_ACCOUNT_DETAILS_FIELDS: ['accountNumber', 'localAccountNumber', 'routingCode', 'localRoutingCode', 'swiftBicCode'] as string[], ACCOUNT_TYPE_KEY: 'BeneficiaryAccountType', - BANK_INFORMATION_FIELDS: ['bankName', 'bankRegion', 'bankCity', 'bankAddressLine1', 'bankAddressLine2', 'bankPostal', 'BeneficiaryBankBranchName'], + BANK_INFORMATION_FIELDS: ['bankName', 'bankRegion', 'bankCity', 'bankAddressLine1', 'bankAddressLine2', 'bankPostal', 'BeneficiaryBankBranchName'] as string[], ACCOUNT_HOLDER_FIELDS: [ 'accountHolderName', 'accountHolderCountry', @@ -6250,7 +6250,7 @@ const CONST = { 'BeneficiaryRUT', 'BeneficiaryCedulaID', 'BeneficiaryTaxID', - ], + ] as string[], SPECIAL_LIST_REGION_KEYS: ['bankRegion', 'accountHolderRegion'] as string[], SPECIAL_LIST_ADDRESS_KEYS: ['bankAddressLine1', 'accountHolderAddress1'] as string[], STEPS_NAME: { diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts index 0a4d7ee26ecd..28455bb19a92 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts +++ b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts @@ -15,10 +15,10 @@ function getFieldsMap(corpayFields: OnyxEntry): Record Date: Tue, 12 Nov 2024 00:32:02 +0530 Subject: [PATCH 04/54] Clear corpay fields before starting flow --- src/libs/actions/BankAccounts.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 42210e9267ec..12c85bf0ec7a 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -75,6 +75,7 @@ function setPlaidEvent(eventName: string | null) { */ function openPersonalBankAccountSetupView(exitReportID?: string, isUserValidated = true) { clearPlaid().then(() => { + Onyx.set(ONYXKEYS.CORPAY_FIELDS, null); if (exitReportID) { Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {exitReportID}); } From 6d5ce0f8672a255bfe10adad69df703b52b39241 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 12 Nov 2024 08:11:46 +0530 Subject: [PATCH 05/54] Fix optional label --- .../substeps/AccountHolderInformation.tsx | 2 +- .../substeps/BankAccountDetails.tsx | 2 +- .../substeps/BankInformation.tsx | 2 +- .../substeps/Confirmation.tsx | 16 ++++++++++++---- .../Wallet/InternationalDepositAccount/utils.ts | 2 +- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx index 77deaa99a0f9..5eca1170747b 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx @@ -96,7 +96,7 @@ function AccountHolderInformation({isEditing, onNext, formValues, fieldsMap}: Cu inputID={field.id} key={field.id} defaultValue={formValues[field.id]} - label={field.label} + label={field.label + (field.isRequired ? ` (${translate('common.optional')})` : '')} items={getItems(field)} renamedInputKeys={{ street: isEmptyObject(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]?.accountHolderAddress1) ? '' : 'accountHolderAddress1', diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx index ccbbd4a185aa..e0c4bbe409f3 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx @@ -85,7 +85,7 @@ function BankAccountDetails({isEditing, onNext, resetScreenIndex, formValues, fi inputID={field.id} key={field.id} defaultValue={formValues[field.id]} - label={field.label} + label={field.label + (field.isRequired ? ` (${translate('common.optional')})` : '')} items={(field.valueSet ?? []).map(({id, text}) => ({value: id, label: text}))} /> ))} diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx index ec82873f4f26..4f0c055e0c4b 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx @@ -91,7 +91,7 @@ function BankInformation({isEditing, onNext, formValues, fieldsMap}: CustomSubSt inputID={field.id} key={field.id} defaultValue={formValues[field.id]} - label={field.label} + label={field.label + (field.isRequired ? ` (${translate('common.optional')})` : '')} items={getItems(field)} renamedInputKeys={{ street: isEmptyObject(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION]?.bankAddressLine1) ? '' : 'bankAddressLine1', diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx index a567f3d664fa..151948dadac9 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx @@ -36,7 +36,9 @@ function Confirmation({onNext, onMove, isEditing, formValues, fieldsMap}: Custom // eslint-disable-next-line guard-for-in for (const fieldName in fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS]) { summaryItems.push({ - description: fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS][fieldName].label, + description: + fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS][fieldName].label + + (fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS][fieldName].isRequired ? ` (${translate('common.optional')})` : ''), title: formValues[fieldName], shouldShowRightIcon: true, onPress: () => { @@ -48,7 +50,9 @@ function Confirmation({onNext, onMove, isEditing, formValues, fieldsMap}: Custom // eslint-disable-next-line guard-for-in for (const fieldName in fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_TYPE]) { summaryItems.push({ - description: fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_TYPE][fieldName].label, + description: + fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_TYPE][fieldName].label + + (fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_TYPE][fieldName].isRequired ? ` (${translate('common.optional')})` : ''), title: formValues[fieldName], shouldShowRightIcon: true, onPress: () => { @@ -60,7 +64,9 @@ function Confirmation({onNext, onMove, isEditing, formValues, fieldsMap}: Custom // eslint-disable-next-line guard-for-in for (const fieldName in fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION]) { summaryItems.push({ - description: fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION][fieldName].label, + description: + fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION][fieldName].label + + (fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION][fieldName].isRequired ? ` (${translate('common.optional')})` : ''), title: formValues[fieldName], shouldShowRightIcon: true, onPress: () => { @@ -72,7 +78,9 @@ function Confirmation({onNext, onMove, isEditing, formValues, fieldsMap}: Custom // eslint-disable-next-line guard-for-in for (const fieldName in fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]) { summaryItems.push({ - description: fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION][fieldName].label, + description: + fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION][fieldName].label + + (fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION][fieldName].isRequired ? ` (${translate('common.optional')})` : ''), title: formValues[fieldName], shouldShowRightIcon: true, onPress: () => { diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts index 28455bb19a92..55395181ec23 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts +++ b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts @@ -67,7 +67,7 @@ function testValidation(values: InternationalBankAccountForm, fieldsMap: CorpayF // eslint-disable-next-line no-continue continue; } - if (fieldsMap[fieldName].isRequired && values[fieldName] === '') { + if (fieldsMap[fieldName].isRequired && (values[fieldName] ?? '') === '') { return false; } if (!values[fieldName]) { From 463d41a9ce9bd0e585378eedb1edc53a68fd4ab0 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 12 Nov 2024 08:22:45 +0530 Subject: [PATCH 06/54] Clear draft values before starting flow --- src/libs/actions/BankAccounts.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 12c85bf0ec7a..937f9091496a 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -24,6 +24,7 @@ import type {ACHContractStepProps, BeneficialOwnersStepProps, CompanyStepProps, import type PlaidBankAccount from '@src/types/onyx/PlaidBankAccount'; import type {BankAccountStep, ReimbursementAccountStep, ReimbursementAccountSubStep} from '@src/types/onyx/ReimbursementAccount'; import type {OnyxData} from '@src/types/onyx/Request'; +import * as FormActions from './FormActions'; import * as ReimbursementAccount from './ReimbursementAccount'; export { @@ -76,6 +77,7 @@ function setPlaidEvent(eventName: string | null) { function openPersonalBankAccountSetupView(exitReportID?: string, isUserValidated = true) { clearPlaid().then(() => { Onyx.set(ONYXKEYS.CORPAY_FIELDS, null); + FormActions.clearDraftValues(ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM); if (exitReportID) { Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {exitReportID}); } From 5411eb350f12a34741223252a4083235c86a743d Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 12 Nov 2024 08:26:07 +0530 Subject: [PATCH 07/54] Fix crash --- .../InternationalDepositAccount/substeps/BankAccountDetails.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx index e0c4bbe409f3..f55369dabb84 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx @@ -21,7 +21,7 @@ function BankAccountDetails({isEditing, onNext, resetScreenIndex, formValues, fi const styles = useThemeStyles(); const handleSubmit = useInternationalBankAccountFormSubmit({ - fieldIds: Object.keys(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS]), + fieldIds: Object.keys(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS] ?? {}), onNext, shouldSaveDraft: true, }); From 6553f6a9b2f21c24034b3c326cf482938bd29994 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 12 Nov 2024 08:29:24 +0530 Subject: [PATCH 08/54] Fix optional --- .../substeps/AccountHolderInformation.tsx | 2 +- .../substeps/BankAccountDetails.tsx | 2 +- .../substeps/BankInformation.tsx | 2 +- .../InternationalDepositAccount/substeps/Confirmation.tsx | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx index 5eca1170747b..75c89efeb71b 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx @@ -96,7 +96,7 @@ function AccountHolderInformation({isEditing, onNext, formValues, fieldsMap}: Cu inputID={field.id} key={field.id} defaultValue={formValues[field.id]} - label={field.label + (field.isRequired ? ` (${translate('common.optional')})` : '')} + label={field.label + (field.isRequired ? '' : ` (${translate('common.optional')})`)} items={getItems(field)} renamedInputKeys={{ street: isEmptyObject(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]?.accountHolderAddress1) ? '' : 'accountHolderAddress1', diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx index f55369dabb84..aff968da3871 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx @@ -85,7 +85,7 @@ function BankAccountDetails({isEditing, onNext, resetScreenIndex, formValues, fi inputID={field.id} key={field.id} defaultValue={formValues[field.id]} - label={field.label + (field.isRequired ? ` (${translate('common.optional')})` : '')} + label={field.label + (field.isRequired ? '' : ` (${translate('common.optional')})`)} items={(field.valueSet ?? []).map(({id, text}) => ({value: id, label: text}))} /> ))} diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx index 4f0c055e0c4b..1e660e012bd3 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx @@ -91,7 +91,7 @@ function BankInformation({isEditing, onNext, formValues, fieldsMap}: CustomSubSt inputID={field.id} key={field.id} defaultValue={formValues[field.id]} - label={field.label + (field.isRequired ? ` (${translate('common.optional')})` : '')} + label={field.label + (field.isRequired ? '' : ` (${translate('common.optional')})`)} items={getItems(field)} renamedInputKeys={{ street: isEmptyObject(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION]?.bankAddressLine1) ? '' : 'bankAddressLine1', diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx index 151948dadac9..46483e525855 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx @@ -38,7 +38,7 @@ function Confirmation({onNext, onMove, isEditing, formValues, fieldsMap}: Custom summaryItems.push({ description: fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS][fieldName].label + - (fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS][fieldName].isRequired ? ` (${translate('common.optional')})` : ''), + (fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS][fieldName].isRequired ? '' : ` (${translate('common.optional')})`), title: formValues[fieldName], shouldShowRightIcon: true, onPress: () => { @@ -52,7 +52,7 @@ function Confirmation({onNext, onMove, isEditing, formValues, fieldsMap}: Custom summaryItems.push({ description: fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_TYPE][fieldName].label + - (fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_TYPE][fieldName].isRequired ? ` (${translate('common.optional')})` : ''), + (fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_TYPE][fieldName].isRequired ? '' : ` (${translate('common.optional')})`), title: formValues[fieldName], shouldShowRightIcon: true, onPress: () => { @@ -66,7 +66,7 @@ function Confirmation({onNext, onMove, isEditing, formValues, fieldsMap}: Custom summaryItems.push({ description: fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION][fieldName].label + - (fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION][fieldName].isRequired ? ` (${translate('common.optional')})` : ''), + (fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION][fieldName].isRequired ? '' : ` (${translate('common.optional')})`), title: formValues[fieldName], shouldShowRightIcon: true, onPress: () => { @@ -80,7 +80,7 @@ function Confirmation({onNext, onMove, isEditing, formValues, fieldsMap}: Custom summaryItems.push({ description: fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION][fieldName].label + - (fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION][fieldName].isRequired ? ` (${translate('common.optional')})` : ''), + (fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION][fieldName].isRequired ? '' : ` (${translate('common.optional')})`), title: formValues[fieldName], shouldShowRightIcon: true, onPress: () => { From d1a3dc989969a9009dc423e3b1cd277c22a7481e Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 12 Nov 2024 08:51:40 +0530 Subject: [PATCH 09/54] Fix skipping condition --- .../InternationalDepositAccountContent.tsx | 6 ++++-- .../InternationalDepositAccount/utils.ts | 20 ++++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx index 74687a56eb00..a0691336841c 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx @@ -19,7 +19,7 @@ import Confirmation from './substeps/Confirmation'; import CountrySelection from './substeps/CountrySelection'; import Success from './substeps/Success'; import type {CustomSubStepProps} from './types'; -import {getFieldsMap, getInitialSubstep, getSubstepValues, testValidation} from './utils'; +import {getFieldsMap, getInitialPersonalDetailsValues, getInitialSubstep, getSubstepValues, testValidation} from './utils'; type InternationalDepositAccountContentProps = { privatePersonalDetails: OnyxEntry; @@ -50,13 +50,15 @@ function InternationalDepositAccountContent({privatePersonalDetails, corpayField [privatePersonalDetails, corpayFields, bankAccountList, draftValues, country], ); + const initialAccountHolderDetailsValues = useMemo(() => getInitialPersonalDetailsValues(privatePersonalDetails), [privatePersonalDetails]); + const fieldsMap = useMemo(() => getFieldsMap(corpayFields), [corpayFields]); const startFrom = useMemo(() => getInitialSubstep(values, fieldsMap), [fieldsMap, values]); const skipAccountTypeStep = isEmptyObject(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_TYPE]); - const skipAccountHolderInformationStep = testValidation(values, fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]); + const skipAccountHolderInformationStep = testValidation(initialAccountHolderDetailsValues, fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]); const skippedSteps = getSkippedSteps(skipAccountTypeStep, skipAccountHolderInformationStep); diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts index 55395181ec23..fa5c0460aa8a 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts +++ b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts @@ -61,6 +61,24 @@ function getSubstepValues( } as unknown as InternationalBankAccountForm; } +function getInitialPersonalDetailsValues(privatePersonalDetails: OnyxEntry): InternationalBankAccountForm { + const address = PersonalDetailsUtils.getCurrentAddress(privatePersonalDetails); + const {street} = address ?? {}; + const [street1, street2] = street ? street.split('\n') : [undefined, undefined]; + const firstName = privatePersonalDetails?.legalFirstName ?? ''; + const lastName = privatePersonalDetails?.legalLastName ?? ''; + const fullName = `${firstName} ${lastName}`.trim(); + return { + accountHolderName: fullName, + accountHolderAddress1: street1 ?? '', + accountHolderAddress2: street2 ?? '', + accountHolderCity: address?.city ?? '', + accountHolderCountry: address?.country ?? '', + accountHolderPostal: address?.zip ?? '', + accountHolderPhoneNumber: privatePersonalDetails?.phoneNumber ?? '', + } as InternationalBankAccountForm; +} + function testValidation(values: InternationalBankAccountForm, fieldsMap: CorpayFieldsMap = {}) { for (const fieldName in fieldsMap) { if (!fieldName) { @@ -101,4 +119,4 @@ function getInitialSubstep(values: InternationalBankAccountForm, fieldsMap: Reco return CONST.CORPAY_FIELDS.INDEXES.MAPPING.CONFIRMATION; } -export {getFieldsMap, getSubstepValues, getInitialSubstep, testValidation}; +export {getFieldsMap, getSubstepValues, getInitialPersonalDetailsValues, getInitialSubstep, testValidation}; From 2fdcca608e71dbd20c2b470e7567364caee9d79f Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 12 Nov 2024 09:00:31 +0530 Subject: [PATCH 10/54] Fix back on success page --- .../InternationalDepositAccountContent.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx index a0691336841c..db0d2f721327 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx @@ -92,6 +92,7 @@ function InternationalDepositAccountContent({privatePersonalDetails, corpayField // Clicking back on the success screen should dismiss the modal if (screenIndex === CONST.CORPAY_FIELDS.INDEXES.MAPPING.SUCCESS) { + FormActions.clearDraftValues(ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM); Navigation.goBack(); return; } From 067a6b7854a95738e46cf2ed683842384662df91 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 12 Nov 2024 10:38:50 +0530 Subject: [PATCH 11/54] Fix styling --- .../substeps/AccountHolderInformation.tsx | 57 +++++++++++++------ .../substeps/BankAccountDetails.tsx | 30 ++++++---- .../substeps/BankInformation.tsx | 54 ++++++++++++------ 3 files changed, 95 insertions(+), 46 deletions(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx index 75c89efeb71b..95a383f59d63 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx @@ -78,6 +78,25 @@ function AccountHolderInformation({isEditing, onNext, formValues, fieldsMap}: Cu [fieldsMap, translate], ); + const getStyle = useCallback( + (field: CorpayFormField) => { + if ((field.valueSet ?? []).length > 0) { + return [styles.mhn5, styles.pt2]; + } + if (CONST.CORPAY_FIELDS.SPECIAL_LIST_REGION_KEYS.includes(field.id)) { + return [styles.mhn5, styles.pt2]; + } + if (CONST.CORPAY_FIELDS.SPECIAL_LIST_ADDRESS_KEYS.includes(field.id)) { + return [styles.mt5]; + } + if (field.id === 'accountHolderCountry') { + return [styles.mhn5, styles.pt2]; + } + return [styles.mt5]; + }, + [styles.mhn5, styles.mt5, styles.pt2], + ); + return ( {translate('privatePersonalDetails.enterLegalName')} {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]).map((field) => ( - + > + + ))} diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx index aff968da3871..b59b584c4977 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx @@ -75,19 +75,25 @@ function BankAccountDetails({isEditing, onNext, resetScreenIndex, formValues, fi > {translate('privatePersonalDetails.enterLegalName')} - - {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS]).map((field) => ( - 0 ? ValuePicker : TextInput} - inputID={field.id} - key={field.id} - defaultValue={formValues[field.id]} - label={field.label + (field.isRequired ? '' : ` (${translate('common.optional')})`)} - items={(field.valueSet ?? []).map(({id, text}) => ({value: id, label: text}))} + + + + {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS] ?? {}).map((field) => ( + 0 ? [styles.mhn5, styles.pt2] : [styles.pt5]} + key={field.id} + > + 0 ? ValuePicker : TextInput} + inputID={field.id} + defaultValue={formValues[field.id]} + label={field.label + (field.isRequired ? '' : ` (${translate('common.optional')})`)} + items={(field.valueSet ?? []).map(({id, text}) => ({value: id, label: text}))} + /> + ))} diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx index 1e660e012bd3..d7096d3299a3 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx @@ -73,6 +73,22 @@ function BankInformation({isEditing, onNext, formValues, fieldsMap}: CustomSubSt [fieldsMap, translate], ); + const getStyle = useCallback( + (field: CorpayFormField) => { + if ((field.valueSet ?? []).length > 0) { + return [styles.mhn5, styles.pt2]; + } + if (CONST.CORPAY_FIELDS.SPECIAL_LIST_REGION_KEYS.includes(field.id)) { + return [styles.mhn5, styles.pt2]; + } + if (CONST.CORPAY_FIELDS.SPECIAL_LIST_ADDRESS_KEYS.includes(field.id)) { + return [styles.mt5]; + } + return [styles.mt5]; + }, + [styles.mhn5, styles.mt5, styles.pt2], + ); + return ( {translate('privatePersonalDetails.enterLegalName')} {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION]).map((field) => ( - + > + + ))} From 40abc7328769f473770c27bd8363b51c4561d1a8 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 12 Nov 2024 10:55:09 +0530 Subject: [PATCH 12/54] Fix validation --- .../substeps/AccountHolderInformation.tsx | 14 ++++++-------- .../substeps/BankAccountDetails.tsx | 14 ++++++-------- .../substeps/BankInformation.tsx | 14 ++++++-------- .../Wallet/InternationalDepositAccount/utils.ts | 14 ++++++-------- 4 files changed, 24 insertions(+), 32 deletions(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx index 95a383f59d63..59e6bb693e16 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx @@ -64,14 +64,12 @@ function AccountHolderInformation({isEditing, onNext, formValues, fieldsMap}: Cu if (fields[fieldName].isRequired && values[fieldName] === '') { ErrorUtils.addErrorMessage(errors, fieldName, translate('common.error.fieldRequired')); } - if (!values[fieldName]) { - fields[fieldName].validationRules.forEach((rule) => { - const regExpCheck = new RegExp(rule.regEx); - if (!regExpCheck.test(values[fieldName])) { - ErrorUtils.addErrorMessage(errors, fieldName, rule.errorMessage); - } - }); - } + fields[fieldName].validationRules.forEach((rule) => { + const regExpCheck = new RegExp(rule.regEx); + if (!regExpCheck.test(values[fieldName])) { + ErrorUtils.addErrorMessage(errors, fieldName, rule.errorMessage); + } + }); } return errors; }, diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx index b59b584c4977..49c4902328d6 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx @@ -49,14 +49,12 @@ function BankAccountDetails({isEditing, onNext, resetScreenIndex, formValues, fi if (fields[fieldName].isRequired && values[fieldName] === '') { ErrorUtils.addErrorMessage(errors, fieldName, translate('common.error.fieldRequired')); } - if (!values[fieldName]) { - fields[fieldName].validationRules.forEach((rule) => { - const regExpCheck = new RegExp(rule.regEx); - if (!regExpCheck.test(values[fieldName])) { - ErrorUtils.addErrorMessage(errors, fieldName, rule.errorMessage); - } - }); - } + fields[fieldName].validationRules.forEach((rule) => { + const regExpCheck = new RegExp(rule.regEx); + if (!regExpCheck.test(values[fieldName])) { + ErrorUtils.addErrorMessage(errors, fieldName, rule.errorMessage); + } + }); } return errors; }, diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx index d7096d3299a3..bcbb8ec9a236 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx @@ -59,14 +59,12 @@ function BankInformation({isEditing, onNext, formValues, fieldsMap}: CustomSubSt if (fields[fieldName].isRequired && values[fieldName] === '') { ErrorUtils.addErrorMessage(errors, fieldName, translate('common.error.fieldRequired')); } - if (!values[fieldName]) { - fields[fieldName].validationRules.forEach((rule) => { - const regExpCheck = new RegExp(rule.regEx); - if (!regExpCheck.test(values[fieldName])) { - ErrorUtils.addErrorMessage(errors, fieldName, rule.errorMessage); - } - }); - } + fields[fieldName].validationRules.forEach((rule) => { + const regExpCheck = new RegExp(rule.regEx); + if (!regExpCheck.test(values[fieldName])) { + ErrorUtils.addErrorMessage(errors, fieldName, rule.errorMessage); + } + }); } return errors; }, diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts index fa5c0460aa8a..105a0d827f91 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts +++ b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts @@ -88,14 +88,12 @@ function testValidation(values: InternationalBankAccountForm, fieldsMap: CorpayF if (fieldsMap[fieldName].isRequired && (values[fieldName] ?? '') === '') { return false; } - if (!values[fieldName]) { - fieldsMap[fieldName].validationRules.forEach((rule) => { - const regExpCheck = new RegExp(rule.regEx); - if (!regExpCheck.test(values[fieldName])) { - return false; - } - }); - } + fieldsMap[fieldName].validationRules.forEach((rule) => { + const regExpCheck = new RegExp(rule.regEx); + if (!regExpCheck.test(values[fieldName] ?? '')) { + return false; + } + }); } return true; } From eb4835f153dca63755b8f816f7483e24f7f120b7 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sat, 16 Nov 2024 17:02:09 +0530 Subject: [PATCH 13/54] Tons of fixes --- src/CONST.ts | 1 + src/languages/en.ts | 8 +++++++ src/languages/es.ts | 8 +++++++ .../BankAccountCreateCorpayParams.ts | 7 ++++++ .../GetCorpayBankAccountFieldsParams.ts | 2 ++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 ++ src/libs/actions/BankAccounts.ts | 14 +++++++---- .../ReimbursementAccount/BankAccountStep.tsx | 4 ++-- .../InternationalDepositAccountContent.tsx | 2 +- .../substeps/AccountHolderInformation.tsx | 2 +- .../substeps/AccountType.tsx | 9 +++++--- .../substeps/BankAccountDetails.tsx | 23 ++++++++++++++++++- .../substeps/BankInformation.tsx | 2 +- .../substeps/Confirmation.tsx | 20 +++++++++++++--- .../substeps/CountrySelection.tsx | 4 ++-- 16 files changed, 91 insertions(+), 18 deletions(-) create mode 100644 src/libs/API/parameters/BankAccountCreateCorpayParams.ts diff --git a/src/CONST.ts b/src/CONST.ts index 6ddd0a20d4f8..108e6ee39caf 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -904,6 +904,7 @@ const CONST = { CONFIGURE_REIMBURSEMENT_SETTINGS_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/workspaces/Configure-Reimbursement-Settings', COPILOT_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Assign-or-remove-a-Copilot', DELAYED_SUBMISSION_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/reports/Automatically-submit-employee-reports', + ENCRYPTION_AND_SECURITY_HELP_URL: 'https://help.expensify.com/articles/new-expensify/settings/Encryption-and-Data-Security', // Use Environment.getEnvironmentURL to get the complete URL with port number DEV_NEW_EXPENSIFY_URL: 'https://dev.new.expensify.com:', NAVATTIC: { diff --git a/src/languages/en.ts b/src/languages/en.ts index 1a703f1bea1b..0fe43b50ac56 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1955,6 +1955,14 @@ const translations = { fullName: 'Please enter a valid full name.', }, }, + addPersonalBankAccount: { + countrySelectionStepHeader: "Where's your bank account located?", + accountDetailsStepHeader: 'What are your account details?', + accountTypeStepHeader: 'What type of account is this?', + bankInformationStepHeader: 'What are your bank details?', + accountHolderInformationStepHeader: 'What are the account holder details?', + howDoWeProtectYourData: 'How do we protect your data?', + }, addPersonalBankAccountPage: { enterPassword: 'Enter Expensify password', alreadyAdded: 'This account has already been added.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 2bb66cec6548..0d305c4806e8 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1977,6 +1977,14 @@ const translations = { fullName: 'Please enter a valid full name.', }, }, + addPersonalBankAccount: { + countrySelectionStepHeader: "Where's your bank account located?", + accountDetailsStepHeader: 'What are your account details?', + accountTypeStepHeader: 'What type of account is this?', + bankInformationStepHeader: 'What are your bank details?', + accountHolderInformationStepHeader: 'What are the account holder details?', + howDoWeProtectYourData: 'How do we protect your data?', + }, addPersonalBankAccountPage: { enterPassword: 'Escribe tu contraseña de Expensify', alreadyAdded: 'Esta cuenta ya ha sido añadida.', diff --git a/src/libs/API/parameters/BankAccountCreateCorpayParams.ts b/src/libs/API/parameters/BankAccountCreateCorpayParams.ts new file mode 100644 index 000000000000..9d576e2c907b --- /dev/null +++ b/src/libs/API/parameters/BankAccountCreateCorpayParams.ts @@ -0,0 +1,7 @@ +type BankAccountCreateCorpayParams = { + inputs: string; + isSavings: boolean; + isWithdrawal: boolean; +}; + +export default BankAccountCreateCorpayParams; diff --git a/src/libs/API/parameters/GetCorpayBankAccountFieldsParams.ts b/src/libs/API/parameters/GetCorpayBankAccountFieldsParams.ts index fbce6c1529a7..a1228a023abe 100644 --- a/src/libs/API/parameters/GetCorpayBankAccountFieldsParams.ts +++ b/src/libs/API/parameters/GetCorpayBankAccountFieldsParams.ts @@ -1,6 +1,8 @@ type GetCorpayBankAccountFieldsParams = { countryISO: string; currency?: string; + isWithdrawal?: boolean; + isBusinessBankAccount?: boolean; }; export default GetCorpayBankAccountFieldsParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index e53ca734bf93..28931b2cc356 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -350,3 +350,4 @@ export type {default as TogglePolicyPerDiemParams} from './TogglePolicyPerDiemPa export type {default as OpenPolicyPerDiemRatesPageParams} from './OpenPolicyPerDiemRatesPageParams'; export type {default as TogglePlatformMuteParams} from './TogglePlatformMuteParams'; export type {default as GetCorpayBankAccountFieldsParams} from './GetCorpayBankAccountFieldsParams'; +export type {default as BankAccountCreateCorpayParams} from './BankAccountCreateCorpayParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 81ebe47b2508..66175c951934 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -1027,6 +1027,7 @@ const SIDE_EFFECT_REQUEST_COMMANDS = { DISCONNECT_AS_DELEGATE: 'DisconnectAsDelegate', COMPLETE_HYBRID_APP_ONBOARDING: 'CompleteHybridAppOnboarding', CONNECT_POLICY_TO_QUICKBOOKS_DESKTOP: 'ConnectPolicyToQuickbooksDesktop', + BANK_ACCOUNT_CREATE_CORPAY: 'BankAccount_CreateCorpay', } as const; type SideEffectRequestCommand = ValueOf; @@ -1047,6 +1048,7 @@ type SideEffectRequestCommandParameters = { [SIDE_EFFECT_REQUEST_COMMANDS.DISCONNECT_AS_DELEGATE]: EmptyObject; [SIDE_EFFECT_REQUEST_COMMANDS.COMPLETE_HYBRID_APP_ONBOARDING]: EmptyObject; [SIDE_EFFECT_REQUEST_COMMANDS.CONNECT_POLICY_TO_QUICKBOOKS_DESKTOP]: Parameters.ConnectPolicyToQuickBooksDesktopParams; + [SIDE_EFFECT_REQUEST_COMMANDS.BANK_ACCOUNT_CREATE_CORPAY]: Parameters.BankAccountCreateCorpayParams; }; type ApiRequestCommandParameters = WriteCommandParameters & ReadCommandParameters & SideEffectRequestCommandParameters; diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index fa20de37f09d..8236ca1a1307 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -11,7 +11,7 @@ import type { ValidateBankAccountWithTransactionsParams, VerifyIdentityForBankAccountParams, } from '@libs/API/parameters'; -import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; +import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as Localize from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; @@ -19,7 +19,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Route} from '@src/ROUTES'; -import type {PersonalBankAccountForm} from '@src/types/form'; +import type {InternationalBankAccountForm, PersonalBankAccountForm} from '@src/types/form'; import type {ACHContractStepProps, BeneficialOwnersStepProps, CompanyStepProps, RequestorStepProps} from '@src/types/form/ReimbursementAccountForm'; import type PlaidBankAccount from '@src/types/onyx/PlaidBankAccount'; import type {BankAccountStep, ReimbursementAccountStep, ReimbursementAccountSubStep} from '@src/types/onyx/ReimbursementAccount'; @@ -704,10 +704,10 @@ function validatePlaidSelection(values: FormOnyxValues): Form return errorFields; } -function fetchCorpayFields(bankCountry: string, bankCurrency?: string) { +function fetchCorpayFields(bankCountry: string, bankCurrency?: string, isWithdrawal?: boolean, isBusinessBankAccount?: boolean) { API.write( WRITE_COMMANDS.GET_CORPAY_BANK_ACCOUNT_FIELDS, - {countryISO: bankCountry, currency: bankCurrency}, + {countryISO: bankCountry, currency: bankCurrency, isWithdrawal, isBusinessBankAccount}, { optimisticData: [ { @@ -739,6 +739,11 @@ function fetchCorpayFields(bankCountry: string, bankCurrency?: string) { ); } +function createCorpayBankAccount(data: InternationalBankAccountForm) { + // eslint-disable-next-line rulesdir/no-api-side-effects-method + return API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.BANK_ACCOUNT_CREATE_CORPAY, {isWithdrawal: false, isSavings: true, inputs: JSON.stringify(data)}); +} + export { acceptACHContractForBankAccount, addBusinessWebsiteForDraft, @@ -769,6 +774,7 @@ export { validatePlaidSelection, fetchCorpayFields, getCorpayBankAccountFields, + createCorpayBankAccount, }; export type {BusinessAddress, PersonalAddress}; diff --git a/src/pages/ReimbursementAccount/BankAccountStep.tsx b/src/pages/ReimbursementAccount/BankAccountStep.tsx index afa2c10bc68e..0c79c98b80de 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.tsx +++ b/src/pages/ReimbursementAccount/BankAccountStep.tsx @@ -213,11 +213,11 @@ function BankAccountStep({ {translate('common.privacy')} Link.openExternalLink('https://help.expensify.com/articles/new-expensify/settings/Encryption-and-Data-Security/')} + onPress={() => Link.openExternalLink(CONST.ENCRYPTION_AND_SECURITY_HELP_URL)} style={[styles.flexRow, styles.alignItemsCenter]} accessibilityLabel={translate('bankAccount.yourDataIsSecure')} > - {translate('bankAccount.yourDataIsSecure')} + {translate('bankAccount.yourDataIsSecure')} - {translate('privatePersonalDetails.enterLegalName')} + {translate('addPersonalBankAccount.accountHolderInformationStepHeader')} {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]).map((field) => ( { setCurrentAccountType(country.value); @@ -47,8 +50,8 @@ function AccountType({isEditing, onNext, formValues, fieldsMap}: CustomSubStepPr return ( <> - - {translate('privatePersonalDetails.enterLegalName')} + + {translate('addPersonalBankAccount.accountTypeStepHeader')} - {translate('privatePersonalDetails.enterLegalName')} + {translate('addPersonalBankAccount.accountDetailsStepHeader')} ))} + + + + + {translate('addPersonalBankAccount.howDoWeProtectYourData')} + + + ); diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx index bcbb8ec9a236..085953215109 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx @@ -98,7 +98,7 @@ function BankInformation({isEditing, onNext, formValues, fieldsMap}: CustomSubSt enabledWhenOffline > - {translate('privatePersonalDetails.enterLegalName')} + {translate('addPersonalBankAccount.bankInformationStepHeader')} {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION]).map((field) => ( { - console.log('getDataAndGoToNextStep', formValues); - onNext(); + setIsSubmitting(true); + BankAccounts.createCorpayBankAccount(formValues).then((response) => { + setIsSubmitting(false); + if (response?.jsonCode) { + if (response.jsonCode === CONST.JSON_CODE.SUCCESS) { + onNext(); + } else { + setHasError(true); + } + } + }); }; const summaryItems = [ @@ -97,6 +109,8 @@ function Confirmation({onNext, onMove, isEditing, formValues, fieldsMap}: Custom pageTitle={translate('personalInfoStep.letsDoubleCheck')} summaryItems={summaryItems} showOnfidoLinks={false} + isLoading={isSubmitting} + error={hasError ? translate('common.genericErrorMessage') : ''} /> ); } diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/CountrySelection.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/CountrySelection.tsx index d44e5ca20010..36bffcd70f95 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/CountrySelection.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/CountrySelection.tsx @@ -61,8 +61,8 @@ function CountrySelection({isEditing, onNext, formValues}: CustomSubStepProps) { return ( <> - - {translate('privatePersonalDetails.enterLegalName')} + + {translate('addPersonalBankAccount.countrySelectionStepHeader')} Date: Sat, 16 Nov 2024 17:07:07 +0530 Subject: [PATCH 14/54] Page loading fix --- .../InternationalDepositAccountContent.tsx | 8 +++++++- .../settings/Wallet/InternationalDepositAccount/index.tsx | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx index 339d30eb999b..cf1ea4529b38 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx @@ -1,5 +1,6 @@ import React, {useCallback, useMemo} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; @@ -27,6 +28,7 @@ type InternationalDepositAccountContentProps = { bankAccountList: OnyxEntry; draftValues: OnyxEntry; country: OnyxEntry; + isAccountLoading: boolean; }; const formSteps = [CountrySelection, BankAccountDetails, AccountType, BankInformation, AccountHolderInformation, Confirmation, Success]; @@ -42,7 +44,7 @@ function getSkippedSteps(skipAccountTypeStep: boolean, skipAccountHolderInformat return skippedSteps; } -function InternationalDepositAccountContent({privatePersonalDetails, corpayFields, bankAccountList, draftValues, country}: InternationalDepositAccountContentProps) { +function InternationalDepositAccountContent({privatePersonalDetails, corpayFields, bankAccountList, draftValues, country, isAccountLoading}: InternationalDepositAccountContentProps) { const {translate} = useLocalize(); const values = useMemo( @@ -107,6 +109,10 @@ function InternationalDepositAccountContent({privatePersonalDetails, corpayField nextScreen(); }, [resetScreenIndex, isEditing, nextScreen]); + if (isAccountLoading) { + return ; + } + return ( ; } @@ -26,6 +26,7 @@ function InternationalDepositAccount() { bankAccountList={bankAccountList} draftValues={draftValues} country={country} + isAccountLoading={isAccountLoading ?? false} /> ); } From 54c7ca78a452025e2988da1a42ad69c8116388ec Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sat, 16 Nov 2024 17:22:20 +0530 Subject: [PATCH 15/54] Style fix --- .../InternationalDepositAccount/substeps/BankAccountDetails.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx index fd0ae48d15a7..ab7463b7c874 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx @@ -98,7 +98,7 @@ function BankAccountDetails({isEditing, onNext, resetScreenIndex, formValues, fi /> ))} - + Date: Sat, 16 Nov 2024 17:38:10 +0530 Subject: [PATCH 16/54] Style fix --- .../substeps/AccountHolderInformation.tsx | 18 +++++++++--------- .../substeps/BankAccountDetails.tsx | 4 ++-- .../substeps/BankInformation.tsx | 16 ++++++++-------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx index a6b7d1cf2f5d..2804247b947e 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx @@ -77,22 +77,22 @@ function AccountHolderInformation({isEditing, onNext, formValues, fieldsMap}: Cu ); const getStyle = useCallback( - (field: CorpayFormField) => { + (field: CorpayFormField, index: number) => { if ((field.valueSet ?? []).length > 0) { - return [styles.mhn5, styles.pt2]; + return [styles.mhn5, index === 0 ? styles.pb1 : styles.pv1]; } if (CONST.CORPAY_FIELDS.SPECIAL_LIST_REGION_KEYS.includes(field.id)) { - return [styles.mhn5, styles.pt2]; + return [styles.mhn5, index === 0 ? styles.pb1 : styles.pv1]; } if (CONST.CORPAY_FIELDS.SPECIAL_LIST_ADDRESS_KEYS.includes(field.id)) { - return [styles.mt5]; + return [index === 0 ? styles.pb2 : styles.pv2]; } if (field.id === 'accountHolderCountry') { - return [styles.mhn5, styles.pt2]; + return [styles.mhn5, index === 0 ? styles.pb1 : styles.pv1]; } - return [styles.mt5]; + return [index === 0 ? styles.pb2 : styles.pv2]; }, - [styles.mhn5, styles.mt5, styles.pt2], + [styles.mhn5, styles.pb1, styles.pb2, styles.pv1, styles.pv2], ); return ( @@ -107,9 +107,9 @@ function AccountHolderInformation({isEditing, onNext, formValues, fieldsMap}: Cu > {translate('addPersonalBankAccount.accountHolderInformationStepHeader')} - {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]).map((field) => ( + {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]).map((field, index) => ( {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS] ?? {}).map((field) => ( 0 ? [styles.mhn5, styles.pt2] : [styles.pt5]} + style={(field.valueSet ?? []).length > 0 ? [styles.mhn5, styles.pv1] : [styles.pv2]} key={field.id} > ))} - + { + (field: CorpayFormField, index: number) => { if ((field.valueSet ?? []).length > 0) { - return [styles.mhn5, styles.pt2]; + return [styles.mhn5, index === 0 ? styles.pb1 : styles.pv1]; } if (CONST.CORPAY_FIELDS.SPECIAL_LIST_REGION_KEYS.includes(field.id)) { - return [styles.mhn5, styles.pt2]; + return [styles.mhn5, index === 0 ? styles.pb1 : styles.pv1]; } if (CONST.CORPAY_FIELDS.SPECIAL_LIST_ADDRESS_KEYS.includes(field.id)) { - return [styles.mt5]; + return [index === 0 ? styles.pb2 : styles.pv2]; } - return [styles.mt5]; + return [index === 0 ? styles.pb2 : styles.pv2]; }, - [styles.mhn5, styles.mt5, styles.pt2], + [styles.mhn5, styles.pb1, styles.pb2, styles.pv1, styles.pv2], ); return ( @@ -99,9 +99,9 @@ function BankInformation({isEditing, onNext, formValues, fieldsMap}: CustomSubSt > {translate('addPersonalBankAccount.bankInformationStepHeader')} - {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION]).map((field) => ( + {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION]).map((field, index) => ( Date: Sat, 16 Nov 2024 17:59:41 +0530 Subject: [PATCH 17/54] Style fix --- src/CONST.ts | 6 +-- .../substeps/AccountHolderInformation.tsx | 52 ++++++++++--------- .../substeps/BankInformation.tsx | 50 +++++++++--------- 3 files changed, 57 insertions(+), 51 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 14021143c86b..faa53bf04beb 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -6294,14 +6294,14 @@ const CONST = { CORPAY_FIELDS: { BANK_ACCOUNT_DETAILS_FIELDS: ['accountNumber', 'localAccountNumber', 'routingCode', 'localRoutingCode', 'swiftBicCode'] as string[], ACCOUNT_TYPE_KEY: 'BeneficiaryAccountType', - BANK_INFORMATION_FIELDS: ['bankName', 'bankRegion', 'bankCity', 'bankAddressLine1', 'bankAddressLine2', 'bankPostal', 'BeneficiaryBankBranchName'] as string[], + BANK_INFORMATION_FIELDS: ['bankName', 'bankAddressLine1', 'bankAddressLine2', 'bankCity', 'bankRegion', 'bankPostal', 'BeneficiaryBankBranchName'] as string[], ACCOUNT_HOLDER_FIELDS: [ 'accountHolderName', - 'accountHolderCountry', - 'accountHolderRegion', 'accountHolderAddress1', 'accountHolderAddress2', 'accountHolderCity', + 'accountHolderRegion', + 'accountHolderCountry', 'accountHolderPostal', 'accountHolderPhoneNumber', 'accountHolderEmail', diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx index 2804247b947e..b3b58f54b311 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx @@ -107,30 +107,34 @@ function AccountHolderInformation({isEditing, onNext, formValues, fieldsMap}: Cu > {translate('addPersonalBankAccount.accountHolderInformationStepHeader')} - {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]).map((field, index) => ( - - - - ))} + {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]) + .sort((a, b) => CONST.CORPAY_FIELDS.ACCOUNT_HOLDER_FIELDS.indexOf(a.id) - CONST.CORPAY_FIELDS.ACCOUNT_HOLDER_FIELDS.indexOf(b.id)) + .map((field, index) => ( + + + + ))} ); diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx index 8d9d9d766359..37424d136e68 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx @@ -99,30 +99,32 @@ function BankInformation({isEditing, onNext, formValues, fieldsMap}: CustomSubSt > {translate('addPersonalBankAccount.bankInformationStepHeader')} - {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION]).map((field, index) => ( - - - - ))} + {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION]) + .sort((a, b) => CONST.CORPAY_FIELDS.BANK_INFORMATION_FIELDS.indexOf(a.id) - CONST.CORPAY_FIELDS.BANK_INFORMATION_FIELDS.indexOf(b.id)) + .map((field, index) => ( + + + + ))} ); From 8f3b3ada1b5faf76a8ecd66f2968cdb6b206b04b Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sat, 16 Nov 2024 22:46:40 +0530 Subject: [PATCH 18/54] Fix confirm page --- .../substeps/Confirmation.tsx | 80 +++++++++++++++---- 1 file changed, 64 insertions(+), 16 deletions(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx index ecd324d8cc70..851ef264facf 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx @@ -1,16 +1,35 @@ -import React, {useState} from 'react'; -import ConfirmationStep from '@components/SubStepForms/ConfirmationStep'; +import React, {useCallback, useState} from 'react'; +import CheckboxWithLabel from '@components/CheckboxWithLabel'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import ScrollView from '@components/ScrollView'; +import Text from '@components/Text'; +import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import type {CustomSubStepProps} from '@pages/settings/Wallet/InternationalDepositAccount/types'; import * as BankAccounts from '@userActions/BankAccounts'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; const STEP_INDEXES = CONST.CORPAY_FIELDS.INDEXES.MAPPING; -function Confirmation({onNext, onMove, isEditing, formValues, fieldsMap}: CustomSubStepProps) { +function TermsAndConditionsLabel() { const {translate} = useLocalize(); + return ( + + {translate('common.iAcceptThe')} + {`${translate('common.addCardTermsOfService')}`} + + ); +} + +function Confirmation({onNext, onMove, formValues, fieldsMap}: CustomSubStepProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); const [isSubmitting, setIsSubmitting] = useState(false); - const [hasError, setHasError] = useState(false); const getDataAndGoToNextStep = () => { setIsSubmitting(true); @@ -19,8 +38,6 @@ function Confirmation({onNext, onMove, isEditing, formValues, fieldsMap}: Custom if (response?.jsonCode) { if (response.jsonCode === CONST.JSON_CODE.SUCCESS) { onNext(); - } else { - setHasError(true); } } }); @@ -101,17 +118,48 @@ function Confirmation({onNext, onMove, isEditing, formValues, fieldsMap}: Custom }); } + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors: FormInputErrors = {}; + if (!values.acceptTerms) { + errors.acceptTerms = translate('common.error.acceptTerms'); + } + return errors; + }, + [translate], + ); + return ( - + + {translate('personalInfoStep.letsDoubleCheck')} + {summaryItems.map(({description, title, shouldShowRightIcon, onPress}) => ( + + ))} + + + + ); } From 92c8153d5ac184172b178e471d1b1634b11b7bc4 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sat, 16 Nov 2024 22:53:31 +0530 Subject: [PATCH 19/54] Fix confirm page --- .../InternationalDepositAccount/substeps/Confirmation.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx index 851ef264facf..bdfbcc424ac4 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx @@ -31,9 +31,9 @@ function Confirmation({onNext, onMove, formValues, fieldsMap}: CustomSubStepProp const styles = useThemeStyles(); const [isSubmitting, setIsSubmitting] = useState(false); - const getDataAndGoToNextStep = () => { + const getDataAndGoToNextStep = (values: FormOnyxValues) => { setIsSubmitting(true); - BankAccounts.createCorpayBankAccount(formValues).then((response) => { + BankAccounts.createCorpayBankAccount(values).then((response) => { setIsSubmitting(false); if (response?.jsonCode) { if (response.jsonCode === CONST.JSON_CODE.SUCCESS) { @@ -150,6 +150,7 @@ function Confirmation({onNext, onMove, formValues, fieldsMap}: CustomSubStepProp style={[styles.mh5, styles.flexGrow1]} enabledWhenOffline={false} isSubmitDisabled={isSubmitting} + shouldHideFixErrorsAlert > Date: Sat, 16 Nov 2024 22:58:10 +0530 Subject: [PATCH 20/54] Fix confirm page --- .../InternationalDepositAccount/substeps/Confirmation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx index bdfbcc424ac4..63694b112ffd 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx @@ -33,7 +33,7 @@ function Confirmation({onNext, onMove, formValues, fieldsMap}: CustomSubStepProp const getDataAndGoToNextStep = (values: FormOnyxValues) => { setIsSubmitting(true); - BankAccounts.createCorpayBankAccount(values).then((response) => { + BankAccounts.createCorpayBankAccount({...formValues, ...values}).then((response) => { setIsSubmitting(false); if (response?.jsonCode) { if (response.jsonCode === CONST.JSON_CODE.SUCCESS) { From ae133b78affc30e41f2cecf06935133ecb709c50 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sun, 17 Nov 2024 05:30:00 +0530 Subject: [PATCH 21/54] Fix confirm page --- src/libs/actions/BankAccounts.ts | 12 ++++++++++-- .../substeps/Confirmation.tsx | 9 ++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 8236ca1a1307..5e7a1910ba02 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -739,9 +739,17 @@ function fetchCorpayFields(bankCountry: string, bankCurrency?: string, isWithdra ); } -function createCorpayBankAccount(data: InternationalBankAccountForm) { +function createCorpayBankAccount(data: InternationalBankAccountForm, classification: string, destinationCountry: string, preferredMethod: string) { + const inputData = { + ...data, + classification, + destinationCountry, + preferredMethod, + setupType: 'manual', + fieldsType: 'international', + }; // eslint-disable-next-line rulesdir/no-api-side-effects-method - return API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.BANK_ACCOUNT_CREATE_CORPAY, {isWithdrawal: false, isSavings: true, inputs: JSON.stringify(data)}); + return API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.BANK_ACCOUNT_CREATE_CORPAY, {isWithdrawal: false, isSavings: true, inputs: JSON.stringify(inputData)}); } export { diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx index 63694b112ffd..ba5f6a46677f 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx @@ -1,4 +1,5 @@ import React, {useCallback, useState} from 'react'; +import {useOnyx} from 'react-native-onyx'; import CheckboxWithLabel from '@components/CheckboxWithLabel'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; @@ -30,10 +31,16 @@ function Confirmation({onNext, onMove, formValues, fieldsMap}: CustomSubStepProp const {translate} = useLocalize(); const styles = useThemeStyles(); const [isSubmitting, setIsSubmitting] = useState(false); + const [corpayFields] = useOnyx(ONYXKEYS.CORPAY_FIELDS); const getDataAndGoToNextStep = (values: FormOnyxValues) => { setIsSubmitting(true); - BankAccounts.createCorpayBankAccount({...formValues, ...values}).then((response) => { + BankAccounts.createCorpayBankAccount( + {...formValues, ...values}, + corpayFields?.classification ?? '', + corpayFields?.destinationCountry ?? '', + corpayFields?.preferredMethod ?? '', + ).then((response) => { setIsSubmitting(false); if (response?.jsonCode) { if (response.jsonCode === CONST.JSON_CODE.SUCCESS) { From 9fe9bde761bb449cd4066013bc8352356a1a2fc3 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 18 Nov 2024 19:04:32 +0530 Subject: [PATCH 22/54] Fix API command --- src/libs/actions/BankAccounts.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 5e7a1910ba02..31c43408dca7 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -747,6 +747,8 @@ function createCorpayBankAccount(data: InternationalBankAccountForm, classificat preferredMethod, setupType: 'manual', fieldsType: 'international', + country: data.bankCountry, + currency: data.bankCurrency, }; // eslint-disable-next-line rulesdir/no-api-side-effects-method return API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.BANK_ACCOUNT_CREATE_CORPAY, {isWithdrawal: false, isSavings: true, inputs: JSON.stringify(inputData)}); From a5ecb6a81188ff0ea25d84c2ed8e7c6858fb3086 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 18 Nov 2024 22:56:50 +0530 Subject: [PATCH 23/54] Fix API command --- .../Wallet/InternationalDepositAccount/utils.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts index 105a0d827f91..77caabd5007d 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts +++ b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts @@ -45,19 +45,19 @@ function getSubstepValues( const [street1, street2] = street ? street.split('\n') : [undefined, undefined]; const firstName = privatePersonalDetails?.legalFirstName ?? ''; const lastName = privatePersonalDetails?.legalLastName ?? ''; - const fullName = `${firstName} ${lastName}`.trim(); + const fullName = `${firstName} ${lastName}`.trim() ? `${firstName} ${lastName}`.trim() : undefined; const latestBankAccount = getLatestCreatedBankAccount(bankAccountList); return { ...internationalBankAccountDraft, bankCountry: internationalBankAccountDraft?.bankCountry ?? corpayFields?.bankCountry ?? address?.country ?? latestBankAccount?.bankCountry ?? country ?? '', bankCurrency: internationalBankAccountDraft?.bankCurrency ?? corpayFields?.bankCurrency, accountHolderName: internationalBankAccountDraft?.accountHolderName ?? fullName, - accountHolderAddress1: internationalBankAccountDraft?.accountHolderAddress1 ?? street1 ?? '', - accountHolderAddress2: internationalBankAccountDraft?.accountHolderAddress2 ?? street2 ?? '', - accountHolderCity: internationalBankAccountDraft?.accountHolderCity ?? address?.city ?? '', - accountHolderCountry: internationalBankAccountDraft?.accountHolderCountry ?? address?.country ?? '', - accountHolderPostal: internationalBankAccountDraft?.accountHolderPostal ?? address?.zip ?? '', - accountHolderPhoneNumber: internationalBankAccountDraft?.accountHolderPhoneNumber ?? privatePersonalDetails?.phoneNumber ?? '', + accountHolderAddress1: internationalBankAccountDraft?.accountHolderAddress1 ?? street1, + accountHolderAddress2: internationalBankAccountDraft?.accountHolderAddress2 ?? street2, + accountHolderCity: internationalBankAccountDraft?.accountHolderCity ?? address?.city, + accountHolderCountry: internationalBankAccountDraft?.accountHolderCountry ?? address?.country, + accountHolderPostal: internationalBankAccountDraft?.accountHolderPostal ?? address?.zip, + accountHolderPhoneNumber: internationalBankAccountDraft?.accountHolderPhoneNumber ?? privatePersonalDetails?.phoneNumber, } as unknown as InternationalBankAccountForm; } From 70abea68ed36d6478d24ab93eabbbda16163a356 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 19 Nov 2024 10:01:26 +0530 Subject: [PATCH 24/54] Fix opening page --- src/libs/actions/BankAccounts.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 31c43408dca7..9e0e87cfe562 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -24,7 +24,6 @@ import type {ACHContractStepProps, BeneficialOwnersStepProps, CompanyStepProps, import type PlaidBankAccount from '@src/types/onyx/PlaidBankAccount'; import type {BankAccountStep, ReimbursementAccountStep, ReimbursementAccountSubStep} from '@src/types/onyx/ReimbursementAccount'; import type {OnyxData} from '@src/types/onyx/Request'; -import * as FormActions from './FormActions'; import * as ReimbursementAccount from './ReimbursementAccount'; export { @@ -63,6 +62,12 @@ function clearPlaid(): Promise { return Onyx.set(ONYXKEYS.PLAID_DATA, CONST.PLAID.DEFAULT_DATA); } +function clearInternationalBankAccount() { + clearPlaid(); + Onyx.set(ONYXKEYS.CORPAY_FIELDS, null); + return Onyx.set(ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM_DRAFT, null); +} + function openPlaidView() { clearPlaid().then(() => ReimbursementAccount.setBankAccountSubStep(CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID)); } @@ -75,9 +80,7 @@ function setPlaidEvent(eventName: string | null) { * Open the personal bank account setup flow, with an optional exitReportID to redirect to once the flow is finished. */ function openPersonalBankAccountSetupView(exitReportID?: string, isUserValidated = true) { - clearPlaid().then(() => { - Onyx.set(ONYXKEYS.CORPAY_FIELDS, null); - FormActions.clearDraftValues(ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM); + clearInternationalBankAccount().then(() => { if (exitReportID) { Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {exitReportID}); } From 13e2a1619c818208267759e3467c7b06923af0ac Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Wed, 20 Nov 2024 13:05:47 +0530 Subject: [PATCH 25/54] Fix API command --- .../InternationalDepositAccountContent.tsx | 8 ++++---- .../InternationalDepositAccount/utils.ts | 18 +++++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx index cf1ea4529b38..c6c2ee73ed1d 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx @@ -47,15 +47,15 @@ function getSkippedSteps(skipAccountTypeStep: boolean, skipAccountHolderInformat function InternationalDepositAccountContent({privatePersonalDetails, corpayFields, bankAccountList, draftValues, country, isAccountLoading}: InternationalDepositAccountContentProps) { const {translate} = useLocalize(); + const fieldsMap = useMemo(() => getFieldsMap(corpayFields), [corpayFields]); + const values = useMemo( - () => getSubstepValues(privatePersonalDetails, corpayFields, bankAccountList, draftValues, country), - [privatePersonalDetails, corpayFields, bankAccountList, draftValues, country], + () => getSubstepValues(privatePersonalDetails, corpayFields, bankAccountList, draftValues, country, fieldsMap), + [privatePersonalDetails, corpayFields, bankAccountList, draftValues, country, fieldsMap], ); const initialAccountHolderDetailsValues = useMemo(() => getInitialPersonalDetailsValues(privatePersonalDetails), [privatePersonalDetails]); - const fieldsMap = useMemo(() => getFieldsMap(corpayFields), [corpayFields]); - const startFrom = useMemo(() => getInitialSubstep(values, fieldsMap), [fieldsMap, values]); const skipAccountTypeStep = isEmptyObject(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_TYPE]); diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts index 77caabd5007d..19bc8c5397a0 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts +++ b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts @@ -39,8 +39,10 @@ function getSubstepValues( bankAccountList: OnyxEntry, internationalBankAccountDraft: OnyxEntry, country: OnyxEntry, + fieldsMap: Record, CorpayFieldsMap>, ): InternationalBankAccountForm { const address = PersonalDetailsUtils.getCurrentAddress(privatePersonalDetails); + const personalDetailsFieldMap = fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]; const {street} = address ?? {}; const [street1, street2] = street ? street.split('\n') : [undefined, undefined]; const firstName = privatePersonalDetails?.legalFirstName ?? ''; @@ -51,13 +53,15 @@ function getSubstepValues( ...internationalBankAccountDraft, bankCountry: internationalBankAccountDraft?.bankCountry ?? corpayFields?.bankCountry ?? address?.country ?? latestBankAccount?.bankCountry ?? country ?? '', bankCurrency: internationalBankAccountDraft?.bankCurrency ?? corpayFields?.bankCurrency, - accountHolderName: internationalBankAccountDraft?.accountHolderName ?? fullName, - accountHolderAddress1: internationalBankAccountDraft?.accountHolderAddress1 ?? street1, - accountHolderAddress2: internationalBankAccountDraft?.accountHolderAddress2 ?? street2, - accountHolderCity: internationalBankAccountDraft?.accountHolderCity ?? address?.city, - accountHolderCountry: internationalBankAccountDraft?.accountHolderCountry ?? address?.country, - accountHolderPostal: internationalBankAccountDraft?.accountHolderPostal ?? address?.zip, - accountHolderPhoneNumber: internationalBankAccountDraft?.accountHolderPhoneNumber ?? privatePersonalDetails?.phoneNumber, + accountHolderName: !isEmptyObject(personalDetailsFieldMap?.accountHolderName) ? internationalBankAccountDraft?.accountHolderName ?? fullName : undefined, + accountHolderAddress1: !isEmptyObject(personalDetailsFieldMap?.accountHolderAddress1) ? internationalBankAccountDraft?.accountHolderAddress1 ?? street1 : undefined, + accountHolderAddress2: !isEmptyObject(personalDetailsFieldMap?.accountHolderAddress2) ? internationalBankAccountDraft?.accountHolderAddress2 ?? street2 : undefined, + accountHolderCity: !isEmptyObject(personalDetailsFieldMap?.accountHolderCity) ? internationalBankAccountDraft?.accountHolderCity ?? address?.city : undefined, + accountHolderCountry: !isEmptyObject(personalDetailsFieldMap?.accountHolderCountry) ? internationalBankAccountDraft?.accountHolderCountry ?? address?.country : undefined, + accountHolderPostal: !isEmptyObject(personalDetailsFieldMap?.accountHolderPostal) ? internationalBankAccountDraft?.accountHolderPostal ?? address?.zip : undefined, + accountHolderPhoneNumber: !isEmptyObject(personalDetailsFieldMap?.accountHolderPhoneNumber) + ? internationalBankAccountDraft?.accountHolderPhoneNumber ?? privatePersonalDetails?.phoneNumber + : undefined, } as unknown as InternationalBankAccountForm; } From d6284c33dc6abf52c1ab0a407ccbe28da55590cb Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sun, 24 Nov 2024 19:50:50 +0530 Subject: [PATCH 26/54] Disable changing of account holder country --- .../substeps/AccountHolderInformation.tsx | 12 ++---------- .../Wallet/InternationalDepositAccount/utils.ts | 4 +++- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx index b3b58f54b311..1ecb79f185ed 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx @@ -1,7 +1,6 @@ import React, {useCallback} from 'react'; import {View} from 'react-native'; import AddressSearch from '@components/AddressSearch'; -import CountryPicker from '@components/CountryPicker'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; @@ -29,9 +28,6 @@ function getInputComponent(field: CorpayFormField) { if (CONST.CORPAY_FIELDS.SPECIAL_LIST_ADDRESS_KEYS.includes(field.id)) { return AddressSearch; } - if (field.id === 'accountHolderCountry') { - return CountryPicker; - } return TextInput; } @@ -87,9 +83,6 @@ function AccountHolderInformation({isEditing, onNext, formValues, fieldsMap}: Cu if (CONST.CORPAY_FIELDS.SPECIAL_LIST_ADDRESS_KEYS.includes(field.id)) { return [index === 0 ? styles.pb2 : styles.pv2]; } - if (field.id === 'accountHolderCountry') { - return [styles.mhn5, index === 0 ? styles.pb1 : styles.pv1]; - } return [index === 0 ? styles.pb2 : styles.pv2]; }, [styles.mhn5, styles.pb1, styles.pb2, styles.pv1, styles.pv2], @@ -120,15 +113,14 @@ function AccountHolderInformation({isEditing, onNext, formValues, fieldsMap}: Cu defaultValue={formValues[field.id]} label={field.label + (field.isRequired ? '' : ` (${translate('common.optional')})`)} items={getItems(field)} + disabled={field.id === 'accountHolderCountry'} renamedInputKeys={{ street: isEmptyObject(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]?.accountHolderAddress1) ? '' : 'accountHolderAddress1', street2: isEmptyObject(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]?.accountHolderAddress2) ? '' : 'accountHolderAddress2', city: isEmptyObject(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]?.accountHolderCity) ? '' : 'accountHolderCity', state: '', zipCode: isEmptyObject(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]?.accountHolderPostal) ? '' : 'accountHolderPostal', - country: (isEmptyObject(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]?.accountHolderCountry) - ? '' - : 'accountHolderCountry') as Country, + country: '', lat: '', lng: '', }} diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts index 19bc8c5397a0..123eb5fa5072 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts +++ b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts @@ -57,7 +57,9 @@ function getSubstepValues( accountHolderAddress1: !isEmptyObject(personalDetailsFieldMap?.accountHolderAddress1) ? internationalBankAccountDraft?.accountHolderAddress1 ?? street1 : undefined, accountHolderAddress2: !isEmptyObject(personalDetailsFieldMap?.accountHolderAddress2) ? internationalBankAccountDraft?.accountHolderAddress2 ?? street2 : undefined, accountHolderCity: !isEmptyObject(personalDetailsFieldMap?.accountHolderCity) ? internationalBankAccountDraft?.accountHolderCity ?? address?.city : undefined, - accountHolderCountry: !isEmptyObject(personalDetailsFieldMap?.accountHolderCountry) ? internationalBankAccountDraft?.accountHolderCountry ?? address?.country : undefined, + accountHolderCountry: !isEmptyObject(personalDetailsFieldMap?.accountHolderCountry) + ? internationalBankAccountDraft?.accountHolderCountry ?? corpayFields?.bankCountry ?? address?.country ?? latestBankAccount?.bankCountry ?? country ?? '' + : undefined, accountHolderPostal: !isEmptyObject(personalDetailsFieldMap?.accountHolderPostal) ? internationalBankAccountDraft?.accountHolderPostal ?? address?.zip : undefined, accountHolderPhoneNumber: !isEmptyObject(personalDetailsFieldMap?.accountHolderPhoneNumber) ? internationalBankAccountDraft?.accountHolderPhoneNumber ?? privatePersonalDetails?.phoneNumber From 0962c44a3381085469424ca5e83738e1cf65b86a Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 25 Nov 2024 09:50:22 +0530 Subject: [PATCH 27/54] Fix lint --- .../substeps/AccountHolderInformation.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx index 1ecb79f185ed..d45de0ef328a 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx @@ -13,7 +13,6 @@ import * as ErrorUtils from '@libs/ErrorUtils'; import type {CustomSubStepProps} from '@pages/settings/Wallet/InternationalDepositAccount/types'; import Text from '@src/components/Text'; import CONST from '@src/CONST'; -import type {Country} from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {CorpayFormField} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; From 3937be1462100005575a8e4268f4b49be739b175 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 25 Nov 2024 16:14:41 +0530 Subject: [PATCH 28/54] Fix viewing bank account --- src/libs/PaymentUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/PaymentUtils.ts b/src/libs/PaymentUtils.ts index c18ebd217406..95fc334b906c 100644 --- a/src/libs/PaymentUtils.ts +++ b/src/libs/PaymentUtils.ts @@ -27,10 +27,10 @@ function hasExpensifyPaymentMethod(fundList: Record, bankAccountLi return validBankAccount || (shouldIncludeDebitCard && validDebitCard); } -function getPaymentMethodDescription(accountType: AccountType, account: BankAccount['accountData'] | Fund['accountData'] | ACHAccount): string { +function getPaymentMethodDescription(accountType: AccountType, account: BankAccount['accountData'] | Fund['accountData'] | ACHAccount, bankCurrency?: string): string { if (account) { if (accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT && 'accountNumber' in account) { - return `${Localize.translateLocal('paymentMethodList.accountLastFour')} ${account.accountNumber?.slice(-4)}`; + return `${bankCurrency} ${CONST.DOT_SEPARATOR} ${Localize.translateLocal('paymentMethodList.accountLastFour')} ${account.accountNumber?.slice(-4)}`; } if (accountType === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT && 'accountNumber' in account) { return `${Localize.translateLocal('paymentMethodList.accountLastFour')} ${account.accountNumber?.slice(-4)}`; @@ -61,7 +61,7 @@ function formatPaymentMethods(bankAccountList: Record, fund }); combinedPaymentMethods.push({ ...bankAccount, - description: getPaymentMethodDescription(bankAccount?.accountType, bankAccount.accountData), + description: getPaymentMethodDescription(bankAccount?.accountType, bankAccount.accountData, bankAccount.bankCurrency), icon, iconSize, iconHeight, From 0ca192557e16ec278b99665b6d6f72bfe1cfa5af Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 25 Nov 2024 16:23:16 +0530 Subject: [PATCH 29/54] Fix start flow --- src/libs/actions/BankAccounts.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 9e0e87cfe562..d4d819fb7ee3 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -63,9 +63,9 @@ function clearPlaid(): Promise { } function clearInternationalBankAccount() { - clearPlaid(); - Onyx.set(ONYXKEYS.CORPAY_FIELDS, null); - return Onyx.set(ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM_DRAFT, null); + return clearPlaid() + .then(() => Onyx.set(ONYXKEYS.CORPAY_FIELDS, null)) + .then(() => Onyx.set(ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM_DRAFT, null)); } function openPlaidView() { From 3f590093cb4c52b798ada291648a5290e0360ceb Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 25 Nov 2024 16:36:48 +0530 Subject: [PATCH 30/54] Fix menu option --- src/pages/settings/Wallet/PaymentMethodList.tsx | 2 ++ src/pages/settings/Wallet/WalletPage/WalletPage.tsx | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index 961f1bffe007..93a2b9f7e0fe 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -96,6 +96,7 @@ type PaymentMethodListProps = { icon?: FormattedSelectedPaymentMethodIcon, isDefault?: boolean, methodID?: number, + description?: string, ) => void; /** The policy invoice's transfer bank accountID */ @@ -332,6 +333,7 @@ function PaymentMethodList({ }, paymentMethod.isDefault, paymentMethod.methodID, + paymentMethod.description, ), wrapperStyle: isMethodActive ? [StyleUtils.getButtonBackgroundColorStyle(CONST.BUTTON_STATES.PRESSED)] : null, disabled: paymentMethod.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx index 7b9366370349..5665abb52105 100644 --- a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx +++ b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx @@ -136,6 +136,7 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) { icon?: FormattedSelectedPaymentMethodIcon, isDefault?: boolean, methodID?: string | number, + description?: string, ) => { if (shouldShowAddPaymentMenu) { setShouldShowAddPaymentMenu(false); @@ -157,14 +158,14 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) { formattedSelectedPaymentMethod = { title: account?.addressName ?? '', icon, - description: PaymentUtils.getPaymentMethodDescription(accountType, account), + description: description ?? PaymentUtils.getPaymentMethodDescription(accountType, account), type: CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT, }; } else if (accountType === CONST.PAYMENT_METHODS.DEBIT_CARD) { formattedSelectedPaymentMethod = { title: account?.addressName ?? '', icon, - description: PaymentUtils.getPaymentMethodDescription(accountType, account), + description: description ?? PaymentUtils.getPaymentMethodDescription(accountType, account), type: CONST.PAYMENT_METHODS.DEBIT_CARD, }; } From 09794f93e83f030a99571280a9fec71159512be0 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 25 Nov 2024 17:14:46 +0530 Subject: [PATCH 31/54] Fix success page in native --- .../InternationalDepositAccountContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx index c6c2ee73ed1d..2a7a4c27158e 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx @@ -115,7 +115,7 @@ function InternationalDepositAccountContent({privatePersonalDetails, corpayField return ( From 1d701a5170d9e2881ad780c159ff823a18aa38ac Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 29 Nov 2024 09:16:13 +0530 Subject: [PATCH 32/54] Fix focus on selecting option --- .../Wallet/InternationalDepositAccount/substeps/AccountType.tsx | 1 + .../InternationalDepositAccount/substeps/CountrySelection.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountType.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountType.tsx index b663d53d3c03..013683a38176 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountType.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountType.tsx @@ -63,6 +63,7 @@ function AccountType({isEditing, onNext, formValues, fieldsMap}: CustomSubStepPr shouldStopPropagation shouldUseDynamicMaxToRenderPerBatch showConfirmButton + shouldUpdateFocusedIndex confirmButtonText={isEditing ? translate('common.confirm') : translate('common.next')} /> diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/CountrySelection.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/CountrySelection.tsx index 36bffcd70f95..36d35824e304 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/CountrySelection.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/CountrySelection.tsx @@ -80,6 +80,7 @@ function CountrySelection({isEditing, onNext, formValues}: CustomSubStepProps) { showConfirmButton confirmButtonText={isEditing ? translate('common.confirm') : translate('common.next')} isConfirmButtonDisabled={isOffline} + shouldUpdateFocusedIndex /> ); From 814a94cb666bfc5a123e932e6d4d37679814ede5 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 29 Nov 2024 09:20:01 +0530 Subject: [PATCH 33/54] Spanish translations --- src/languages/es.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 96bef8a26cfc..01ab985bfbb4 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1992,12 +1992,12 @@ const translations = { }, }, addPersonalBankAccount: { - countrySelectionStepHeader: "Where's your bank account located?", - accountDetailsStepHeader: 'What are your account details?', - accountTypeStepHeader: 'What type of account is this?', - bankInformationStepHeader: 'What are your bank details?', - accountHolderInformationStepHeader: 'What are the account holder details?', - howDoWeProtectYourData: 'How do we protect your data?', + countrySelectionStepHeader: '¿Dónde está ubicada tu cuenta bancaria?', + accountDetailsStepHeader: '¿Cuáles son los detalles de tu cuenta?', + accountTypeStepHeader: '¿Qué tipo de cuenta es esta?', + bankInformationStepHeader: '¿Cuáles son los detalles de tu banco?', + accountHolderInformationStepHeader: '¿Cuáles son los detalles del titular de la cuenta?', + howDoWeProtectYourData: '¿Cómo protegemos tus datos?', }, addPersonalBankAccountPage: { enterPassword: 'Escribe tu contraseña de Expensify', From 8442147a020546dd49523bbb6ef20a514a6dc444 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 29 Nov 2024 09:34:57 +0530 Subject: [PATCH 34/54] Applying suggestions --- .../substeps/AccountHolderInformation.tsx | 21 ++------------- .../substeps/BankAccountDetails.tsx | 21 ++------------- .../substeps/BankInformation.tsx | 21 ++------------- .../InternationalDepositAccount/utils.ts | 26 ++++++++++++++++++- 4 files changed, 31 insertions(+), 58 deletions(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx index d45de0ef328a..c4b2465228f8 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx @@ -9,8 +9,8 @@ import ValuePicker from '@components/ValuePicker'; import useInternationalBankAccountFormSubmit from '@hooks/useInternationalBankAccountFormSubmit'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ErrorUtils from '@libs/ErrorUtils'; import type {CustomSubStepProps} from '@pages/settings/Wallet/InternationalDepositAccount/types'; +import {getValidationErrors} from '@pages/settings/Wallet/InternationalDepositAccount/utils'; import Text from '@src/components/Text'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -49,24 +49,7 @@ function AccountHolderInformation({isEditing, onNext, formValues, fieldsMap}: Cu const validate = useCallback( (values: FormOnyxValues): FormInputErrors => { - const errors = {}; - const fields = fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]; - for (const fieldName in fields) { - if (!fieldName) { - // eslint-disable-next-line no-continue - continue; - } - if (fields[fieldName].isRequired && values[fieldName] === '') { - ErrorUtils.addErrorMessage(errors, fieldName, translate('common.error.fieldRequired')); - } - fields[fieldName].validationRules.forEach((rule) => { - const regExpCheck = new RegExp(rule.regEx); - if (!regExpCheck.test(values[fieldName])) { - ErrorUtils.addErrorMessage(errors, fieldName, rule.errorMessage); - } - }); - } - return errors; + return getValidationErrors(values, fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION], translate); }, [fieldsMap, translate], ); diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx index b8c440034330..5640674f6f1b 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx @@ -13,8 +13,8 @@ import useInternationalBankAccountFormSubmit from '@hooks/useInternationalBankAc import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ErrorUtils from '@libs/ErrorUtils'; import type {CustomSubStepProps} from '@pages/settings/Wallet/InternationalDepositAccount/types'; +import {getValidationErrors} from '@pages/settings/Wallet/InternationalDepositAccount/utils'; import * as BankAccounts from '@userActions/BankAccounts'; import Text from '@src/components/Text'; import CONST from '@src/CONST'; @@ -44,24 +44,7 @@ function BankAccountDetails({isEditing, onNext, resetScreenIndex, formValues, fi const validate = useCallback( (values: FormOnyxValues): FormInputErrors => { - const errors = {}; - const fields = fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS]; - for (const fieldName in fields) { - if (!fieldName) { - // eslint-disable-next-line no-continue - continue; - } - if (fields[fieldName].isRequired && values[fieldName] === '') { - ErrorUtils.addErrorMessage(errors, fieldName, translate('common.error.fieldRequired')); - } - fields[fieldName].validationRules.forEach((rule) => { - const regExpCheck = new RegExp(rule.regEx); - if (!regExpCheck.test(values[fieldName])) { - ErrorUtils.addErrorMessage(errors, fieldName, rule.errorMessage); - } - }); - } - return errors; + return getValidationErrors(values, fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS], translate); }, [fieldsMap, translate], ); diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx index 37424d136e68..f4998062ed32 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankInformation.tsx @@ -9,8 +9,8 @@ import ValuePicker from '@components/ValuePicker'; import useInternationalBankAccountFormSubmit from '@hooks/useInternationalBankAccountFormSubmit'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ErrorUtils from '@libs/ErrorUtils'; import type {CustomSubStepProps} from '@pages/settings/Wallet/InternationalDepositAccount/types'; +import {getValidationErrors} from '@pages/settings/Wallet/InternationalDepositAccount/utils'; import Text from '@src/components/Text'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -49,24 +49,7 @@ function BankInformation({isEditing, onNext, formValues, fieldsMap}: CustomSubSt const validate = useCallback( (values: FormOnyxValues): FormInputErrors => { - const errors = {}; - const fields = fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION]; - for (const fieldName in fields) { - if (!fieldName) { - // eslint-disable-next-line no-continue - continue; - } - if (fields[fieldName].isRequired && values[fieldName] === '') { - ErrorUtils.addErrorMessage(errors, fieldName, translate('common.error.fieldRequired')); - } - fields[fieldName].validationRules.forEach((rule) => { - const regExpCheck = new RegExp(rule.regEx); - if (!regExpCheck.test(values[fieldName])) { - ErrorUtils.addErrorMessage(errors, fieldName, rule.errorMessage); - } - }); - } - return errors; + return getValidationErrors(values, fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_INFORMATION], translate); }, [fieldsMap, translate], ); diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts index 123eb5fa5072..5f3370693403 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts +++ b/src/pages/settings/Wallet/InternationalDepositAccount/utils.ts @@ -1,8 +1,12 @@ import lodashSortBy from 'lodash/sortBy'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; +import type {FormOnyxValues} from '@components/Form/types'; +import type {LocaleContextProps} from '@components/LocaleContextProvider'; +import * as ErrorUtils from '@libs/ErrorUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import CONST from '@src/CONST'; +import type ONYXKEYS from '@src/ONYXKEYS'; import type {InternationalBankAccountForm} from '@src/types/form'; import type {BankAccount, BankAccountList, CorpayFields, PrivatePersonalDetails} from '@src/types/onyx'; import type {CorpayFieldsMap} from '@src/types/onyx/CorpayFields'; @@ -123,4 +127,24 @@ function getInitialSubstep(values: InternationalBankAccountForm, fieldsMap: Reco return CONST.CORPAY_FIELDS.INDEXES.MAPPING.CONFIRMATION; } -export {getFieldsMap, getSubstepValues, getInitialPersonalDetailsValues, getInitialSubstep, testValidation}; +function getValidationErrors(values: FormOnyxValues, fieldsMap: CorpayFieldsMap, translate: LocaleContextProps['translate']) { + const errors = {}; + for (const fieldName in fieldsMap) { + if (!fieldName) { + // eslint-disable-next-line no-continue + continue; + } + if (fieldsMap[fieldName].isRequired && values[fieldName] === '') { + ErrorUtils.addErrorMessage(errors, fieldName, translate('common.error.fieldRequired')); + } + fieldsMap[fieldName].validationRules.forEach((rule) => { + const regExpCheck = new RegExp(rule.regEx); + if (!regExpCheck.test(values[fieldName])) { + ErrorUtils.addErrorMessage(errors, fieldName, rule.errorMessage); + } + }); + } + return errors; +} + +export {getFieldsMap, getSubstepValues, getInitialPersonalDetailsValues, getInitialSubstep, testValidation, getValidationErrors}; From 3347a48d8c6e192cba5dfcfb726581e987154bd0 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 6 Dec 2024 13:07:07 +0530 Subject: [PATCH 35/54] Fix padding --- src/components/CurrencyPicker.tsx | 2 +- .../InternationalDepositAccountContent.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/CurrencyPicker.tsx b/src/components/CurrencyPicker.tsx index 56735d5b3505..6a8a078391e2 100644 --- a/src/components/CurrencyPicker.tsx +++ b/src/components/CurrencyPicker.tsx @@ -58,7 +58,7 @@ function CurrencyPicker({value, errorText, onInputChange = () => {}}: CurrencyPi From c082ae6ab94a20fa998336dbc0a49a717defa0ea Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 6 Dec 2024 13:37:57 +0530 Subject: [PATCH 36/54] Fix components --- src/components/TextPicker/index.tsx | 11 +++++++++-- .../substeps/AccountHolderInformation.tsx | 7 +++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/components/TextPicker/index.tsx b/src/components/TextPicker/index.tsx index 968338391aaa..cdab78d911d2 100644 --- a/src/components/TextPicker/index.tsx +++ b/src/components/TextPicker/index.tsx @@ -7,11 +7,17 @@ import CONST from '@src/CONST'; import TextSelectorModal from './TextSelectorModal'; import type {TextPickerProps} from './types'; -function TextPicker({value, description, placeholder = '', errorText = '', onInputChange, furtherDetails, rightLabel, ...rest}: TextPickerProps, forwardedRef: ForwardedRef) { +function TextPicker( + {value, description, placeholder = '', errorText = '', onInputChange, furtherDetails, rightLabel, disabled = false, ...rest}: TextPickerProps, + forwardedRef: ForwardedRef, +) { const styles = useThemeStyles(); const [isPickerVisible, setIsPickerVisible] = useState(false); const showPickerModal = () => { + if (disabled) { + return; + } setIsPickerVisible(true); }; @@ -30,7 +36,7 @@ function TextPicker({value, description, placeholder = '', errorText = '', onInp diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx index c4b2465228f8..c5152591234a 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx @@ -16,6 +16,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {CorpayFormField} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import TextPicker from '@components/TextPicker'; function getInputComponent(field: CorpayFormField) { if ((field.valueSet ?? []).length > 0) { @@ -27,6 +28,9 @@ function getInputComponent(field: CorpayFormField) { if (CONST.CORPAY_FIELDS.SPECIAL_LIST_ADDRESS_KEYS.includes(field.id)) { return AddressSearch; } + if (field.id === 'accountHolderCountry') { + return TextPicker; + } return TextInput; } @@ -65,6 +69,9 @@ function AccountHolderInformation({isEditing, onNext, formValues, fieldsMap}: Cu if (CONST.CORPAY_FIELDS.SPECIAL_LIST_ADDRESS_KEYS.includes(field.id)) { return [index === 0 ? styles.pb2 : styles.pv2]; } + if (field.id === 'accountHolderCountry') { + return [styles.mhn5, index === 0 ? styles.pb1 : styles.pv1]; + } return [index === 0 ? styles.pb2 : styles.pv2]; }, [styles.mhn5, styles.pb1, styles.pb2, styles.pv1, styles.pv2], From 2bc8d2e5274cd012b9033ba0b6bd72e1254e9424 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 6 Dec 2024 13:43:06 +0530 Subject: [PATCH 37/54] Fix textpicker label --- .../substeps/AccountHolderInformation.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx index c5152591234a..0ec69d0cb46e 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx @@ -101,6 +101,7 @@ function AccountHolderInformation({isEditing, onNext, formValues, fieldsMap}: Cu inputID={field.id} defaultValue={formValues[field.id]} label={field.label + (field.isRequired ? '' : ` (${translate('common.optional')})`)} + description={field.id === 'accountHolderCountry' ? field.label : undefined} items={getItems(field)} disabled={field.id === 'accountHolderCountry'} renamedInputKeys={{ From 2b60723b961c51a2b96f7c409dd84677cd36b4ff Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 6 Dec 2024 14:01:59 +0530 Subject: [PATCH 38/54] Make error modal for error codes --- .../substeps/Confirmation.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx index ba5f6a46677f..474c67536863 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx @@ -4,6 +4,7 @@ import CheckboxWithLabel from '@components/CheckboxWithLabel'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import FormHelpMessage from '@components/FormHelpMessage'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; @@ -31,9 +32,11 @@ function Confirmation({onNext, onMove, formValues, fieldsMap}: CustomSubStepProp const {translate} = useLocalize(); const styles = useThemeStyles(); const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(''); const [corpayFields] = useOnyx(ONYXKEYS.CORPAY_FIELDS); const getDataAndGoToNextStep = (values: FormOnyxValues) => { + setError(''); setIsSubmitting(true); BankAccounts.createCorpayBankAccount( {...formValues, ...values}, @@ -45,6 +48,8 @@ function Confirmation({onNext, onMove, formValues, fieldsMap}: CustomSubStepProp if (response?.jsonCode) { if (response.jsonCode === CONST.JSON_CODE.SUCCESS) { onNext(); + } else { + setError(response.message ?? ''); } } }); @@ -166,6 +171,11 @@ function Confirmation({onNext, onMove, formValues, fieldsMap}: CustomSubStepProp LabelComponent={TermsAndConditionsLabel} style={[styles.mt3]} /> + ); From fb2ff946e943d86128995ea85bfe12f6e69ea84b Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 6 Dec 2024 14:06:52 +0530 Subject: [PATCH 39/54] Lint fix --- .../substeps/AccountHolderInformation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx index 0ec69d0cb46e..f88131ecbfd9 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx @@ -5,6 +5,7 @@ import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import TextInput from '@components/TextInput'; +import TextPicker from '@components/TextPicker'; import ValuePicker from '@components/ValuePicker'; import useInternationalBankAccountFormSubmit from '@hooks/useInternationalBankAccountFormSubmit'; import useLocalize from '@hooks/useLocalize'; @@ -16,7 +17,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {CorpayFormField} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import TextPicker from '@components/TextPicker'; function getInputComponent(field: CorpayFormField) { if ((field.valueSet ?? []).length > 0) { From 979a784dff692ed7bd89abfa0d02f8b1c659fef0 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 9 Dec 2024 15:09:23 +0530 Subject: [PATCH 40/54] Fix navigation when changing country --- .../substeps/CountrySelection.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/CountrySelection.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/CountrySelection.tsx index 36d35824e304..dc16737a5dbc 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/CountrySelection.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/CountrySelection.tsx @@ -17,7 +17,7 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ROUTES from '@src/ROUTES'; -function CountrySelection({isEditing, onNext, formValues}: CustomSubStepProps) { +function CountrySelection({isEditing, onNext, formValues, resetScreenIndex}: CustomSubStepProps) { const {translate} = useLocalize(); const {isOffline} = useNetwork(); const styles = useThemeStyles(); @@ -34,8 +34,8 @@ function CountrySelection({isEditing, onNext, formValues}: CustomSubStepProps) { return; } BankAccounts.fetchCorpayFields(currentCountry, formValues.bankCurrency); - onNext(); - }, [currentCountry, formValues.bankCountry, formValues.bankCurrency, isEditing, onNext]); + resetScreenIndex?.(CONST.CORPAY_FIELDS.INDEXES.MAPPING.BANK_ACCOUNT_DETAILS); + }, [currentCountry, formValues.bankCountry, formValues.bankCurrency, isEditing, onNext, resetScreenIndex]); const onSelectionChange = useCallback((country: Option) => { setCurrentCountry(country.value); From 58c684c943b25f388230816e2393ea174a213aef Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Wed, 11 Dec 2024 12:35:10 +0530 Subject: [PATCH 41/54] Disable account holder country interactive --- src/components/TextPicker/index.tsx | 3 ++- src/components/TextPicker/types.ts | 2 +- .../substeps/AccountHolderInformation.tsx | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/TextPicker/index.tsx b/src/components/TextPicker/index.tsx index cdab78d911d2..38125f5129ed 100644 --- a/src/components/TextPicker/index.tsx +++ b/src/components/TextPicker/index.tsx @@ -8,7 +8,7 @@ import TextSelectorModal from './TextSelectorModal'; import type {TextPickerProps} from './types'; function TextPicker( - {value, description, placeholder = '', errorText = '', onInputChange, furtherDetails, rightLabel, disabled = false, ...rest}: TextPickerProps, + {value, description, placeholder = '', errorText = '', onInputChange, furtherDetails, rightLabel, disabled = false, interactive = true, ...rest}: TextPickerProps, forwardedRef: ForwardedRef, ) { const styles = useThemeStyles(); @@ -45,6 +45,7 @@ function TextPicker( brickRoadIndicator={errorText ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={errorText} style={[styles.moneyRequestMenuItem]} + interactive={interactive} /> & +} & Pick & TextProps; export type {TextSelectorModalProps, TextPickerProps}; diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx index f88131ecbfd9..2ea368c6b5b8 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx @@ -104,6 +104,7 @@ function AccountHolderInformation({isEditing, onNext, formValues, fieldsMap}: Cu description={field.id === 'accountHolderCountry' ? field.label : undefined} items={getItems(field)} disabled={field.id === 'accountHolderCountry'} + interactive={field.id === 'accountHolderCountry' ? false : undefined} renamedInputKeys={{ street: isEmptyObject(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]?.accountHolderAddress1) ? '' : 'accountHolderAddress1', street2: isEmptyObject(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]?.accountHolderAddress2) ? '' : 'accountHolderAddress2', From 7476678410331f8425c1e6c5e86fe31bc98e4ee9 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal <58412969+shubham1206agra@users.noreply.github.com> Date: Fri, 13 Dec 2024 18:29:44 +0530 Subject: [PATCH 42/54] Apply suggestions from code review --- src/ONYXKEYS.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 0d594d24d424..6091c2806389 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -454,7 +454,7 @@ const ONYXKEYS = { /** Corpay Fields while doing international bank account connection */ CORPAY_FIELDS: 'corpayFields', - + /** The user's session that will be preserved when using imported state */ PRESERVED_USER_SESSION: 'preservedUserSession', From 603f742e37bbae1a0400a5ebd1036a9e4dbaef1e Mon Sep 17 00:00:00 2001 From: Shubham Agrawal <58412969+shubham1206agra@users.noreply.github.com> Date: Wed, 18 Dec 2024 09:43:12 +0530 Subject: [PATCH 43/54] Apply suggestions from code review --- .../InternationalDepositAccount/substeps/Confirmation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx index 474c67536863..af735bc85add 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx @@ -23,7 +23,7 @@ function TermsAndConditionsLabel() { return ( {translate('common.iAcceptThe')} - {`${translate('common.addCardTermsOfService')}`} + {`${translate('common.addCardTermsOfService')}`} ); } From 91a9c52f1d10c10f89a4ff772c8064f338036b7b Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Thu, 19 Dec 2024 12:08:42 +0530 Subject: [PATCH 44/54] Use loader when submitting form --- src/components/Form/FormProvider.tsx | 9 +++++++-- src/components/Form/FormWrapper.tsx | 6 +++++- .../substeps/Confirmation.tsx | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index 2731d6bd1f98..d9dfbca277fc 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -69,6 +69,9 @@ type FormProviderProps = FormProps, @@ -189,7 +193,7 @@ function FormProvider( const submit = useDebounceNonReactive( useCallback(() => { // Return early if the form is already submitting to avoid duplicate submission - if (formState?.isLoading) { + if (!!formState?.isLoading || isLoading) { return; } @@ -210,7 +214,7 @@ function FormProvider( } KeyboardUtils.dismiss().then(() => onSubmit(trimmedStringValues)); - }, [enabledWhenOffline, formState?.isLoading, inputValues, network?.isOffline, onSubmit, onValidate, shouldTrimValues]), + }, [enabledWhenOffline, formState?.isLoading, inputValues, isLoading, network?.isOffline, onSubmit, onValidate, shouldTrimValues]), 1000, {leading: true, trailing: false}, ); @@ -406,6 +410,7 @@ function FormProvider( onSubmit={submit} inputRefs={inputRefs} errors={errors} + isLoading={isLoading} enabledWhenOffline={enabledWhenOffline} > {typeof children === 'function' ? children({inputValues}) : children} diff --git a/src/components/Form/FormWrapper.tsx b/src/components/Form/FormWrapper.tsx index 64bb2173f5b0..a7eb32d226e4 100644 --- a/src/components/Form/FormWrapper.tsx +++ b/src/components/Form/FormWrapper.tsx @@ -36,6 +36,8 @@ type FormWrapperProps = ChildrenProps & /** Callback to submit the form */ onSubmit: () => void; + + isLoading?: boolean; }; function FormWrapper({ @@ -57,6 +59,7 @@ function FormWrapper({ shouldHideFixErrorsAlert = false, disablePressOnEnter = false, isSubmitDisabled = false, + isLoading = false, }: FormWrapperProps) { const styles = useThemeStyles(); const {paddingBottom: safeAreaInsetPaddingBottom} = useStyledSafeAreaInsets(); @@ -112,7 +115,7 @@ function FormWrapper({ buttonText={submitButtonText} isDisabled={isSubmitDisabled} isAlertVisible={((!isEmptyObject(errors) || !isEmptyObject(formState?.errorFields)) && !shouldHideFixErrorsAlert) || !!errorMessage} - isLoading={!!formState?.isLoading} + isLoading={!!formState?.isLoading || isLoading} message={isEmptyObject(formState?.errorFields) ? errorMessage : undefined} onSubmit={onSubmit} footerContent={footerContent} @@ -143,6 +146,7 @@ function FormWrapper({ formState?.isLoading, shouldHideFixErrorsAlert, errorMessage, + isLoading, onSubmit, footerContent, onFixTheErrorsLinkPressed, diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx index af735bc85add..267af66d9a7d 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx @@ -161,7 +161,7 @@ function Confirmation({onNext, onMove, formValues, fieldsMap}: CustomSubStepProp submitButtonText={translate('common.confirm')} style={[styles.mh5, styles.flexGrow1]} enabledWhenOffline={false} - isSubmitDisabled={isSubmitting} + isLoading={isSubmitting} shouldHideFixErrorsAlert > Date: Thu, 19 Dec 2024 12:09:26 +0530 Subject: [PATCH 45/54] Add docstring --- src/components/Form/FormWrapper.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Form/FormWrapper.tsx b/src/components/Form/FormWrapper.tsx index a7eb32d226e4..7e3662e0d8d5 100644 --- a/src/components/Form/FormWrapper.tsx +++ b/src/components/Form/FormWrapper.tsx @@ -37,6 +37,7 @@ type FormWrapperProps = ChildrenProps & /** Callback to submit the form */ onSubmit: () => void; + /** Whether the form is loading */ isLoading?: boolean; }; From af44752a90f80b40814c8b8ba44e9ff0d1f261ad Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Thu, 19 Dec 2024 12:24:16 +0530 Subject: [PATCH 46/54] Add header to currency step --- src/components/CurrencyPicker.tsx | 7 ++++++- src/languages/en.ts | 1 + src/languages/es.ts | 1 + .../substeps/BankAccountDetails.tsx | 3 +++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/CurrencyPicker.tsx b/src/components/CurrencyPicker.tsx index 6a8a078391e2..6d2f4826fbc5 100644 --- a/src/components/CurrencyPicker.tsx +++ b/src/components/CurrencyPicker.tsx @@ -1,3 +1,4 @@ +import type {ReactNode} from 'react'; import React, {useState} from 'react'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -15,6 +16,9 @@ type CurrencyPickerProps = { /** Current value of the selected item */ value?: string; + /** Custom content to display in the header */ + headerContent?: ReactNode; + /** Callback when the list item is selected */ onInputChange?: (value: string, key?: string) => void; @@ -22,7 +26,7 @@ type CurrencyPickerProps = { errorText?: string; }; -function CurrencyPicker({value, errorText, onInputChange = () => {}}: CurrencyPickerProps) { +function CurrencyPicker({value, errorText, headerContent, onInputChange = () => {}}: CurrencyPickerProps) { const {translate} = useLocalize(); const [isPickerVisible, setIsPickerVisible] = useState(false); const styles = useThemeStyles(); @@ -66,6 +70,7 @@ function CurrencyPicker({value, errorText, onInputChange = () => {}}: CurrencyPi shouldShowBackButton onBackButtonPress={hidePickerModal} /> + {!!headerContent && headerContent} {translate('addPersonalBankAccount.currencyHeader')}; + return ( {Object.values(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.BANK_ACCOUNT_DETAILS] ?? {}).map((field) => ( From aee88f3466974b6930c261475c54a582001c27c3 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Thu, 19 Dec 2024 12:49:02 +0530 Subject: [PATCH 47/54] Style fix --- .../substeps/BankAccountDetails.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx index 9a383cf75e8c..782a4eafb091 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/BankAccountDetails.tsx @@ -49,7 +49,11 @@ function BankAccountDetails({isEditing, onNext, resetScreenIndex, formValues, fi [fieldsMap, translate], ); - const currencyHeaderContent = {translate('addPersonalBankAccount.currencyHeader')}; + const currencyHeaderContent = ( + + {translate('addPersonalBankAccount.currencyHeader')} + + ); return ( Date: Mon, 30 Dec 2024 12:47:35 +0530 Subject: [PATCH 48/54] Fix eslint --- src/pages/settings/Wallet/PaymentMethodList.tsx | 4 ++-- src/pages/settings/Wallet/WalletPage/WalletPage.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index c11624a59c8a..be0942e4716d 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -128,7 +128,7 @@ function dismissError(item: PaymentMethodItem) { const isBankAccount = item.accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT; const paymentList = isBankAccount ? ONYXKEYS.BANK_ACCOUNT_LIST : ONYXKEYS.FUND_LIST; - const paymentID = isBankAccount ? item.accountData?.bankAccountID ?? '' : item.accountData?.fundID ?? ''; + const paymentID = isBankAccount ? item.accountData?.bankAccountID : item.accountData?.fundID; if (!paymentID) { Log.info('Unable to clear payment method error: ', undefined, item); @@ -423,7 +423,7 @@ function PaymentMethodList({ shouldShowDefaultBadge( filteredPaymentMethods, item, - userWallet?.walletLinkedAccountID ?? 0, + userWallet?.walletLinkedAccountID ?? CONST.DEFAULT_NUMBER_ID, invoiceTransferBankAccountID ? invoiceTransferBankAccountID === item.methodID : item.isDefault, ) ? translate('paymentMethodList.defaultPaymentMethod') diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx index 4648c0970b15..be3aa2b5327a 100644 --- a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx +++ b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx @@ -178,7 +178,7 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) { selectedPaymentMethod: account ?? {}, selectedPaymentMethodType: accountType, formattedSelectedPaymentMethod, - methodID: methodID ?? '-1', + methodID: methodID ?? CONST.DEFAULT_NUMBER_ID, }); setShouldShowDefaultDeleteMenu(true); setMenuPosition(); @@ -233,9 +233,9 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) { const previousPaymentMethod = paymentMethods.find((method) => !!method.isDefault); const currentPaymentMethod = paymentMethods.find((method) => method.methodID === paymentMethod.methodID); if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { - PaymentMethods.makeDefaultPaymentMethod(paymentMethod.selectedPaymentMethod.bankAccountID ?? -1, 0, previousPaymentMethod, currentPaymentMethod); + PaymentMethods.makeDefaultPaymentMethod(paymentMethod.selectedPaymentMethod.bankAccountID ?? CONST.DEFAULT_NUMBER_ID, 0, previousPaymentMethod, currentPaymentMethod); } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { - PaymentMethods.makeDefaultPaymentMethod(0, paymentMethod.selectedPaymentMethod.fundID ?? -1, previousPaymentMethod, currentPaymentMethod); + PaymentMethods.makeDefaultPaymentMethod(0, paymentMethod.selectedPaymentMethod.fundID ?? CONST.DEFAULT_NUMBER_ID, previousPaymentMethod, currentPaymentMethod); } }, [ paymentMethod.methodID, From 8f5f7b8ed49928fc7db901f52bc70aaaa46ce9e9 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 30 Dec 2024 23:40:14 +0530 Subject: [PATCH 49/54] Fix eslint --- src/libs/actions/BankAccounts.ts | 4 +- .../RequestorOnfidoStep.tsx | 97 ------------------- 2 files changed, 2 insertions(+), 99 deletions(-) delete mode 100644 src/pages/ReimbursementAccount/RequestorOnfidoStep.tsx diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 94edfad959f6..4610b647aa9c 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -631,11 +631,11 @@ function connectBankAccountManually(bankAccountID: number, bankAccount: PlaidBan /** * Verify the user's identity via Onfido */ -function verifyIdentityForBankAccount(bankAccountID: number, onfidoData: OnfidoDataWithApplicantID, policyID?: string) { +function verifyIdentityForBankAccount(bankAccountID: number, onfidoData: OnfidoDataWithApplicantID, policyID: string) { const parameters: VerifyIdentityForBankAccountParams = { bankAccountID, onfidoData: JSON.stringify(onfidoData), - policyID: policyID ?? '-1', + policyID, }; API.write(WRITE_COMMANDS.VERIFY_IDENTITY_FOR_BANK_ACCOUNT, parameters, getVBBADataForOnyx()); diff --git a/src/pages/ReimbursementAccount/RequestorOnfidoStep.tsx b/src/pages/ReimbursementAccount/RequestorOnfidoStep.tsx deleted file mode 100644 index 26197abd5f6c..000000000000 --- a/src/pages/ReimbursementAccount/RequestorOnfidoStep.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; -import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import Onfido from '@components/Onfido'; -import type {OnfidoData} from '@components/Onfido/types'; -import ScreenWrapper from '@components/ScreenWrapper'; -import ScrollView from '@components/ScrollView'; -import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; -import Growl from '@libs/Growl'; -import * as BankAccounts from '@userActions/BankAccounts'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type {ReimbursementAccount} from '@src/types/onyx'; - -type RequestorOnfidoStepOnyxProps = { - /** The token required to initialize the Onfido SDK */ - onfidoToken: OnyxEntry; - - /** The application ID for our Onfido instance */ - onfidoApplicantID: OnyxEntry; -}; - -type RequestorOnfidoStepProps = RequestorOnfidoStepOnyxProps & { - /** The bank account currently in setup */ - reimbursementAccount: ReimbursementAccount; - - /** Goes to the previous step */ - onBackButtonPress: () => void; -}; - -const HEADER_STEP_COUNTER = {step: 3, total: 5}; -const ONFIDO_ERROR_DISPLAY_DURATION = 10000; - -function RequestorOnfidoStep({onBackButtonPress, reimbursementAccount, onfidoToken, onfidoApplicantID}: RequestorOnfidoStepProps) { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - - const submitOnfidoData = (onfidoData: OnfidoData) => { - BankAccounts.verifyIdentityForBankAccount(reimbursementAccount.achData?.bankAccountID ?? -1, { - ...onfidoData, - applicantID: onfidoApplicantID ?? '-1', - }); - BankAccounts.updateReimbursementAccountDraft({isOnfidoSetupComplete: true}); - }; - - const handleOnfidoError = () => { - // In case of any unexpected error we log it to the server, show a growl, and return the user back to the requestor step so they can try again. - Growl.error(translate('onfidoStep.genericError'), ONFIDO_ERROR_DISPLAY_DURATION); - BankAccounts.clearOnfidoToken(); - BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); - }; - - const handleOnfidoUserExit = () => { - BankAccounts.clearOnfidoToken(); - BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); - }; - - return ( - - - - - - - - - ); -} - -RequestorOnfidoStep.displayName = 'RequestorOnfidoStep'; - -export default withOnyx({ - onfidoToken: { - key: ONYXKEYS.ONFIDO_TOKEN, - }, - onfidoApplicantID: { - key: ONYXKEYS.ONFIDO_APPLICANT_ID, - }, -})(RequestorOnfidoStep); From 201ed20530abe2b5d712aec58646d139fc0e4fed Mon Sep 17 00:00:00 2001 From: Shubham Agrawal <58412969+shubham1206agra@users.noreply.github.com> Date: Tue, 31 Dec 2024 07:44:58 +0530 Subject: [PATCH 50/54] Apply suggestions from code review Co-authored-by: Monil Bhavsar --- src/hooks/useSubStep/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hooks/useSubStep/index.ts b/src/hooks/useSubStep/index.ts index a8518060b3b5..cc1c79d593d9 100644 --- a/src/hooks/useSubStep/index.ts +++ b/src/hooks/useSubStep/index.ts @@ -30,11 +30,11 @@ export default function useSubStep({bodyContent, on const lastScreenIndex = useMemo(() => calculateLastIndex(bodyContent.length, skipSteps), [bodyContent.length, skipSteps]); const prevScreen = useCallback(() => { - let decrementNuber = 1; - while (screenIndex - decrementNuber >= 0 && skipSteps.includes(screenIndex - decrementNuber)) { - decrementNuber += 1; + let decrementNumber = 1; + while (screenIndex - decrementNumber >= 0 && skipSteps.includes(screenIndex - decrementNumber)) { + decrementNumber += 1; } - const prevScreenIndex = screenIndex - decrementNuber; + const prevScreenIndex = screenIndex - decrementNumber; if (prevScreenIndex < 0) { return; From 0c31420717b6f56dfadc3df569af49b61ad62800 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 31 Dec 2024 22:28:34 +0530 Subject: [PATCH 51/54] Fix lint without changing any logic --- src/libs/API/parameters/index.ts | 2 -- src/libs/actions/BankAccounts.ts | 6 +++--- src/pages/ReimbursementAccount/NonUSD/BankInfo/types.ts | 6 +++--- .../InternationalDepositAccount/substeps/Confirmation.tsx | 2 +- src/types/onyx/CorpayFields.ts | 4 ++++ src/types/onyx/index.ts | 1 - 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index f2f33dc1b845..7b1f8a203ffc 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -8,7 +8,6 @@ export type {default as RestartBankAccountSetupParams} from './RestartBankAccoun export type {default as AddSchoolPrincipalParams} from './AddSchoolPrincipalParams'; export type {default as AuthenticatePusherParams} from './AuthenticatePusherParams'; export type {default as BankAccountHandlePlaidErrorParams} from './BankAccountHandlePlaidErrorParams'; -export type {default as BankAccountCreateCorpayParams} from './BankAccountCreateCorpayParams'; export type {default as BeginAppleSignInParams} from './BeginAppleSignInParams'; export type {default as BeginGoogleSignInParams} from './BeginGoogleSignInParams'; export type {default as BeginSignInParams} from './BeginSignInParams'; @@ -30,7 +29,6 @@ export type {default as ExpandURLPreviewParams} from './ExpandURLPreviewParams'; export type {default as GetMissingOnyxMessagesParams} from './GetMissingOnyxMessagesParams'; export type {default as GetNewerActionsParams} from './GetNewerActionsParams'; export type {default as GetOlderActionsParams} from './GetOlderActionsParams'; -export type {default as GetCorpayBankAccountFieldsParams} from './GetCorpayBankAccountFieldsParams'; export type {default as GetPolicyCategoriesParams} from './GetPolicyCategories'; export type {default as GetReportPrivateNoteParams} from './GetReportPrivateNoteParams'; export type {default as GetRouteParams} from './GetRouteParams'; diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index c41fbab63741..40e2b188d33f 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -680,7 +680,7 @@ function fetchCorpayFields(bankCountry: string, bankCurrency?: string, isWithdra ); } -function createCorpayBankAccount(data: InternationalBankAccountForm, classification: string, destinationCountry: string, preferredMethod: string) { +function createCorpayBankAccountForWalletFlow(data: InternationalBankAccountForm, classification: string, destinationCountry: string, preferredMethod: string) { const inputData = { ...data, classification, @@ -725,9 +725,9 @@ export { clearPersonalBankAccountSetupType, validatePlaidSelection, fetchCorpayFields, - getCorpayBankAccountFields, - createCorpayBankAccount, clearReimbursementAccountBankCreation, + getCorpayBankAccountFields, + createCorpayBankAccountForWalletFlow, }; export type {BusinessAddress, PersonalAddress}; diff --git a/src/pages/ReimbursementAccount/NonUSD/BankInfo/types.ts b/src/pages/ReimbursementAccount/NonUSD/BankInfo/types.ts index 8c84f5680e19..76432490d8d9 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BankInfo/types.ts +++ b/src/pages/ReimbursementAccount/NonUSD/BankInfo/types.ts @@ -1,6 +1,6 @@ import type {SubStepProps} from '@hooks/useSubStep/types'; -import type CorpayFormFields from '@src/types/onyx/CorpayFields'; +import type {CorpayFields} from '@src/types/onyx/CorpayFields'; -type BankInfoSubStepProps = SubStepProps & {corpayFields?: CorpayFormFields; preferredMethod?: string}; +type BankInfoSubStepProps = SubStepProps & {corpayFields?: CorpayFields; preferredMethod?: string}; -export type {BankInfoSubStepProps, CorpayFormFields}; +export type {BankInfoSubStepProps, CorpayFields}; diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx index 267af66d9a7d..c049a7545b74 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/Confirmation.tsx @@ -38,7 +38,7 @@ function Confirmation({onNext, onMove, formValues, fieldsMap}: CustomSubStepProp const getDataAndGoToNextStep = (values: FormOnyxValues) => { setError(''); setIsSubmitting(true); - BankAccounts.createCorpayBankAccount( + BankAccounts.createCorpayBankAccountForWalletFlow( {...formValues, ...values}, corpayFields?.classification ?? '', corpayFields?.destinationCountry ?? '', diff --git a/src/types/onyx/CorpayFields.ts b/src/types/onyx/CorpayFields.ts index a697abea4e88..2fa2961c41d2 100644 --- a/src/types/onyx/CorpayFields.ts +++ b/src/types/onyx/CorpayFields.ts @@ -67,6 +67,10 @@ type CorpayFields = { preferredMethod: string; /** Form fields for the Corpay form */ formFields: CorpayFormField[]; + /** Indicates if the fields are loading */ + isLoading: boolean; + /** Indicates if the fields loaded successfully */ + isSuccess: boolean; }; /** CorpayFieldsMap */ diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 8c7416e97b17..be43bd8a42ae 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -126,7 +126,6 @@ export type { CardList, CardOnWaitlist, Credentials, - CorpayFields, Currency, CurrencyList, CustomStatusDraft, From 9178cb4757f389ed0bb61825f45b9b0dd25e734d Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 31 Dec 2024 22:33:35 +0530 Subject: [PATCH 52/54] Applying suggestions --- .../substeps/AccountHolderInformation.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx index 2ea368c6b5b8..aacf04e2ebc4 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/substeps/AccountHolderInformation.tsx @@ -18,6 +18,8 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {CorpayFormField} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +const ACCOUNT_HOLDER_COUNTRY = 'accountHolderCountry'; + function getInputComponent(field: CorpayFormField) { if ((field.valueSet ?? []).length > 0) { return ValuePicker; @@ -28,7 +30,7 @@ function getInputComponent(field: CorpayFormField) { if (CONST.CORPAY_FIELDS.SPECIAL_LIST_ADDRESS_KEYS.includes(field.id)) { return AddressSearch; } - if (field.id === 'accountHolderCountry') { + if (field.id === ACCOUNT_HOLDER_COUNTRY) { return TextPicker; } return TextInput; @@ -69,7 +71,7 @@ function AccountHolderInformation({isEditing, onNext, formValues, fieldsMap}: Cu if (CONST.CORPAY_FIELDS.SPECIAL_LIST_ADDRESS_KEYS.includes(field.id)) { return [index === 0 ? styles.pb2 : styles.pv2]; } - if (field.id === 'accountHolderCountry') { + if (field.id === ACCOUNT_HOLDER_COUNTRY) { return [styles.mhn5, index === 0 ? styles.pb1 : styles.pv1]; } return [index === 0 ? styles.pb2 : styles.pv2]; @@ -101,10 +103,10 @@ function AccountHolderInformation({isEditing, onNext, formValues, fieldsMap}: Cu inputID={field.id} defaultValue={formValues[field.id]} label={field.label + (field.isRequired ? '' : ` (${translate('common.optional')})`)} - description={field.id === 'accountHolderCountry' ? field.label : undefined} + description={field.id === ACCOUNT_HOLDER_COUNTRY ? field.label : undefined} items={getItems(field)} - disabled={field.id === 'accountHolderCountry'} - interactive={field.id === 'accountHolderCountry' ? false : undefined} + disabled={field.id === ACCOUNT_HOLDER_COUNTRY} + interactive={field.id === ACCOUNT_HOLDER_COUNTRY ? false : undefined} renamedInputKeys={{ street: isEmptyObject(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]?.accountHolderAddress1) ? '' : 'accountHolderAddress1', street2: isEmptyObject(fieldsMap[CONST.CORPAY_FIELDS.STEPS_NAME.ACCOUNT_HOLDER_INFORMATION]?.accountHolderAddress2) ? '' : 'accountHolderAddress2', From 4b10219b79722fef7720939c190e206eb9908158 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 31 Dec 2024 22:39:43 +0530 Subject: [PATCH 53/54] Fixing types --- src/libs/API/parameters/VerifyIdentityForBankAccountParams.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/API/parameters/VerifyIdentityForBankAccountParams.ts b/src/libs/API/parameters/VerifyIdentityForBankAccountParams.ts index 5b7a221a8702..6ef6b3712439 100644 --- a/src/libs/API/parameters/VerifyIdentityForBankAccountParams.ts +++ b/src/libs/API/parameters/VerifyIdentityForBankAccountParams.ts @@ -1,6 +1,6 @@ type VerifyIdentityForBankAccountParams = { bankAccountID: number; onfidoData: string; - policyID?: string; + policyID: string; }; export default VerifyIdentityForBankAccountParams; From a63f170c7a9ad651955eeee9b7ed5e6ec6300771 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 31 Dec 2024 22:47:00 +0530 Subject: [PATCH 54/54] Applying suggestions --- tests/unit/useSubStepTest.tsx | 370 +++++++++++++++++----------------- 1 file changed, 189 insertions(+), 181 deletions(-) diff --git a/tests/unit/useSubStepTest.tsx b/tests/unit/useSubStepTest.tsx index 6e3da7ba2e7f..c4765cca315f 100644 --- a/tests/unit/useSubStepTest.tsx +++ b/tests/unit/useSubStepTest.tsx @@ -20,271 +20,279 @@ const mockOnFinished = jest.fn(); const mockOnFinished2 = jest.fn(); describe('useSubStep hook', () => { - it('returns componentToRender, isEditing, currentIndex, prevScreen, nextScreen, moveTo', () => { - const {result} = renderHook(() => useSubStep({bodyContent: [MockSubStepComponent], onFinished: mockOnFinished, startFrom: 0})); - - const {componentToRender, isEditing, moveTo, nextScreen, prevScreen, screenIndex} = result.current; + describe('given skipSteps as empty array', () => { + it('returns componentToRender, isEditing, currentIndex, prevScreen, nextScreen, moveTo', () => { + const {result} = renderHook(() => useSubStep({bodyContent: [MockSubStepComponent], onFinished: mockOnFinished, startFrom: 0})); + + const {componentToRender, isEditing, moveTo, nextScreen, prevScreen, screenIndex} = result.current; + + expect(componentToRender).toBe(MockSubStepComponent); + expect(isEditing).toBe(false); + expect(screenIndex).toBe(0); + expect(typeof prevScreen).toBe('function'); + expect(typeof nextScreen).toBe('function'); + expect(typeof moveTo).toBe('function'); + }); - expect(componentToRender).toBe(MockSubStepComponent); - expect(isEditing).toBe(false); - expect(screenIndex).toBe(0); - expect(typeof prevScreen).toBe('function'); - expect(typeof nextScreen).toBe('function'); - expect(typeof moveTo).toBe('function'); - }); + it('calls onFinished when it is the last step', () => { + const {result} = renderHook(() => useSubStep({bodyContent: [MockSubStepComponent], onFinished: mockOnFinished, startFrom: 0})); - it('calls onFinished when it is the last step', () => { - const {result} = renderHook(() => useSubStep({bodyContent: [MockSubStepComponent], onFinished: mockOnFinished, startFrom: 0})); + const {nextScreen} = result.current; - const {nextScreen} = result.current; + act(() => { + nextScreen(); + }); - act(() => { - nextScreen(); + expect(mockOnFinished).toHaveBeenCalledTimes(1); }); - expect(mockOnFinished).toHaveBeenCalledTimes(1); - }); + it('returns component at requested substep when calling moveTo', () => { + const {result, rerender} = renderHook(() => + useSubStep({bodyContent: [MockSubStepComponent2, MockSubStepComponent, MockSubStepComponent], onFinished: mockOnFinished, startFrom: 2}), + ); + + const {moveTo} = result.current; + + act(() => { + moveTo(0); + }); - it('returns component at requested substep when calling moveTo', () => { - const {result, rerender} = renderHook(() => useSubStep({bodyContent: [MockSubStepComponent2, MockSubStepComponent, MockSubStepComponent], onFinished: mockOnFinished, startFrom: 2})); + rerender({}); - const {moveTo} = result.current; + const {componentToRender} = result.current; - act(() => { - moveTo(0); + expect(componentToRender).toBe(MockSubStepComponent2); }); - rerender({}); + it('returns substep component at the previous index when calling prevScreen (if possible)', () => { + const {result, rerender} = renderHook(() => + useSubStep({bodyContent: [MockSubStepComponent2, MockSubStepComponent, MockSubStepComponent], onFinished: mockOnFinished, startFrom: 1}), + ); - const {componentToRender} = result.current; + const {prevScreen, screenIndex} = result.current; - expect(componentToRender).toBe(MockSubStepComponent2); - }); + expect(screenIndex).toBe(1); - it('returns substep component at the previous index when calling prevScreen (if possible)', () => { - const {result, rerender} = renderHook(() => useSubStep({bodyContent: [MockSubStepComponent2, MockSubStepComponent, MockSubStepComponent], onFinished: mockOnFinished, startFrom: 1})); + act(() => { + prevScreen(); + }); - const {prevScreen, screenIndex} = result.current; + rerender({}); - expect(screenIndex).toBe(1); + const {componentToRender, screenIndex: newScreenIndex} = result.current; + expect(newScreenIndex).toBe(0); - act(() => { - prevScreen(); + expect(componentToRender).toBe(MockSubStepComponent2); }); - rerender({}); + it('stays on the first substep component when calling prevScreen on the first screen', () => { + const {result, rerender} = renderHook(() => + useSubStep({bodyContent: [MockSubStepComponent2, MockSubStepComponent, MockSubStepComponent], onFinished: mockOnFinished, startFrom: 0}), + ); - const {componentToRender, screenIndex: newScreenIndex} = result.current; - expect(newScreenIndex).toBe(0); + const {componentToRender, prevScreen, screenIndex} = result.current; - expect(componentToRender).toBe(MockSubStepComponent2); - }); + expect(screenIndex).toBe(0); + expect(componentToRender).toBe(MockSubStepComponent2); - it('stays on the first substep component when calling prevScreen on the first screen', () => { - const {result, rerender} = renderHook(() => useSubStep({bodyContent: [MockSubStepComponent2, MockSubStepComponent, MockSubStepComponent], onFinished: mockOnFinished, startFrom: 0})); + act(() => { + prevScreen(); + }); - const {componentToRender, prevScreen, screenIndex} = result.current; + rerender({}); - expect(screenIndex).toBe(0); - expect(componentToRender).toBe(MockSubStepComponent2); + const {componentToRender: newComponentToRender, screenIndex: newScreenIndex} = result.current; - act(() => { - prevScreen(); + expect(newScreenIndex).toBe(0); + expect(newComponentToRender).toBe(MockSubStepComponent2); }); + }); - rerender({}); + describe('given skipSteps as non-empty array', () => { + it('calls onFinished when it is the second last step (last step is skipped)', () => { + const {result} = renderHook(() => useSubStep({bodyContent: [MockSubStepComponent, MockSubStepComponent2], onFinished: mockOnFinished2, startFrom: 0, skipSteps: [1]})); - const {componentToRender: newComponentToRender, screenIndex: newScreenIndex} = result.current; + const {nextScreen} = result.current; - expect(newScreenIndex).toBe(0); - expect(newComponentToRender).toBe(MockSubStepComponent2); - }); -}); + act(() => { + nextScreen(); + }); -describe('useSubStep hook with skipSteps', () => { - it('calls onFinished when it is the second last step (last step is skipped)', () => { - const {result} = renderHook(() => useSubStep({bodyContent: [MockSubStepComponent, MockSubStepComponent2], onFinished: mockOnFinished2, startFrom: 0, skipSteps: [1]})); + expect(mockOnFinished2).toHaveBeenCalledTimes(1); + }); - const {nextScreen} = result.current; + it('returns component at requested substep when calling moveTo even though the step is marked as skipped', () => { + const {result, rerender} = renderHook(() => + useSubStep({bodyContent: [MockSubStepComponent2, MockSubStepComponent3, MockSubStepComponent], onFinished: mockOnFinished, startFrom: 2, skipSteps: [1]}), + ); - act(() => { - nextScreen(); - }); + const {moveTo} = result.current; - expect(mockOnFinished2).toHaveBeenCalledTimes(1); - }); + act(() => { + moveTo(1); + }); - it('returns component at requested substep when calling moveTo even though the step is marked as skipped', () => { - const {result, rerender} = renderHook(() => - useSubStep({bodyContent: [MockSubStepComponent2, MockSubStepComponent3, MockSubStepComponent], onFinished: mockOnFinished, startFrom: 2, skipSteps: [1]}), - ); + rerender({}); - const {moveTo} = result.current; + const {componentToRender} = result.current; - act(() => { - moveTo(1); + expect(componentToRender).toBe(MockSubStepComponent3); }); - rerender({}); + it('returns substep component at the previous index when calling prevScreen (if possible)', () => { + const {result, rerender} = renderHook(() => + useSubStep({ + bodyContent: [MockSubStepComponent, MockSubStepComponent2, MockSubStepComponent3, MockSubStepComponent4], + onFinished: mockOnFinished, + startFrom: 3, + skipSteps: [0, 2], + }), + ); - const {componentToRender} = result.current; + const {prevScreen, screenIndex} = result.current; - expect(componentToRender).toBe(MockSubStepComponent3); - }); + expect(screenIndex).toBe(3); - it('returns substep component at the previous index when calling prevScreen (if possible)', () => { - const {result, rerender} = renderHook(() => - useSubStep({ - bodyContent: [MockSubStepComponent, MockSubStepComponent2, MockSubStepComponent3, MockSubStepComponent4], - onFinished: mockOnFinished, - startFrom: 3, - skipSteps: [0, 2], - }), - ); + act(() => { + prevScreen(); + }); - const {prevScreen, screenIndex} = result.current; + rerender({}); - expect(screenIndex).toBe(3); + const {componentToRender, screenIndex: newScreenIndex} = result.current; + expect(newScreenIndex).toBe(1); - act(() => { - prevScreen(); + expect(componentToRender).toBe(MockSubStepComponent2); }); - rerender({}); + it('stays on the first substep component when calling prevScreen on the second screen if the first screen is skipped', () => { + const {result, rerender} = renderHook(() => + useSubStep({bodyContent: [MockSubStepComponent, MockSubStepComponent2, MockSubStepComponent3], onFinished: mockOnFinished, startFrom: 1, skipSteps: [0]}), + ); - const {componentToRender, screenIndex: newScreenIndex} = result.current; - expect(newScreenIndex).toBe(1); + const {componentToRender, prevScreen, screenIndex} = result.current; - expect(componentToRender).toBe(MockSubStepComponent2); - }); + expect(screenIndex).toBe(1); + expect(componentToRender).toBe(MockSubStepComponent2); - it('stays on the first substep component when calling prevScreen on the second screen if the first screen is skipped', () => { - const {result, rerender} = renderHook(() => - useSubStep({bodyContent: [MockSubStepComponent, MockSubStepComponent2, MockSubStepComponent3], onFinished: mockOnFinished, startFrom: 1, skipSteps: [0]}), - ); + act(() => { + prevScreen(); + }); - const {componentToRender, prevScreen, screenIndex} = result.current; + rerender({}); - expect(screenIndex).toBe(1); - expect(componentToRender).toBe(MockSubStepComponent2); + const {componentToRender: newComponentToRender, screenIndex: newScreenIndex} = result.current; - act(() => { - prevScreen(); + expect(newScreenIndex).toBe(1); + expect(newComponentToRender).toBe(MockSubStepComponent2); }); - rerender({}); + it('skips step which are marked as skipped when using nextScreen', () => { + const {result, rerender} = renderHook(() => + useSubStep({ + bodyContent: [MockSubStepComponent, MockSubStepComponent2, MockSubStepComponent3, MockSubStepComponent4], + onFinished: mockOnFinished, + startFrom: 0, + skipSteps: [1, 2], + }), + ); - const {componentToRender: newComponentToRender, screenIndex: newScreenIndex} = result.current; + const {componentToRender, nextScreen, screenIndex} = result.current; - expect(newScreenIndex).toBe(1); - expect(newComponentToRender).toBe(MockSubStepComponent2); - }); + expect(screenIndex).toBe(0); + expect(componentToRender).toBe(MockSubStepComponent); - it('skips step which are marked as skipped when using nextScreen', () => { - const {result, rerender} = renderHook(() => - useSubStep({ - bodyContent: [MockSubStepComponent, MockSubStepComponent2, MockSubStepComponent3, MockSubStepComponent4], - onFinished: mockOnFinished, - startFrom: 0, - skipSteps: [1, 2], - }), - ); + act(() => { + nextScreen(); + }); - const {componentToRender, nextScreen, screenIndex} = result.current; + rerender({}); - expect(screenIndex).toBe(0); - expect(componentToRender).toBe(MockSubStepComponent); + const {componentToRender: newComponentToRender, screenIndex: newScreenIndex} = result.current; - act(() => { - nextScreen(); + expect(newScreenIndex).toBe(3); + expect(newComponentToRender).toBe(MockSubStepComponent4); }); - rerender({}); + it('nextScreen works correctly when called from skipped screen', () => { + const {result, rerender} = renderHook(() => + useSubStep({ + bodyContent: [MockSubStepComponent, MockSubStepComponent2, MockSubStepComponent3, MockSubStepComponent4], + onFinished: mockOnFinished, + startFrom: 1, + skipSteps: [1, 2], + }), + ); - const {componentToRender: newComponentToRender, screenIndex: newScreenIndex} = result.current; + const {componentToRender, nextScreen, screenIndex} = result.current; - expect(newScreenIndex).toBe(3); - expect(newComponentToRender).toBe(MockSubStepComponent4); - }); + expect(screenIndex).toBe(1); + expect(componentToRender).toBe(MockSubStepComponent2); - it('nextScreen works correctly when called from skipped screen', () => { - const {result, rerender} = renderHook(() => - useSubStep({ - bodyContent: [MockSubStepComponent, MockSubStepComponent2, MockSubStepComponent3, MockSubStepComponent4], - onFinished: mockOnFinished, - startFrom: 1, - skipSteps: [1, 2], - }), - ); + act(() => { + nextScreen(); + }); - const {componentToRender, nextScreen, screenIndex} = result.current; + rerender({}); - expect(screenIndex).toBe(1); - expect(componentToRender).toBe(MockSubStepComponent2); + const {componentToRender: newComponentToRender, screenIndex: newScreenIndex} = result.current; - act(() => { - nextScreen(); + expect(newScreenIndex).toBe(3); + expect(newComponentToRender).toBe(MockSubStepComponent4); }); - rerender({}); + it('skips step which are marked as skipped when using prevScreen', () => { + const {result, rerender} = renderHook(() => + useSubStep({ + bodyContent: [MockSubStepComponent, MockSubStepComponent2, MockSubStepComponent3, MockSubStepComponent4], + onFinished: mockOnFinished, + startFrom: 3, + skipSteps: [1, 2], + }), + ); - const {componentToRender: newComponentToRender, screenIndex: newScreenIndex} = result.current; + const {componentToRender, prevScreen, screenIndex} = result.current; - expect(newScreenIndex).toBe(3); - expect(newComponentToRender).toBe(MockSubStepComponent4); - }); + expect(screenIndex).toBe(3); + expect(componentToRender).toBe(MockSubStepComponent4); - it('skips step which are marked as skipped when using prevScreen', () => { - const {result, rerender} = renderHook(() => - useSubStep({ - bodyContent: [MockSubStepComponent, MockSubStepComponent2, MockSubStepComponent3, MockSubStepComponent4], - onFinished: mockOnFinished, - startFrom: 3, - skipSteps: [1, 2], - }), - ); + act(() => { + prevScreen(); + }); - const {componentToRender, prevScreen, screenIndex} = result.current; + rerender({}); - expect(screenIndex).toBe(3); - expect(componentToRender).toBe(MockSubStepComponent4); + const {componentToRender: newComponentToRender, screenIndex: newScreenIndex} = result.current; - act(() => { - prevScreen(); + expect(newScreenIndex).toBe(0); + expect(newComponentToRender).toBe(MockSubStepComponent); }); - rerender({}); + it('prevScreen works correctly when called from skipped screen', () => { + const {result, rerender} = renderHook(() => + useSubStep({ + bodyContent: [MockSubStepComponent, MockSubStepComponent2, MockSubStepComponent3, MockSubStepComponent4], + onFinished: mockOnFinished, + startFrom: 2, + skipSteps: [1, 2], + }), + ); - const {componentToRender: newComponentToRender, screenIndex: newScreenIndex} = result.current; + const {componentToRender, prevScreen, screenIndex} = result.current; - expect(newScreenIndex).toBe(0); - expect(newComponentToRender).toBe(MockSubStepComponent); - }); + expect(screenIndex).toBe(2); + expect(componentToRender).toBe(MockSubStepComponent3); - it('prevScreen works correctly when called from skipped screen', () => { - const {result, rerender} = renderHook(() => - useSubStep({ - bodyContent: [MockSubStepComponent, MockSubStepComponent2, MockSubStepComponent3, MockSubStepComponent4], - onFinished: mockOnFinished, - startFrom: 2, - skipSteps: [1, 2], - }), - ); + act(() => { + prevScreen(); + }); - const {componentToRender, prevScreen, screenIndex} = result.current; + rerender({}); - expect(screenIndex).toBe(2); - expect(componentToRender).toBe(MockSubStepComponent3); + const {componentToRender: newComponentToRender, screenIndex: newScreenIndex} = result.current; - act(() => { - prevScreen(); + expect(newScreenIndex).toBe(0); + expect(newComponentToRender).toBe(MockSubStepComponent); }); - - rerender({}); - - const {componentToRender: newComponentToRender, screenIndex: newScreenIndex} = result.current; - - expect(newScreenIndex).toBe(0); - expect(newComponentToRender).toBe(MockSubStepComponent); }); });