From e058da375a61a96746c6ddc672e9c26ba1f20b3f Mon Sep 17 00:00:00 2001 From: burczu Date: Wed, 25 Sep 2024 10:26:34 +0200 Subject: [PATCH 01/52] interactive step wrapper extended to match all use cases --- src/components/InteractiveStepWrapper.tsx | 46 ++++++++++++++++++++--- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/src/components/InteractiveStepWrapper.tsx b/src/components/InteractiveStepWrapper.tsx index 6ffe00b9bd5d2..0ed2f89f28856 100644 --- a/src/components/InteractiveStepWrapper.tsx +++ b/src/components/InteractiveStepWrapper.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import {View} from 'react-native'; +import React, {forwardRef} from 'react'; +import {StyleProp, View, ViewStyle} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import HeaderWithBackButton from './HeaderWithBackButton'; @@ -24,21 +24,55 @@ type InteractiveStepWrapperProps = { // Array of step names stepNames?: readonly string[]; + + // Should enable max height + shouldEnableMaxHeight?: boolean; + + // Should show offline indicator + shouldShowOfflineIndicator?: boolean; + + // Should enable picker avoiding + shouldEnablePickerAvoiding?: boolean; + + // Call task ID for the guides + guidesCallTaskID?: string; + + // Offline indicator style + offlineIndicatorStyle?: StyleProp; }; -function InteractiveStepWrapper({children, wrapperID, handleBackButtonPress, headerTitle, startStepIndex, stepNames}: InteractiveStepWrapperProps) { +function InteractiveStepWrapper( + { + children, + wrapperID, + handleBackButtonPress, + headerTitle, + startStepIndex, + stepNames, + shouldEnableMaxHeight, + shouldShowOfflineIndicator, + shouldEnablePickerAvoiding = false, + guidesCallTaskID, + offlineIndicatorStyle, + }: InteractiveStepWrapperProps, + ref: React.ForwardedRef, +) { const styles = useThemeStyles(); return ( {stepNames && ( @@ -55,4 +89,4 @@ function InteractiveStepWrapper({children, wrapperID, handleBackButtonPress, hea InteractiveStepWrapper.displayName = 'InteractiveStepWrapper'; -export default InteractiveStepWrapper; +export default forwardRef(InteractiveStepWrapper); From 5ed587c28c547f564240583763efbd392a03431c Mon Sep 17 00:00:00 2001 From: burczu Date: Wed, 25 Sep 2024 14:29:06 +0200 Subject: [PATCH 02/52] refactor: using InteractiveStepWrapper wherever applies --- .../FeesAndTerms/FeesAndTerms.tsx | 28 +++++------------ .../PersonalInfo/PersonalInfo.tsx | 28 +++++------------ .../BankInfo/BankInfo.tsx | 29 +++++------------- .../BeneficialOwnersStep.tsx | 23 +++++--------- .../BusinessInfo/BusinessInfo.tsx | 30 ++++++------------- .../CompleteVerification.tsx | 26 +++++----------- .../PersonalInfo/PersonalInfo.tsx | 27 ++++++----------- .../VerifyIdentity/VerifyIdentity.tsx | 25 ++++++---------- .../expensifyCard/issueNew/AssigneeStep.tsx | 26 +++++----------- .../expensifyCard/issueNew/CardNameStep.tsx | 26 +++++----------- .../expensifyCard/issueNew/CardTypeStep.tsx | 22 +++++--------- .../issueNew/ConfirmationStep.tsx | 25 +++++----------- .../expensifyCard/issueNew/LimitStep.tsx | 26 +++++----------- .../expensifyCard/issueNew/LimitTypeStep.tsx | 26 +++++----------- 14 files changed, 115 insertions(+), 252 deletions(-) diff --git a/src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx b/src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx index 512abc57a990f..e8db5d75cfd0b 100644 --- a/src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx +++ b/src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx @@ -1,13 +1,9 @@ import React from 'react'; -import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; -import ScreenWrapper from '@components/ScreenWrapper'; +import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import useLocalize from '@hooks/useLocalize'; import useSubStep from '@hooks/useSubStep'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@navigation/Navigation'; import * as BankAccounts from '@userActions/BankAccounts'; import * as Wallet from '@userActions/Wallet'; @@ -21,7 +17,6 @@ const termsAndFeesSubsteps: Array> = [FeesStep function FeesAndTerms() { const {translate} = useLocalize(); - const styles = useThemeStyles(); const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS); const submit = () => { @@ -44,28 +39,21 @@ function FeesAndTerms() { }; return ( - - - - - - + ); } diff --git a/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx b/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx index 55d369b4a2c5f..2a91766b52038 100644 --- a/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx +++ b/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx @@ -1,14 +1,10 @@ import React, {useMemo} from 'react'; -import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; -import ScreenWrapper from '@components/ScreenWrapper'; +import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import useLocalize from '@hooks/useLocalize'; import useSubStep from '@hooks/useSubStep'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; import {parsePhoneNumber} from '@libs/PhoneNumber'; import IdologyQuestions from '@pages/EnablePayments/IdologyQuestions'; import getInitialSubstepForPersonalInfo from '@pages/EnablePayments/utils/getInitialSubstepForPersonalInfo'; @@ -41,7 +37,6 @@ const bodyContent: Array> = [FullName, DateOfB function PersonalInfoPage({walletAdditionalDetails, walletAdditionalDetailsDraft}: PersonalInfoPageProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); const showIdologyQuestions = walletAdditionalDetails?.questions && walletAdditionalDetails?.questions.length > 0; const values = useMemo(() => getSubstepValues(PERSONAL_INFO_STEP_KEYS, walletAdditionalDetailsDraft, walletAdditionalDetails), [walletAdditionalDetails, walletAdditionalDetailsDraft]); @@ -94,20 +89,13 @@ function PersonalInfoPage({walletAdditionalDetails, walletAdditionalDetailsDraft }; return ( - - - - - {showIdologyQuestions ? ( )} - + ); } diff --git a/src/pages/ReimbursementAccount/BankInfo/BankInfo.tsx b/src/pages/ReimbursementAccount/BankInfo/BankInfo.tsx index 888ad24ba2bef..8bb02fdda4d02 100644 --- a/src/pages/ReimbursementAccount/BankInfo/BankInfo.tsx +++ b/src/pages/ReimbursementAccount/BankInfo/BankInfo.tsx @@ -1,13 +1,9 @@ import React, {useCallback, useEffect, useMemo} from 'react'; -import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; -import ScreenWrapper from '@components/ScreenWrapper'; +import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import useLocalize from '@hooks/useLocalize'; import useSubStep from '@hooks/useSubStep'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; import getPlaidOAuthReceivedRedirectURI from '@libs/getPlaidOAuthReceivedRedirectURI'; import getSubstepValues from '@pages/ReimbursementAccount/utils/getSubstepValues'; import * as BankAccounts from '@userActions/BankAccounts'; @@ -37,7 +33,6 @@ function BankInfo({onBackButtonPress, policyID}: BankInfoProps) { const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT); const [plaidLinkToken] = useOnyx(ONYXKEYS.PLAID_LINK_TOKEN); const {translate} = useLocalize(); - const styles = useThemeStyles(); const [redirectedFromPlaidToManual, setRedirectedFromPlaidToManual] = React.useState(false); const values = useMemo(() => getSubstepValues(BANK_INFO_STEP_KEYS, reimbursementAccountDraft, reimbursementAccount ?? {}), [reimbursementAccount, reimbursementAccountDraft]); @@ -125,28 +120,20 @@ function BankInfo({onBackButtonPress, policyID}: BankInfoProps) { }; return ( - - - - - - + ); } diff --git a/src/pages/ReimbursementAccount/BeneficialOwnersStep.tsx b/src/pages/ReimbursementAccount/BeneficialOwnersStep.tsx index ae0fded74347d..0e67a5f84c104 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnersStep.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnersStep.tsx @@ -5,6 +5,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; +import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useSubStep from '@hooks/useSubStep'; @@ -216,23 +217,15 @@ function BeneficialOwnersStep({reimbursementAccount, reimbursementAccountDraft, }; return ( - - - - - - {currentUBOSubstep === SUBSTEP.IS_USER_UBO && ( )} - + ); } diff --git a/src/pages/ReimbursementAccount/BusinessInfo/BusinessInfo.tsx b/src/pages/ReimbursementAccount/BusinessInfo/BusinessInfo.tsx index 814db3536973a..835ac77ee4410 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/BusinessInfo.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/BusinessInfo.tsx @@ -1,15 +1,11 @@ import lodashPick from 'lodash/pick'; import React, {useCallback, useMemo} from 'react'; -import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; -import ScreenWrapper from '@components/ScreenWrapper'; +import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import useLocalize from '@hooks/useLocalize'; import useSubStep from '@hooks/useSubStep'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; import {parsePhoneNumber} from '@libs/PhoneNumber'; import getInitialSubstepForBusinessInfo from '@pages/ReimbursementAccount/utils/getInitialSubstepForBusinessInfo'; import getSubstepValues from '@pages/ReimbursementAccount/utils/getSubstepValues'; @@ -58,7 +54,6 @@ const bodyContent: Array> = [ function BusinessInfo({reimbursementAccount, reimbursementAccountDraft, onBackButtonPress}: BusinessInfoProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); const getBankAccountFields = useCallback( (fieldNames: string[]) => ({ @@ -114,29 +109,22 @@ function BusinessInfo({reimbursementAccount, reimbursementAccountDraft, onBackBu }; return ( - - - - - - + ); } diff --git a/src/pages/ReimbursementAccount/CompleteVerification/CompleteVerification.tsx b/src/pages/ReimbursementAccount/CompleteVerification/CompleteVerification.tsx index 900ca7207f596..a258f69d5eb98 100644 --- a/src/pages/ReimbursementAccount/CompleteVerification/CompleteVerification.tsx +++ b/src/pages/ReimbursementAccount/CompleteVerification/CompleteVerification.tsx @@ -1,11 +1,8 @@ import type {ComponentType} from 'react'; import React, {useCallback, useMemo} from 'react'; -import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; -import ScreenWrapper from '@components/ScreenWrapper'; +import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import useLocalize from '@hooks/useLocalize'; import useSubStep from '@hooks/useSubStep'; import type {SubStepProps} from '@hooks/useSubStep/types'; @@ -70,28 +67,21 @@ function CompleteVerification({reimbursementAccount, reimbursementAccountDraft, }; return ( - - - - - - + ); } diff --git a/src/pages/ReimbursementAccount/PersonalInfo/PersonalInfo.tsx b/src/pages/ReimbursementAccount/PersonalInfo/PersonalInfo.tsx index 1aa7e519416e7..f955572da738c 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/PersonalInfo.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/PersonalInfo.tsx @@ -1,11 +1,9 @@ import type {RefAttributes} from 'react'; import React, {forwardRef, useCallback, useMemo} from 'react'; -import {View} from 'react-native'; +import type {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; -import ScreenWrapper from '@components/ScreenWrapper'; +import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import useLocalize from '@hooks/useLocalize'; import useSubStep from '@hooks/useSubStep'; import type {SubStepProps} from '@hooks/useSubStep/types'; @@ -79,29 +77,22 @@ function PersonalInfo({reimbursementAccount, reimbursementAccountDraft, onBackBu }; return ( - - - - - - + ); } diff --git a/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx b/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx index cabaf543a7564..81ff3599628e8 100644 --- a/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx +++ b/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx @@ -1,13 +1,10 @@ import React, {useCallback} from 'react'; -import {View} from 'react-native'; 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 InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; +import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; 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'; @@ -61,17 +58,13 @@ function VerifyIdentity({reimbursementAccount, onBackButtonPress, onfidoApplican }; return ( - - - - - + - + ); } diff --git a/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx b/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx index e89db5cb88db7..769532e49351e 100644 --- a/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx @@ -1,11 +1,8 @@ import React, {useMemo} from 'react'; -import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; -import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; -import ScreenWrapper from '@components/ScreenWrapper'; +import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import SelectionList from '@components/SelectionList'; import type {ListItem} from '@components/SelectionList/types'; import UserListItem from '@components/SelectionList/UserListItem'; @@ -134,22 +131,15 @@ function AssigneeStep({policy}: AssigneeStepProps) { }, [debouncedSearchTerm, sections]); return ( - - - - - {translate('workspace.card.issueNewCard.whoNeedsCard')} - + ); } diff --git a/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx b/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx index b75a1f417c789..26ae497406d6c 100644 --- a/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx @@ -1,12 +1,9 @@ import React, {useCallback} from 'react'; -import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; -import ScreenWrapper from '@components/ScreenWrapper'; +import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; @@ -61,22 +58,15 @@ function CardNameStep() { }, [isEditing]); return ( - - - - - {translate('workspace.card.issueNewCard.giveItName')} - + ); } diff --git a/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx b/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx index fd1fb5a7586fe..acb1da3a8ec23 100644 --- a/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx @@ -5,6 +5,7 @@ import type {ValueOf} from 'type-fest'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Illustrations from '@components/Icon/Illustrations'; import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; +import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import MenuItem from '@components/MenuItem'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; @@ -41,23 +42,16 @@ function CardTypeStep() { }; return ( - - - - - {translate('workspace.card.issueNewCard.chooseCardType')} - + ); } diff --git a/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx b/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx index c5a8524508284..e2f18836c73b3 100644 --- a/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx @@ -2,10 +2,8 @@ import React, {useEffect, useRef} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; +import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; -import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; @@ -62,22 +60,15 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) { const translationForLimitType = getTranslationKeyForLimitType(data?.limitType); return ( - - - - - - + ); } diff --git a/src/pages/workspace/expensifyCard/issueNew/LimitStep.tsx b/src/pages/workspace/expensifyCard/issueNew/LimitStep.tsx index eb7c2e7d8e0fd..70776be526fa7 100644 --- a/src/pages/workspace/expensifyCard/issueNew/LimitStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/LimitStep.tsx @@ -1,13 +1,10 @@ import React, {useCallback} from 'react'; -import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import AmountForm from '@components/AmountForm'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; -import ScreenWrapper from '@components/ScreenWrapper'; +import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import Text from '@components/Text'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; @@ -60,22 +57,15 @@ function LimitStep() { ); return ( - - - - - {translate('workspace.card.issueNewCard.setLimit')} - + ); } diff --git a/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx b/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx index de4bca070d51a..110f39460c82a 100644 --- a/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx @@ -1,11 +1,8 @@ import React, {useCallback, useMemo, useState} from 'react'; -import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; -import ScreenWrapper from '@components/ScreenWrapper'; +import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; import Text from '@components/Text'; @@ -84,22 +81,15 @@ function LimitTypeStep({policy}: LimitTypeStepProps) { }, [areApprovalsConfigured, translate, typeSelected]); return ( - - - - - {translate('workspace.card.issueNewCard.chooseLimitType')} - + ); } From ee229c84fe26e20e1c735e8c211d159e26aeaad1 Mon Sep 17 00:00:00 2001 From: burczu Date: Wed, 2 Oct 2024 09:07:20 +0200 Subject: [PATCH 03/52] missing personal details refactor: using separate form providers --- src/hooks/usePersonalDetailsStepFormSubmit.ts | 27 ++++++++ src/pages/MissingPersonalDetails/index.tsx | 63 +++++++++-------- .../substeps/Address.tsx | 67 ++++++++++++++++++- .../substeps/DateOfBirth.tsx | 42 ++++++++++-- .../substeps/LegalName.tsx | 46 +++++++++++-- .../substeps/PhoneNumber.tsx | 48 +++++++++++-- 6 files changed, 251 insertions(+), 42 deletions(-) create mode 100644 src/hooks/usePersonalDetailsStepFormSubmit.ts diff --git a/src/hooks/usePersonalDetailsStepFormSubmit.ts b/src/hooks/usePersonalDetailsStepFormSubmit.ts new file mode 100644 index 0000000000000..32dc1371049b6 --- /dev/null +++ b/src/hooks/usePersonalDetailsStepFormSubmit.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 UsePersonalDetailsStepFormSubmitParams = Pick & { + formId?: OnyxFormKey; + fieldIds: Array>; + shouldSaveDraft: boolean; +}; + +/** + * Hook for handling submit method in MissingPersonalDetails 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 usePersonalDetailsStepFormSubmit({onNext, fieldIds, shouldSaveDraft}: UsePersonalDetailsStepFormSubmitParams) { + return useStepFormSubmit({ + formId: ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM, + onNext, + fieldIds, + shouldSaveDraft, + }); +} diff --git a/src/pages/MissingPersonalDetails/index.tsx b/src/pages/MissingPersonalDetails/index.tsx index 5220f25be981f..2ad9d772b2ca1 100644 --- a/src/pages/MissingPersonalDetails/index.tsx +++ b/src/pages/MissingPersonalDetails/index.tsx @@ -68,14 +68,14 @@ function MissingPersonalDetails() { prevScreen(); }; - const handleNextScreen = useCallback(() => { - if (isEditing) { - goToTheLastStep(); - return; - } - ref.current?.moveNext(); - nextScreen(); - }, [goToTheLastStep, isEditing, nextScreen]); + // const handleNextScreen = useCallback(() => { + // if (isEditing) { + // goToTheLastStep(); + // return; + // } + // ref.current?.moveNext(); + // nextScreen(); + // }, [goToTheLastStep, isEditing, nextScreen]); const validate = useCallback( (values: FormOnyxValues): FormInputErrors => { @@ -186,25 +186,34 @@ function MissingPersonalDetails() { stepNames={CONST.MISSING_PERSONAL_DETAILS_INDEXES.INDEX_LIST} /> - - - - - + + + + {/**/} + {/* */} + {/* */} + {/* */} + {/**/} ); } diff --git a/src/pages/MissingPersonalDetails/substeps/Address.tsx b/src/pages/MissingPersonalDetails/substeps/Address.tsx index 384a2648b307c..153027db6fa1b 100644 --- a/src/pages/MissingPersonalDetails/substeps/Address.tsx +++ b/src/pages/MissingPersonalDetails/substeps/Address.tsx @@ -2,21 +2,28 @@ import React, {useCallback, useEffect, useMemo, useState} 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 StatePicker from '@components/StatePicker'; import type {State} from '@components/StateSelector'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; +import usePersonalDetailsStepFormSubmit from '@hooks/usePersonalDetailsStepFormSubmit'; import useThemeStyles from '@hooks/useThemeStyles'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; +import * as ValidationUtils from '@libs/ValidationUtils'; import type {CountryZipRegex, CustomSubStepProps} from '@pages/MissingPersonalDetails/types'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/PersonalDetailsForm'; import type {Address} from '@src/types/onyx/PrivatePersonalDetails'; -function AddressStep({privatePersonalDetails}: CustomSubStepProps) { +const STEP_FIELDS = [INPUT_IDS.ADDRESS_LINE_1, INPUT_IDS.ADDRESS_LINE_2, INPUT_IDS.CITY, INPUT_IDS.STATE, INPUT_IDS.COUNTRY, INPUT_IDS.ZIP_POST_CODE]; + +function AddressStep({privatePersonalDetails, isEditing, onNext}: CustomSubStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const address = useMemo(() => PersonalDetailsUtils.getCurrentAddress(privatePersonalDetails), [privatePersonalDetails]); @@ -39,6 +46,40 @@ function AddressStep({privatePersonalDetails}: CustomSubStepProps) { // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [address?.state, address?.country, address?.city, address?.zip]); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + const addressRequiredFields = [INPUT_IDS.ADDRESS_LINE_1, INPUT_IDS.CITY, INPUT_IDS.COUNTRY, INPUT_IDS.STATE] as const; + addressRequiredFields.forEach((fieldKey) => { + const fieldValue = values[fieldKey] ?? ''; + if (ValidationUtils.isRequiredFulfilled(fieldValue)) { + return; + } + errors[fieldKey] = translate('common.error.fieldRequired'); + }); + + // If no country is selected, default value is an empty string and there's no related regex data so we default to an empty object + const countryRegexDetails = (values.country ? CONST.COUNTRY_ZIP_REGEX_DATA?.[values.country] : {}) as CountryZipRegex; + + // The postal code system might not exist for a country, so no regex either for them. + const countrySpecificZipRegex = countryRegexDetails?.regex; + const countryZipFormat = countryRegexDetails?.samples ?? ''; + if (countrySpecificZipRegex) { + if (!countrySpecificZipRegex.test(values[INPUT_IDS.ZIP_POST_CODE]?.trim().toUpperCase())) { + if (ValidationUtils.isRequiredFulfilled(values[INPUT_IDS.ZIP_POST_CODE]?.trim())) { + errors[INPUT_IDS.ZIP_POST_CODE] = translate('privatePersonalDetails.error.incorrectZipFormat', {zipFormat: countryZipFormat}); + } else { + errors[INPUT_IDS.ZIP_POST_CODE] = translate('common.error.fieldRequired'); + } + } + } else if (!CONST.GENERIC_ZIP_CODE_REGEX.test(values[INPUT_IDS.ZIP_POST_CODE]?.trim()?.toUpperCase() ?? '')) { + errors[INPUT_IDS.ZIP_POST_CODE] = translate('privatePersonalDetails.error.incorrectZipFormat'); + } + return errors; + }, + [translate], + ); + const handleAddressChange = useCallback((value: unknown, key: unknown) => { const addressPart = value as string; const addressPartKey = key as keyof Address; @@ -67,6 +108,12 @@ function AddressStep({privatePersonalDetails}: CustomSubStepProps) { setZipcode(addressPart); }, []); + const handleSubmit = usePersonalDetailsStepFormSubmit({ + fieldIds: STEP_FIELDS, + onNext, + shouldSaveDraft: isEditing, + }); + const isUSAForm = currentCountry === CONST.COUNTRY.US; const zipSampleFormat = (currentCountry && (CONST.COUNTRY_ZIP_REGEX_DATA[currentCountry] as CountryZipRegex)?.samples) ?? ''; @@ -74,7 +121,14 @@ function AddressStep({privatePersonalDetails}: CustomSubStepProps) { const zipFormat = translate('common.zipCodeExampleFormat', {zipSampleFormat}); return ( - <> + {translate('privatePersonalDetails.enterAddress')} {isUSAForm ? ( @@ -123,6 +180,7 @@ function AddressStep({privatePersonalDetails}: CustomSubStepProps) { inputID={INPUT_IDS.STATE} value={state as State} onValueChange={handleAddressChange} + shouldSaveDraft={!isEditing} /> ) : ( @@ -137,6 +195,7 @@ function AddressStep({privatePersonalDetails}: CustomSubStepProps) { spellCheck={false} onValueChange={handleAddressChange} containerStyles={styles.mt3} + shouldSaveDraft={!isEditing} /> )} - + ); } diff --git a/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx b/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx index 5aba7ed74ef87..e5850240d0717 100644 --- a/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx +++ b/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx @@ -1,23 +1,56 @@ import {subYears} from 'date-fns'; -import React from 'react'; +import React, {useCallback} from 'react'; import DatePicker from '@components/DatePicker'; +import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; +import usePersonalDetailsStepFormSubmit from '@hooks/usePersonalDetailsStepFormSubmit'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as ValidationUtils from '@libs/ValidationUtils'; import type {CustomSubStepProps} from '@pages/MissingPersonalDetails/types'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/PersonalDetailsForm'; -function DateOfBirthStep({privatePersonalDetails}: CustomSubStepProps) { +const STEP_FIELDS = [INPUT_IDS.DATE_OF_BIRTH]; + +function DateOfBirthStep({privatePersonalDetails, isEditing, onNext}: CustomSubStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const minDate = subYears(new Date(), CONST.DATE_BIRTH.MAX_AGE); const maxDate = subYears(new Date(), CONST.DATE_BIRTH.MIN_AGE); + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + if (!ValidationUtils.isRequiredFulfilled(values[INPUT_IDS.DATE_OF_BIRTH])) { + errors[INPUT_IDS.DATE_OF_BIRTH] = translate('common.error.fieldRequired'); + } else if (!ValidationUtils.isValidPastDate(values[INPUT_IDS.DATE_OF_BIRTH]) || !ValidationUtils.meetsMaximumAgeRequirement(values[INPUT_IDS.DATE_OF_BIRTH])) { + errors[INPUT_IDS.DATE_OF_BIRTH] = translate('bankAccount.error.dob'); + } + return errors; + }, + [translate], + ); + + const handleSubmit = usePersonalDetailsStepFormSubmit({ + fieldIds: STEP_FIELDS, + onNext, + shouldSaveDraft: isEditing, + }); + return ( - <> + {translate('privatePersonalDetails.enterDateOfBirth')} - + ); } diff --git a/src/pages/MissingPersonalDetails/substeps/LegalName.tsx b/src/pages/MissingPersonalDetails/substeps/LegalName.tsx index 8b88bee4337d1..6b41791f36959 100644 --- a/src/pages/MissingPersonalDetails/substeps/LegalName.tsx +++ b/src/pages/MissingPersonalDetails/substeps/LegalName.tsx @@ -1,20 +1,56 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; +import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; +import usePersonalDetailsStepFormSubmit from '@hooks/usePersonalDetailsStepFormSubmit'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as ValidationUtils from '@libs/ValidationUtils'; import type {CustomSubStepProps} from '@pages/MissingPersonalDetails/types'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/PersonalDetailsForm'; -function LegalNameStep({privatePersonalDetails}: CustomSubStepProps) { +const STEP_FIELDS = [INPUT_IDS.LEGAL_FIRST_NAME, INPUT_IDS.LEGAL_LAST_NAME]; + +function LegalNameStep({privatePersonalDetails, isEditing, onNext}: CustomSubStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); + // TODO: apply validation from index file + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + if (values.legalFirstName && !ValidationUtils.isValidPersonName(values.legalLastName)) { + errors.legalFirstName = translate('bankAccount.error.firstName'); + } + + if (values.legalLastName && !ValidationUtils.isValidPersonName(values.legalLastName)) { + errors.legalLastName = translate('bankAccount.error.lastName'); + } + return errors; + }, + [translate], + ); + + const handleSubmit = usePersonalDetailsStepFormSubmit({ + fieldIds: STEP_FIELDS, + onNext, + shouldSaveDraft: isEditing, + }); + return ( - <> + {translate('privatePersonalDetails.enterLegalName')} @@ -38,9 +75,10 @@ function LegalNameStep({privatePersonalDetails}: CustomSubStepProps) { role={CONST.ROLE.PRESENTATION} defaultValue={privatePersonalDetails?.legalLastName} spellCheck={false} + shouldSaveDraft={!isEditing} /> - + ); } diff --git a/src/pages/MissingPersonalDetails/substeps/PhoneNumber.tsx b/src/pages/MissingPersonalDetails/substeps/PhoneNumber.tsx index d336e15dd6c28..f38e69a53ca78 100644 --- a/src/pages/MissingPersonalDetails/substeps/PhoneNumber.tsx +++ b/src/pages/MissingPersonalDetails/substeps/PhoneNumber.tsx @@ -1,19 +1,58 @@ -import React from 'react'; +import {Str} from 'expensify-common'; +import React, {useCallback} from 'react'; +import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; +import usePersonalDetailsStepFormSubmit from '@hooks/usePersonalDetailsStepFormSubmit'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as LoginUtils from '@libs/LoginUtils'; +import * as PhoneNumberUtils from '@libs/PhoneNumber'; +import * as ValidationUtils from '@libs/ValidationUtils'; import type {CustomSubStepProps} from '@pages/MissingPersonalDetails/types'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/PersonalDetailsForm'; -function PhoneNumberStep({privatePersonalDetails}: CustomSubStepProps) { +const STEP_FIELDS = [INPUT_IDS.PHONE_NUMBER]; + +function PhoneNumberStep({privatePersonalDetails, isEditing, onNext}: CustomSubStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); + const handleSubmit = usePersonalDetailsStepFormSubmit({ + fieldIds: STEP_FIELDS, + onNext, + shouldSaveDraft: isEditing, + }); + + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); + if (!ValidationUtils.isRequiredFulfilled(values[INPUT_IDS.PHONE_NUMBER])) { + errors[INPUT_IDS.PHONE_NUMBER] = translate('common.error.fieldRequired'); + } + const phoneNumber = LoginUtils.appendCountryCode(values[INPUT_IDS.PHONE_NUMBER]); + const parsedPhoneNumber = PhoneNumberUtils.parsePhoneNumber(phoneNumber); + if (!parsedPhoneNumber.possible || !Str.isValidE164Phone(phoneNumber.slice(0))) { + errors[INPUT_IDS.PHONE_NUMBER] = translate('bankAccount.error.phoneNumber'); + } + return errors; + }, + [translate], + ); + return ( - <> + {translate('privatePersonalDetails.enterPhoneNumber')} - + ); } From 21a39de2b19e0bf56923dbcf3f175e23e4b76b67 Mon Sep 17 00:00:00 2001 From: burczu Date: Thu, 3 Oct 2024 10:05:51 +0200 Subject: [PATCH 04/52] missing personal details page final refactor --- src/pages/MissingPersonalDetails/index.tsx | 141 +++--------------- src/pages/MissingPersonalDetails/types.ts | 7 +- .../PersonalInfo/PersonalInfo.tsx | 2 - 3 files changed, 26 insertions(+), 124 deletions(-) diff --git a/src/pages/MissingPersonalDetails/index.tsx b/src/pages/MissingPersonalDetails/index.tsx index 2ad9d772b2ca1..01e1270baf4ca 100644 --- a/src/pages/MissingPersonalDetails/index.tsx +++ b/src/pages/MissingPersonalDetails/index.tsx @@ -1,11 +1,8 @@ /* eslint-disable no-case-declarations */ -import {Str} from 'expensify-common'; import React, {useCallback, useMemo, useRef} from 'react'; import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; -import FormProvider from '@components/Form/FormProvider'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; import type {InteractiveStepSubHeaderHandle} from '@components/InteractiveStepSubHeader'; @@ -13,20 +10,17 @@ import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useSubStep from '@hooks/useSubStep'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ErrorUtils from '@libs/ErrorUtils'; -import * as LoginUtils from '@libs/LoginUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as PhoneNumberUtils from '@libs/PhoneNumber'; -import * as ValidationUtils from '@libs/ValidationUtils'; import * as PersonalDetails from '@userActions/PersonalDetails'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {PersonalDetailsForm} from '@src/types/form/PersonalDetailsForm'; import INPUT_IDS from '@src/types/form/PersonalDetailsForm'; import Address from './substeps/Address'; import DateOfBirth from './substeps/DateOfBirth'; import LegalName from './substeps/LegalName'; import PhoneNumber from './substeps/PhoneNumber'; -import type {CountryZipRegex, CustomSubStepProps} from './types'; +import type {CustomSubStepProps, SubStepsValues} from './types'; const formSteps = [LegalName, DateOfBirth, Address, PhoneNumber]; @@ -35,13 +29,23 @@ function MissingPersonalDetails() { const {translate} = useLocalize(); const ref: ForwardedRef = useRef(null); const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS); + const [personalDetailsForm] = useOnyx(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM); + const [personalDetailsFormDraft] = useOnyx(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM_DRAFT); const [cardList] = useOnyx(ONYXKEYS.CARD_LIST); const firstUnissuedCard = useMemo(() => Object.values(cardList ?? {}).find((card) => card.state === CONST.EXPENSIFY_CARD.STATE.STATE_NOT_ISSUED), [cardList]); + const values = useMemo(() => { + return Object.entries(INPUT_IDS).reduce((acc, [, value]) => { + // @ts-expect-error complaints about Country not being a string, but it is + acc[value] = (personalDetailsFormDraft?.[value] ?? personalDetailsForm?.[value] ?? '') as PersonalDetailsForm[keyof PersonalDetailsForm]; + return acc; + }, {} as SubStepsValues); + }, [personalDetailsForm, personalDetailsFormDraft]); const handleFinishStep = useCallback(() => { + PersonalDetails.updatePersonalDetailsAndShipExpensifyCard(values, firstUnissuedCard?.cardID ?? 0); Navigation.goBack(); - }, []); + }, [firstUnissuedCard?.cardID, values]); const { componentToRender: SubStep, @@ -51,7 +55,12 @@ function MissingPersonalDetails() { screenIndex, moveTo, goToTheLastStep, - } = useSubStep({bodyContent: formSteps, startFrom: CONST.MISSING_PERSONAL_DETAILS_INDEXES.MAPPING.LEGAL_NAME, onFinished: handleFinishStep}); + } = useSubStep({ + bodyContent: formSteps, + startFrom: CONST.MISSING_PERSONAL_DETAILS_INDEXES.MAPPING.LEGAL_NAME, + onFinished: handleFinishStep, + onNextSubStep: () => ref.current?.moveNext(), + }); const handleBackButtonPress = () => { if (isEditing) { @@ -68,6 +77,7 @@ function MissingPersonalDetails() { prevScreen(); }; + // TODO: consider if this is necessary // const handleNextScreen = useCallback(() => { // if (isEditing) { // goToTheLastStep(); @@ -77,98 +87,6 @@ function MissingPersonalDetails() { // nextScreen(); // }, [goToTheLastStep, isEditing, nextScreen]); - const validate = useCallback( - (values: FormOnyxValues): FormInputErrors => { - const errors: FormInputErrors = {}; - switch (screenIndex) { - case CONST.MISSING_PERSONAL_DETAILS_INDEXES.MAPPING.LEGAL_NAME: - if (!ValidationUtils.isRequiredFulfilled(values[INPUT_IDS.LEGAL_FIRST_NAME])) { - errors[INPUT_IDS.LEGAL_FIRST_NAME] = translate('common.error.fieldRequired'); - } else if (!ValidationUtils.isValidLegalName(values[INPUT_IDS.LEGAL_FIRST_NAME])) { - errors[INPUT_IDS.LEGAL_FIRST_NAME] = translate('privatePersonalDetails.error.hasInvalidCharacter'); - } else if (values[INPUT_IDS.LEGAL_FIRST_NAME].length > CONST.LEGAL_NAME.MAX_LENGTH) { - errors[INPUT_IDS.LEGAL_FIRST_NAME] = translate('common.error.characterLimitExceedCounter', { - length: values[INPUT_IDS.LEGAL_FIRST_NAME].length, - limit: CONST.LEGAL_NAME.MAX_LENGTH, - }); - } - if (ValidationUtils.doesContainReservedWord(values[INPUT_IDS.LEGAL_FIRST_NAME], CONST.DISPLAY_NAME.RESERVED_NAMES)) { - ErrorUtils.addErrorMessage(errors, INPUT_IDS.LEGAL_FIRST_NAME, translate('personalDetails.error.containsReservedWord')); - } - if (!ValidationUtils.isRequiredFulfilled(values[INPUT_IDS.LEGAL_LAST_NAME])) { - errors[INPUT_IDS.LEGAL_LAST_NAME] = translate('common.error.fieldRequired'); - } else if (!ValidationUtils.isValidLegalName(values[INPUT_IDS.LEGAL_LAST_NAME])) { - errors[INPUT_IDS.LEGAL_LAST_NAME] = translate('privatePersonalDetails.error.hasInvalidCharacter'); - } else if (values[INPUT_IDS.LEGAL_LAST_NAME].length > CONST.LEGAL_NAME.MAX_LENGTH) { - errors[INPUT_IDS.LEGAL_LAST_NAME] = translate('common.error.characterLimitExceedCounter', { - length: values[INPUT_IDS.LEGAL_LAST_NAME].length, - limit: CONST.LEGAL_NAME.MAX_LENGTH, - }); - } - if (ValidationUtils.doesContainReservedWord(values[INPUT_IDS.LEGAL_LAST_NAME], CONST.DISPLAY_NAME.RESERVED_NAMES)) { - ErrorUtils.addErrorMessage(errors, INPUT_IDS.LEGAL_LAST_NAME, translate('personalDetails.error.containsReservedWord')); - } - return errors; - case CONST.MISSING_PERSONAL_DETAILS_INDEXES.MAPPING.DATE_OF_BIRTH: - if (!ValidationUtils.isRequiredFulfilled(values[INPUT_IDS.DATE_OF_BIRTH])) { - errors[INPUT_IDS.DATE_OF_BIRTH] = translate('common.error.fieldRequired'); - } else if (!ValidationUtils.isValidPastDate(values[INPUT_IDS.DATE_OF_BIRTH]) || !ValidationUtils.meetsMaximumAgeRequirement(values[INPUT_IDS.DATE_OF_BIRTH])) { - errors[INPUT_IDS.DATE_OF_BIRTH] = translate('bankAccount.error.dob'); - } - return errors; - case CONST.MISSING_PERSONAL_DETAILS_INDEXES.MAPPING.ADDRESS: - const addressRequiredFields = [INPUT_IDS.ADDRESS_LINE_1, INPUT_IDS.CITY, INPUT_IDS.COUNTRY, INPUT_IDS.STATE] as const; - addressRequiredFields.forEach((fieldKey) => { - const fieldValue = values[fieldKey] ?? ''; - if (ValidationUtils.isRequiredFulfilled(fieldValue)) { - return; - } - errors[fieldKey] = translate('common.error.fieldRequired'); - }); - - // If no country is selected, default value is an empty string and there's no related regex data so we default to an empty object - const countryRegexDetails = (values.country ? CONST.COUNTRY_ZIP_REGEX_DATA?.[values.country] : {}) as CountryZipRegex; - - // The postal code system might not exist for a country, so no regex either for them. - const countrySpecificZipRegex = countryRegexDetails?.regex; - const countryZipFormat = countryRegexDetails?.samples ?? ''; - if (countrySpecificZipRegex) { - if (!countrySpecificZipRegex.test(values[INPUT_IDS.ZIP_POST_CODE]?.trim().toUpperCase())) { - if (ValidationUtils.isRequiredFulfilled(values[INPUT_IDS.ZIP_POST_CODE]?.trim())) { - errors[INPUT_IDS.ZIP_POST_CODE] = translate('privatePersonalDetails.error.incorrectZipFormat', {zipFormat: countryZipFormat}); - } else { - errors[INPUT_IDS.ZIP_POST_CODE] = translate('common.error.fieldRequired'); - } - } - } else if (!CONST.GENERIC_ZIP_CODE_REGEX.test(values[INPUT_IDS.ZIP_POST_CODE]?.trim()?.toUpperCase() ?? '')) { - errors[INPUT_IDS.ZIP_POST_CODE] = translate('privatePersonalDetails.error.incorrectZipFormat'); - } - return errors; - case CONST.MISSING_PERSONAL_DETAILS_INDEXES.MAPPING.PHONE_NUMBER: - if (!ValidationUtils.isRequiredFulfilled(values[INPUT_IDS.PHONE_NUMBER])) { - errors[INPUT_IDS.PHONE_NUMBER] = translate('common.error.fieldRequired'); - } - const phoneNumber = LoginUtils.appendCountryCode(values[INPUT_IDS.PHONE_NUMBER]); - const parsedPhoneNumber = PhoneNumberUtils.parsePhoneNumber(phoneNumber); - if (!parsedPhoneNumber.possible || !Str.isValidE164Phone(phoneNumber.slice(0))) { - errors[INPUT_IDS.PHONE_NUMBER] = translate('bankAccount.error.phoneNumber'); - } - return errors; - default: - return errors; - } - }, - [screenIndex, translate], - ); - - const updatePersonalDetails = useCallback( - (formValues: FormOnyxValues) => { - PersonalDetails.updatePersonalDetailsAndShipExpensifyCard(formValues, firstUnissuedCard?.cardID ?? 0); - nextScreen(); - }, - [nextScreen, firstUnissuedCard], - ); - return ( - {/**/} - {/* */} - {/* */} - {/* */} - {/**/} ); } diff --git a/src/pages/MissingPersonalDetails/types.ts b/src/pages/MissingPersonalDetails/types.ts index 9a9963cc628a4..ccc90f0ae11f4 100644 --- a/src/pages/MissingPersonalDetails/types.ts +++ b/src/pages/MissingPersonalDetails/types.ts @@ -1,5 +1,6 @@ import type {OnyxEntry} from 'react-native-onyx'; import type {SubStepProps} from '@hooks/useSubStep/types'; +import type {PersonalDetailsForm} from '@src/types/form'; import type {PrivatePersonalDetails} from '@src/types/onyx'; type CustomSubStepProps = SubStepProps & { @@ -12,4 +13,8 @@ type CountryZipRegex = { samples?: string; }; -export type {CustomSubStepProps, CountryZipRegex}; +type SubStepsValues = { + [TKey in keyof PersonalDetailsForm]: PersonalDetailsForm[TKey]; +}; + +export type {CustomSubStepProps, CountryZipRegex, SubStepsValues}; diff --git a/src/pages/ReimbursementAccount/PersonalInfo/PersonalInfo.tsx b/src/pages/ReimbursementAccount/PersonalInfo/PersonalInfo.tsx index f955572da738c..84c3cc5bab5ee 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/PersonalInfo.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/PersonalInfo.tsx @@ -7,7 +7,6 @@ import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import useLocalize from '@hooks/useLocalize'; import useSubStep from '@hooks/useSubStep'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; import getInitialSubstepForPersonalInfo from '@pages/ReimbursementAccount/utils/getInitialSubstepForPersonalInfo'; import getSubstepValues from '@pages/ReimbursementAccount/utils/getSubstepValues'; import * as BankAccounts from '@userActions/BankAccounts'; @@ -40,7 +39,6 @@ const bodyContent: Array> = [FullName, DateOfB function PersonalInfo({reimbursementAccount, reimbursementAccountDraft, onBackButtonPress}: PersonalInfoProps, ref: React.ForwardedRef) { const {translate} = useLocalize(); - const styles = useThemeStyles(); const policyID = reimbursementAccount?.achData?.policyID ?? '-1'; const values = useMemo(() => getSubstepValues(PERSONAL_INFO_STEP_KEYS, reimbursementAccountDraft, reimbursementAccount), [reimbursementAccount, reimbursementAccountDraft]); From d0b68c9be2f73c0ea2a628ebda16b203d3088c2f Mon Sep 17 00:00:00 2001 From: burczu Date: Mon, 7 Oct 2024 08:37:44 +0200 Subject: [PATCH 05/52] styling fixed --- .../substeps/Address.tsx | 160 +++++++++--------- .../substeps/DateOfBirth.tsx | 27 +-- .../substeps/LegalName.tsx | 56 +++--- .../substeps/PhoneNumber.tsx | 29 ++-- 4 files changed, 141 insertions(+), 131 deletions(-) diff --git a/src/pages/MissingPersonalDetails/substeps/Address.tsx b/src/pages/MissingPersonalDetails/substeps/Address.tsx index 153027db6fa1b..dcf34631e0b39 100644 --- a/src/pages/MissingPersonalDetails/substeps/Address.tsx +++ b/src/pages/MissingPersonalDetails/substeps/Address.tsx @@ -126,105 +126,107 @@ function AddressStep({privatePersonalDetails, isEditing, onNext}: CustomSubStepP submitButtonText={translate(isEditing ? 'common.confirm' : 'common.next')} onSubmit={handleSubmit} validate={validate} - style={[styles.mh0, styles.flexGrow1]} + style={[styles.mh5, styles.flexGrow1]} submitButtonStyles={[styles.mb0]} > - {translate('privatePersonalDetails.enterAddress')} + {translate('privatePersonalDetails.enterAddress')} + + { + handleAddressChange(data, key); + }} + defaultValue={street1} + containerStyles={styles.mt3} + renamedInputKeys={{ + street: INPUT_IDS.ADDRESS_LINE_1, + street2: INPUT_IDS.ADDRESS_LINE_2, + city: INPUT_IDS.CITY, + state: INPUT_IDS.STATE, + zipCode: INPUT_IDS.ZIP_POST_CODE, + country: INPUT_IDS.COUNTRY as Country, + }} + maxInputLength={CONST.FORM_CHARACTER_LIMIT} + shouldSaveDraft={!isEditing} + /> + { - handleAddressChange(data, key); - }} - defaultValue={street1} - containerStyles={styles.mt3} - renamedInputKeys={{ - street: INPUT_IDS.ADDRESS_LINE_1, - street2: INPUT_IDS.ADDRESS_LINE_2, - city: INPUT_IDS.CITY, - state: INPUT_IDS.STATE, - zipCode: INPUT_IDS.ZIP_POST_CODE, - country: INPUT_IDS.COUNTRY as Country, - }} - maxInputLength={CONST.FORM_CHARACTER_LIMIT} - shouldSaveDraft={!isEditing} - /> - - - - - - {isUSAForm ? ( - ) : ( + {isUSAForm ? ( + + + + ) : ( + + )} - )} - - + + ); } diff --git a/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx b/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx index e5850240d0717..ec63fdca2a177 100644 --- a/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx +++ b/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx @@ -1,5 +1,6 @@ import {subYears} from 'date-fns'; import React, {useCallback} from 'react'; +import {View} from 'react-native'; import DatePicker from '@components/DatePicker'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; @@ -48,20 +49,22 @@ function DateOfBirthStep({privatePersonalDetails, isEditing, onNext}: CustomSubS submitButtonText={translate(isEditing ? 'common.confirm' : 'common.next')} onSubmit={handleSubmit} validate={validate} - style={[styles.mh0, styles.flexGrow1]} + style={[styles.mh5, styles.flexGrow1]} submitButtonStyles={[styles.mb0]} > - {translate('privatePersonalDetails.enterDateOfBirth')} - + + {translate('privatePersonalDetails.enterDateOfBirth')} + + ); } diff --git a/src/pages/MissingPersonalDetails/substeps/LegalName.tsx b/src/pages/MissingPersonalDetails/substeps/LegalName.tsx index 6b41791f36959..e2926cc7dbb85 100644 --- a/src/pages/MissingPersonalDetails/substeps/LegalName.tsx +++ b/src/pages/MissingPersonalDetails/substeps/LegalName.tsx @@ -48,35 +48,37 @@ function LegalNameStep({privatePersonalDetails, isEditing, onNext}: CustomSubSte submitButtonText={translate(isEditing ? 'common.confirm' : 'common.next')} validate={validate} onSubmit={handleSubmit} - style={[styles.mh0, styles.flexGrow1]} + style={[styles.mh5, styles.flexGrow1]} submitButtonStyles={[styles.mb0]} > - {translate('privatePersonalDetails.enterLegalName')} - - - - - + + {translate('privatePersonalDetails.enterLegalName')} + + + + + + ); diff --git a/src/pages/MissingPersonalDetails/substeps/PhoneNumber.tsx b/src/pages/MissingPersonalDetails/substeps/PhoneNumber.tsx index f38e69a53ca78..b76e7a80f80d7 100644 --- a/src/pages/MissingPersonalDetails/substeps/PhoneNumber.tsx +++ b/src/pages/MissingPersonalDetails/substeps/PhoneNumber.tsx @@ -1,5 +1,6 @@ import {Str} from 'expensify-common'; import React, {useCallback} from 'react'; +import {View} from 'react-native'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; @@ -50,21 +51,23 @@ function PhoneNumberStep({privatePersonalDetails, isEditing, onNext}: CustomSubS submitButtonText={translate(isEditing ? 'common.confirm' : 'common.next')} onSubmit={handleSubmit} validate={validate} - style={[styles.mh0, styles.flexGrow1]} + style={[styles.mh5, styles.flexGrow1]} submitButtonStyles={[styles.mb0]} > - {translate('privatePersonalDetails.enterPhoneNumber')} - + + {translate('privatePersonalDetails.enterPhoneNumber')} + + ); } From 6c9be432f4c6894aa1f60ea7668afe1a2193c52c Mon Sep 17 00:00:00 2001 From: burczu Date: Mon, 7 Oct 2024 13:14:04 +0200 Subject: [PATCH 06/52] missing personal details adjusted to use interactive step wrapper --- src/components/InteractiveStepSubHeader.tsx | 9 +- src/hooks/useSubStep/index.ts | 7 +- src/pages/MissingPersonalDetails/index.tsx | 85 +++++++------------ .../utils/getFormValues.tsx | 20 +++++ .../utils/getInitialStepForPersonalInfo.tsx | 31 +++++++ 5 files changed, 94 insertions(+), 58 deletions(-) create mode 100644 src/pages/MissingPersonalDetails/utils/getFormValues.tsx create mode 100644 src/pages/MissingPersonalDetails/utils/getInitialStepForPersonalInfo.tsx diff --git a/src/components/InteractiveStepSubHeader.tsx b/src/components/InteractiveStepSubHeader.tsx index d8899a317df55..483e8857461bb 100644 --- a/src/components/InteractiveStepSubHeader.tsx +++ b/src/components/InteractiveStepSubHeader.tsx @@ -1,5 +1,5 @@ import type {ForwardedRef} from 'react'; -import React, {forwardRef, useImperativeHandle, useState} from 'react'; +import React, {forwardRef, useEffect, useImperativeHandle, useState} from 'react'; import type {ViewStyle} from 'react-native'; import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -36,12 +36,17 @@ const MIN_AMOUNT_OF_STEPS = 2; function InteractiveStepSubHeader({stepNames, startStepIndex = 0, onStepSelected}: InteractiveStepSubHeaderProps, ref: ForwardedRef) { const styles = useThemeStyles(); const containerWidthStyle: ViewStyle = stepNames.length < MIN_AMOUNT_FOR_EXPANDING ? styles.mnw60 : styles.mnw100; + const [currentStep, setCurrentStep] = useState(startStepIndex); if (stepNames.length < MIN_AMOUNT_OF_STEPS) { throw new Error(`stepNames list must have at least ${MIN_AMOUNT_OF_STEPS} elements.`); } - const [currentStep, setCurrentStep] = useState(startStepIndex); + useEffect(() => { + // make sure to update current step if startStepIndex changes outside the component + setCurrentStep(startStepIndex); + }, [startStepIndex]); + useImperativeHandle( ref, () => ({ diff --git a/src/hooks/useSubStep/index.ts b/src/hooks/useSubStep/index.ts index c89bb5fd5735c..229140f71e831 100644 --- a/src/hooks/useSubStep/index.ts +++ b/src/hooks/useSubStep/index.ts @@ -1,4 +1,4 @@ -import {useCallback, useRef, useState} from 'react'; +import {useCallback, useEffect, useRef, useState} from 'react'; import type {SubStepProps, UseSubStep} from './types'; /** @@ -12,6 +12,11 @@ export default function useSubStep({bodyContent, on const [screenIndex, setScreenIndex] = useState(startFrom); const isEditing = useRef(false); + useEffect(() => { + // make sure the screen index is updated whenever the startFrom prop changes + setScreenIndex(startFrom); + }, [startFrom]); + const prevScreen = useCallback(() => { const prevScreenIndex = screenIndex - 1; diff --git a/src/pages/MissingPersonalDetails/index.tsx b/src/pages/MissingPersonalDetails/index.tsx index 01e1270baf4ca..9e5667b333973 100644 --- a/src/pages/MissingPersonalDetails/index.tsx +++ b/src/pages/MissingPersonalDetails/index.tsx @@ -1,49 +1,43 @@ /* eslint-disable no-case-declarations */ -import React, {useCallback, useMemo, useRef} from 'react'; -import type {ForwardedRef} from 'react'; -import {View} from 'react-native'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {useOnyx} from 'react-native-onyx'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; -import type {InteractiveStepSubHeaderHandle} from '@components/InteractiveStepSubHeader'; -import ScreenWrapper from '@components/ScreenWrapper'; +import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import useLocalize from '@hooks/useLocalize'; import useSubStep from '@hooks/useSubStep'; -import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; +import * as FormActions from '@userActions/FormActions'; import * as PersonalDetails from '@userActions/PersonalDetails'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PersonalDetailsForm} from '@src/types/form/PersonalDetailsForm'; import INPUT_IDS from '@src/types/form/PersonalDetailsForm'; import Address from './substeps/Address'; import DateOfBirth from './substeps/DateOfBirth'; import LegalName from './substeps/LegalName'; import PhoneNumber from './substeps/PhoneNumber'; -import type {CustomSubStepProps, SubStepsValues} from './types'; +import type {CustomSubStepProps} from './types'; +import getFormValues from './utils/getFormValues'; +import getInitialStepForPersonalInfo from './utils/getInitialStepForPersonalInfo'; const formSteps = [LegalName, DateOfBirth, Address, PhoneNumber]; function MissingPersonalDetails() { - const styles = useThemeStyles(); const {translate} = useLocalize(); - const ref: ForwardedRef = useRef(null); const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS); const [personalDetailsForm] = useOnyx(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM); const [personalDetailsFormDraft] = useOnyx(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM_DRAFT); const [cardList] = useOnyx(ONYXKEYS.CARD_LIST); + const [currentStep, setCurrentStep] = useState(getInitialStepForPersonalInfo(personalDetailsFormDraft)); + + useEffect(() => { + setCurrentStep(getInitialStepForPersonalInfo(personalDetailsFormDraft)); + }, [personalDetailsFormDraft]); const firstUnissuedCard = useMemo(() => Object.values(cardList ?? {}).find((card) => card.state === CONST.EXPENSIFY_CARD.STATE.STATE_NOT_ISSUED), [cardList]); - const values = useMemo(() => { - return Object.entries(INPUT_IDS).reduce((acc, [, value]) => { - // @ts-expect-error complaints about Country not being a string, but it is - acc[value] = (personalDetailsFormDraft?.[value] ?? personalDetailsForm?.[value] ?? '') as PersonalDetailsForm[keyof PersonalDetailsForm]; - return acc; - }, {} as SubStepsValues); - }, [personalDetailsForm, personalDetailsFormDraft]); + const values = useMemo(() => getFormValues(INPUT_IDS, personalDetailsFormDraft, personalDetailsForm), [personalDetailsForm, personalDetailsFormDraft]); const handleFinishStep = useCallback(() => { PersonalDetails.updatePersonalDetailsAndShipExpensifyCard(values, firstUnissuedCard?.cardID ?? 0); + FormActions.clearDraftValues(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM); Navigation.goBack(); }, [firstUnissuedCard?.cardID, values]); @@ -57,9 +51,9 @@ function MissingPersonalDetails() { goToTheLastStep, } = useSubStep({ bodyContent: formSteps, - startFrom: CONST.MISSING_PERSONAL_DETAILS_INDEXES.MAPPING.LEGAL_NAME, + startFrom: currentStep, onFinished: handleFinishStep, - onNextSubStep: () => ref.current?.moveNext(), + onNextSubStep: () => setCurrentStep(currentStep + 1), }); const handleBackButtonPress = () => { @@ -73,47 +67,28 @@ function MissingPersonalDetails() { Navigation.goBack(); return; } - ref.current?.movePrevious(); + setCurrentStep(currentStep - 1); prevScreen(); }; - // TODO: consider if this is necessary - // const handleNextScreen = useCallback(() => { - // if (isEditing) { - // goToTheLastStep(); - // return; - // } - // ref.current?.moveNext(); - // nextScreen(); - // }, [goToTheLastStep, isEditing, nextScreen]); - return ( - - - - - - - - - + ); } diff --git a/src/pages/MissingPersonalDetails/utils/getFormValues.tsx b/src/pages/MissingPersonalDetails/utils/getFormValues.tsx new file mode 100644 index 0000000000000..aa58c476602b5 --- /dev/null +++ b/src/pages/MissingPersonalDetails/utils/getFormValues.tsx @@ -0,0 +1,20 @@ +import type {OnyxEntry} from 'react-native-onyx'; +import type {PersonalDetailsForm} from '@src/types/form'; + +type FormValues = { + [TKey in TProps]: PersonalDetailsForm[TKey]; +}; + +function getFormValues( + inputKeys: Record, + personalDetailsFormDraft: OnyxEntry, + personalDetailsForm: OnyxEntry, +): FormValues { + return Object.entries(inputKeys).reduce((acc, [, value]) => { + // @ts-expect-error complaints about Country not being a string, but it is + acc[value] = (personalDetailsFormDraft?.[value] ?? personalDetailsForm?.[value] ?? '') as PersonalDetailsForm[keyof PersonalDetailsForm]; + return acc; + }, {} as FormValues); +} + +export default getFormValues; diff --git a/src/pages/MissingPersonalDetails/utils/getInitialStepForPersonalInfo.tsx b/src/pages/MissingPersonalDetails/utils/getInitialStepForPersonalInfo.tsx new file mode 100644 index 0000000000000..c1353dfb00028 --- /dev/null +++ b/src/pages/MissingPersonalDetails/utils/getInitialStepForPersonalInfo.tsx @@ -0,0 +1,31 @@ +import type {OnyxEntry} from 'react-native-onyx'; +import type {PersonalDetailsForm} from '@src/types/form/PersonalDetailsForm'; +import INPUT_IDS from '@src/types/form/PersonalDetailsForm'; + +/** + * Returns the initial step for the Personal Info step based on already existing data + */ +function getInitialStepForPersonalInfo(data: OnyxEntry): number { + if (!data?.[INPUT_IDS.LEGAL_FIRST_NAME] || !data?.[INPUT_IDS.LEGAL_LAST_NAME]) { + return 0; + } + + if (!data?.[INPUT_IDS.DATE_OF_BIRTH]) { + return 1; + } + + if ( + !data?.[INPUT_IDS.ADDRESS_LINE_1] || + !data?.[INPUT_IDS.ADDRESS_LINE_2] || + !data?.[INPUT_IDS.CITY] || + !data?.[INPUT_IDS.STATE] || + !data?.[INPUT_IDS.ZIP_POST_CODE] || + !data?.[INPUT_IDS.COUNTRY] + ) { + return 2; + } + + return 3; +} + +export default getInitialStepForPersonalInfo; From 2b3b92b4b777a0f57e74b41cd74edd216babdb28 Mon Sep 17 00:00:00 2001 From: burczu Date: Mon, 7 Oct 2024 15:53:20 +0200 Subject: [PATCH 07/52] handling draft and form correctly --- src/libs/actions/FormActions.ts | 10 +++++++++- src/pages/MissingPersonalDetails/index.tsx | 7 ++++--- .../MissingPersonalDetails/substeps/Address.tsx | 12 ++++++++++-- .../MissingPersonalDetails/substeps/DateOfBirth.tsx | 12 ++++++++++-- .../MissingPersonalDetails/substeps/LegalName.tsx | 12 ++++++++++-- .../MissingPersonalDetails/substeps/PhoneNumber.tsx | 13 +++++++++++-- 6 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/libs/actions/FormActions.ts b/src/libs/actions/FormActions.ts index 5fe1705d8db3d..df9f5c0a2a944 100644 --- a/src/libs/actions/FormActions.ts +++ b/src/libs/actions/FormActions.ts @@ -31,4 +31,12 @@ function clearDraftValues(formID: OnyxFormKey) { Onyx.set(`${formID}Draft`, null); } -export {clearDraftValues, clearErrorFields, clearErrors, setDraftValues, setErrorFields, setErrors, setIsLoading}; +function setFormValues(formId: OnyxFormKey, values: NullishDeep>) { + Onyx.merge(formId, values ?? null); +} + +function clearFormValues(formId: OnyxFormKey) { + Onyx.set(formId, null); +} + +export {clearDraftValues, clearErrorFields, clearErrors, setDraftValues, setErrorFields, setErrors, setIsLoading, setFormValues, clearFormValues}; diff --git a/src/pages/MissingPersonalDetails/index.tsx b/src/pages/MissingPersonalDetails/index.tsx index 9e5667b333973..78a6bf408d7bb 100644 --- a/src/pages/MissingPersonalDetails/index.tsx +++ b/src/pages/MissingPersonalDetails/index.tsx @@ -26,17 +26,18 @@ function MissingPersonalDetails() { const [personalDetailsForm] = useOnyx(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM); const [personalDetailsFormDraft] = useOnyx(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM_DRAFT); const [cardList] = useOnyx(ONYXKEYS.CARD_LIST); - const [currentStep, setCurrentStep] = useState(getInitialStepForPersonalInfo(personalDetailsFormDraft)); + const [currentStep, setCurrentStep] = useState(getInitialStepForPersonalInfo(personalDetailsForm)); useEffect(() => { - setCurrentStep(getInitialStepForPersonalInfo(personalDetailsFormDraft)); - }, [personalDetailsFormDraft]); + setCurrentStep(getInitialStepForPersonalInfo(personalDetailsForm)); + }, [personalDetailsForm]); const firstUnissuedCard = useMemo(() => Object.values(cardList ?? {}).find((card) => card.state === CONST.EXPENSIFY_CARD.STATE.STATE_NOT_ISSUED), [cardList]); const values = useMemo(() => getFormValues(INPUT_IDS, personalDetailsFormDraft, personalDetailsForm), [personalDetailsForm, personalDetailsFormDraft]); const handleFinishStep = useCallback(() => { PersonalDetails.updatePersonalDetailsAndShipExpensifyCard(values, firstUnissuedCard?.cardID ?? 0); + FormActions.clearFormValues(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM); FormActions.clearDraftValues(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM); Navigation.goBack(); }, [firstUnissuedCard?.cardID, values]); diff --git a/src/pages/MissingPersonalDetails/substeps/Address.tsx b/src/pages/MissingPersonalDetails/substeps/Address.tsx index dcf34631e0b39..22bfff7844db6 100644 --- a/src/pages/MissingPersonalDetails/substeps/Address.tsx +++ b/src/pages/MissingPersonalDetails/substeps/Address.tsx @@ -15,11 +15,13 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ValidationUtils from '@libs/ValidationUtils'; import type {CountryZipRegex, CustomSubStepProps} from '@pages/MissingPersonalDetails/types'; +import * as FormActions from '@userActions/FormActions'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/PersonalDetailsForm'; import type {Address} from '@src/types/onyx/PrivatePersonalDetails'; +import {useOnyx} from "react-native-onyx"; const STEP_FIELDS = [INPUT_IDS.ADDRESS_LINE_1, INPUT_IDS.ADDRESS_LINE_2, INPUT_IDS.CITY, INPUT_IDS.STATE, INPUT_IDS.COUNTRY, INPUT_IDS.ZIP_POST_CODE]; @@ -108,12 +110,18 @@ function AddressStep({privatePersonalDetails, isEditing, onNext}: CustomSubStepP setZipcode(addressPart); }, []); - const handleSubmit = usePersonalDetailsStepFormSubmit({ + const submitPersonalDetails = usePersonalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, - shouldSaveDraft: isEditing, + shouldSaveDraft: true, }); + const handleSubmit = (values: FormOnyxValues<'personalDetailsForm'>) => { + // in case the address is taken from existing personal details object, we need to force apply its values to the draft object + FormActions.setFormValues(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM, values); + submitPersonalDetails(values); + }; + const isUSAForm = currentCountry === CONST.COUNTRY.US; const zipSampleFormat = (currentCountry && (CONST.COUNTRY_ZIP_REGEX_DATA[currentCountry] as CountryZipRegex)?.samples) ?? ''; diff --git a/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx b/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx index ec63fdca2a177..bee0d07aa18be 100644 --- a/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx +++ b/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx @@ -1,6 +1,7 @@ import {subYears} from 'date-fns'; import React, {useCallback} from 'react'; import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import DatePicker from '@components/DatePicker'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; @@ -11,6 +12,7 @@ import usePersonalDetailsStepFormSubmit from '@hooks/usePersonalDetailsStepFormS import useThemeStyles from '@hooks/useThemeStyles'; import * as ValidationUtils from '@libs/ValidationUtils'; import type {CustomSubStepProps} from '@pages/MissingPersonalDetails/types'; +import * as FormActions from '@userActions/FormActions'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/PersonalDetailsForm'; @@ -37,12 +39,18 @@ function DateOfBirthStep({privatePersonalDetails, isEditing, onNext}: CustomSubS [translate], ); - const handleSubmit = usePersonalDetailsStepFormSubmit({ + const submitPersonalDetails = usePersonalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, - shouldSaveDraft: isEditing, + shouldSaveDraft: true, }); + const handleSubmit = (values: FormOnyxValues<'personalDetailsForm'>) => { + // in case the dob is taken from existing personal details object, we need to force apply its values to the draft object + FormActions.setFormValues(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM, values); + submitPersonalDetails(values); + }; + return ( ) => { + // in case the legal name is taken from existing personal details object, we need to force apply its values to the draft object + FormActions.setFormValues(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM, values); + submitPersonalDetails(values); + }; + return ( ) => { + // in case the phone number is taken from existing personal details object, we need to force apply its values to the draft object + FormActions.setFormValues(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM, values); + submitPersonalDetails(values); + }; + const validate = useCallback( (values: FormOnyxValues): FormInputErrors => { const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); From 26cd6ae99f527a48fd091d9907c10322f90551b2 Mon Sep 17 00:00:00 2001 From: burczu Date: Mon, 14 Oct 2024 14:12:04 +0200 Subject: [PATCH 08/52] common full name step component created --- src/components/FullNameStep.tsx | 104 ++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/components/FullNameStep.tsx diff --git a/src/components/FullNameStep.tsx b/src/components/FullNameStep.tsx new file mode 100644 index 0000000000000..035df1b246b6c --- /dev/null +++ b/src/components/FullNameStep.tsx @@ -0,0 +1,104 @@ +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import useLocalize from '@hooks/useLocalize'; +import type {SubStepProps} from '@hooks/useSubStep/types'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as ValidationUtils from '@libs/ValidationUtils'; +import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; +import CONST from '@src/CONST'; +import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; +import FormProvider from './Form/FormProvider'; +import InputWrapper from './Form/InputWrapper'; +import type {FormOnyxKeys, FormOnyxValues} from './Form/types'; +import Text from './Text'; +import TextInput from './TextInput'; + +type FullNameStepProps = SubStepProps & { + /** The ID of the form */ + formID: keyof OnyxFormValuesMapping; + + /** The title of the form */ + formTitle: string; + + /** The validation function to call when the form is submitted */ + customValidate?: (values: FormOnyxValues) => Partial>; + + /** A function to call when the form is submitted */ + onSubmit: () => void; + + /** Fields list of the form */ + stepFields: Array>; + + /** The ID of the first name input */ + firstNameInputID: never; + + /** The ID of the last name input */ + lastNameInputID: never; + + /** The default values for the form */ + defaultValues: { + firstName: string; + lastName: string; + }; +}; + +function FullNameStep({formID, formTitle, customValidate, onSubmit, stepFields, firstNameInputID, lastNameInputID, defaultValues, isEditing}: FullNameStepProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const validate = useCallback( + (values: FormOnyxValues): Partial> => { + const errors: Partial> = ValidationUtils.getFieldRequiredErrors(values, stepFields); + if (values[firstNameInputID] && !ValidationUtils.isValidLegalName(values[firstNameInputID])) { + // @ts-expect-error type mismatch to be fixed + errors[firstNameInputID] = translate('common.error.fieldRequired'); + } + + if (values[lastNameInputID] && !ValidationUtils.isValidLegalName(values[lastNameInputID])) { + // @ts-expect-error type mismatch to be fixed + errors[lastNameInputID] = translate('common.error.fieldRequired'); + } + return errors; + }, + [firstNameInputID, lastNameInputID, stepFields, translate], + ); + + return ( + + + {formTitle} + + + + + + ); +} + +FullNameStep.displayName = 'FullNameStep'; + +export default FullNameStep; From 7e1c89e45cacbd023f769894380f3e205e7892d8 Mon Sep 17 00:00:00 2001 From: burczu Date: Tue, 15 Oct 2024 12:40:55 +0200 Subject: [PATCH 09/52] switched to use common full name step component in reimbursement account --- src/components/FullNameStep.tsx | 6 +- .../PersonalInfo/substeps/FullName.tsx | 82 ++++--------------- 2 files changed, 21 insertions(+), 67 deletions(-) diff --git a/src/components/FullNameStep.tsx b/src/components/FullNameStep.tsx index 035df1b246b6c..d61658c5f4487 100644 --- a/src/components/FullNameStep.tsx +++ b/src/components/FullNameStep.tsx @@ -24,16 +24,16 @@ type FullNameStepProps = SubStepProps & { customValidate?: (values: FormOnyxValues) => Partial>; /** A function to call when the form is submitted */ - onSubmit: () => void; + onSubmit: (values: FormOnyxValues) => void; /** Fields list of the form */ stepFields: Array>; /** The ID of the first name input */ - firstNameInputID: never; + firstNameInputID: keyof FormOnyxValues; /** The ID of the last name input */ - lastNameInputID: never; + lastNameInputID: keyof FormOnyxValues; /** The default values for the form */ defaultValues: { diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx index 15c4114432da4..9fbd70d095e8d 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx @@ -1,20 +1,12 @@ -import React, {useCallback} from 'react'; -import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import Text from '@components/Text'; -import TextInput from '@components/TextInput'; +import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; +import FullNameStep from '@components/FullNameStep'; import useLocalize from '@hooks/useLocalize'; import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; -import * as ValidationUtils from '@libs/ValidationUtils'; -import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; import type {ReimbursementAccount} from '@src/types/onyx'; @@ -26,77 +18,39 @@ type FullNameOnyxProps = { type FullNameProps = FullNameOnyxProps & SubStepProps; const PERSONAL_INFO_STEP_KEY = INPUT_IDS.PERSONAL_INFO_STEP; -const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.FIRST_NAME, PERSONAL_INFO_STEP_KEY.LAST_NAME]; -function FullName({reimbursementAccount, onNext, isEditing}: FullNameProps) { +const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.FIRST_NAME, PERSONAL_INFO_STEP_KEY.LAST_NAME] as Array>; + +function FullName({reimbursementAccount, onNext, onMove, isEditing}: FullNameProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); const defaultValues = { firstName: reimbursementAccount?.achData?.[PERSONAL_INFO_STEP_KEY.FIRST_NAME] ?? '', lastName: reimbursementAccount?.achData?.[PERSONAL_INFO_STEP_KEY.LAST_NAME] ?? '', }; - const validate = useCallback( - (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.firstName && !ValidationUtils.isValidPersonName(values.firstName)) { - errors.firstName = translate('bankAccount.error.firstName'); - } - - if (values.lastName && !ValidationUtils.isValidPersonName(values.lastName)) { - errors.lastName = translate('bankAccount.error.lastName'); - } - return errors; - }, - [translate], - ); - const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: isEditing, - }); + }) as (values: FormOnyxValues) => void; return ( - - - {translate('personalInfoStep.enterYourLegalFirstAndLast')} - - - - - - - - - + stepFields={STEP_FIELDS} + firstNameInputID={PERSONAL_INFO_STEP_KEY.FIRST_NAME as keyof FormOnyxValues} + lastNameInputID={PERSONAL_INFO_STEP_KEY.LAST_NAME as keyof FormOnyxValues} + defaultValues={defaultValues} + /> ); } -FullName.displayName = 'FullName'; +FullName.defaultName = 'FullName'; export default withOnyx({ // @ts-expect-error: ONYXKEYS.REIMBURSEMENT_ACCOUNT is conflicting with ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM From 1f849db85c47af15ae804736a9e158db14f180d0 Mon Sep 17 00:00:00 2001 From: burczu Date: Tue, 15 Oct 2024 12:55:17 +0200 Subject: [PATCH 10/52] switched to use common full name step component in enable payments page --- .../PersonalInfo/PersonalInfo.tsx | 4 +- .../PersonalInfo/substeps/FullNameStep.tsx | 92 ------------------- .../PersonalInfo/substeps/LegalNameStep.tsx | 48 ++++++++++ .../PersonalInfo/substeps/FullName.tsx | 1 + 4 files changed, 51 insertions(+), 94 deletions(-) delete mode 100644 src/pages/EnablePayments/PersonalInfo/substeps/FullNameStep.tsx create mode 100644 src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx diff --git a/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx b/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx index 2a91766b52038..b416578bdf995 100644 --- a/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx +++ b/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx @@ -18,7 +18,7 @@ import type {WalletAdditionalDetailsRefactor} from '@src/types/onyx/WalletAdditi import Address from './substeps/AddressStep'; import Confirmation from './substeps/ConfirmationStep'; import DateOfBirth from './substeps/DateOfBirthStep'; -import FullName from './substeps/FullNameStep'; +import LegalName from './substeps/LegalNameStep'; import PhoneNumber from './substeps/PhoneNumberStep'; import SocialSecurityNumber from './substeps/SocialSecurityNumberStep'; @@ -33,7 +33,7 @@ type PersonalInfoPageOnyxProps = { type PersonalInfoPageProps = PersonalInfoPageOnyxProps; const PERSONAL_INFO_STEP_KEYS = INPUT_IDS.PERSONAL_INFO_STEP; -const bodyContent: Array> = [FullName, DateOfBirth, Address, PhoneNumber, SocialSecurityNumber, Confirmation]; +const bodyContent: Array> = [LegalName, DateOfBirth, Address, PhoneNumber, SocialSecurityNumber, Confirmation]; function PersonalInfoPage({walletAdditionalDetails, walletAdditionalDetailsDraft}: PersonalInfoPageProps) { const {translate} = useLocalize(); diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/FullNameStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/FullNameStep.tsx deleted file mode 100644 index b40fb22029431..0000000000000 --- a/src/pages/EnablePayments/PersonalInfo/substeps/FullNameStep.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React, {useCallback} from 'react'; -import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import Text from '@components/Text'; -import TextInput from '@components/TextInput'; -import useLocalize from '@hooks/useLocalize'; -import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; -import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; -import * as ValidationUtils from '@libs/ValidationUtils'; -import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; - -const PERSONAL_INFO_STEP_KEY = INPUT_IDS.PERSONAL_INFO_STEP; -const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.FIRST_NAME, PERSONAL_INFO_STEP_KEY.LAST_NAME]; - -function FullNameStep({onNext, isEditing}: SubStepProps) { - const {translate} = useLocalize(); - const styles = useThemeStyles(); - - const [walletAdditionalDetails] = useOnyx(ONYXKEYS.WALLET_ADDITIONAL_DETAILS); - - const defaultValues = { - firstName: walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.FIRST_NAME] ?? '', - lastName: walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.LAST_NAME] ?? '', - }; - - const validate = useCallback( - (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - if (values.legalFirstName && !ValidationUtils.isValidLegalName(values.legalFirstName)) { - errors.legalFirstName = translate('bankAccount.error.firstName'); - } - - if (values.legalLastName && !ValidationUtils.isValidLegalName(values.legalLastName)) { - errors.legalLastName = translate('bankAccount.error.lastName'); - } - return errors; - }, - [translate], - ); - - const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ - fieldIds: STEP_FIELDS, - onNext, - shouldSaveDraft: isEditing, - }); - - return ( - - - {translate('personalInfoStep.whatsYourLegalName')} - - - - - - ); -} - -FullNameStep.displayName = 'FullNameStep'; - -export default FullNameStep; diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx new file mode 100644 index 0000000000000..d15a8b6da0f7d --- /dev/null +++ b/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import {useOnyx} from 'react-native-onyx'; +import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; +import FullNameStep from '@components/FullNameStep'; +import useLocalize from '@hooks/useLocalize'; +import type {SubStepProps} from '@hooks/useSubStep/types'; +import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; +import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; + +const PERSONAL_INFO_STEP_KEY = INPUT_IDS.PERSONAL_INFO_STEP; +const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.FIRST_NAME, PERSONAL_INFO_STEP_KEY.LAST_NAME] as Array>; + +function LegalNameStep({onNext, onMove, isEditing}: SubStepProps) { + const {translate} = useLocalize(); + const [walletAdditionalDetails] = useOnyx(ONYXKEYS.WALLET_ADDITIONAL_DETAILS); + + const defaultValues = { + firstName: walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.FIRST_NAME] ?? '', + lastName: walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.LAST_NAME] ?? '', + }; + + const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ + fieldIds: STEP_FIELDS, + onNext, + shouldSaveDraft: isEditing, + }) as (values: FormOnyxValues) => void; + + return ( + + ); +} + +LegalNameStep.defaultName = 'LegalNameStep'; + +export default LegalNameStep; diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx index 9fbd70d095e8d..21b041f49bd48 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; From b47e29aab19707e610cce55e1735697a9a4168fa Mon Sep 17 00:00:00 2001 From: burczu Date: Tue, 15 Oct 2024 14:47:01 +0200 Subject: [PATCH 11/52] switched to use common full name step component in missing personal details page --- src/components/FullNameStep.tsx | 6 +- .../substeps/LegalName.tsx | 79 +++++++------------ 2 files changed, 32 insertions(+), 53 deletions(-) diff --git a/src/components/FullNameStep.tsx b/src/components/FullNameStep.tsx index d61658c5f4487..55b71fe29a245 100644 --- a/src/components/FullNameStep.tsx +++ b/src/components/FullNameStep.tsx @@ -40,9 +40,11 @@ type FullNameStepProps = SubStepProps & { firstName: string; lastName: string; }; + + shouldShowHelpLinks?: boolean; }; -function FullNameStep({formID, formTitle, customValidate, onSubmit, stepFields, firstNameInputID, lastNameInputID, defaultValues, isEditing}: FullNameStepProps) { +function FullNameStep({formID, formTitle, customValidate, onSubmit, stepFields, firstNameInputID, lastNameInputID, defaultValues, isEditing, shouldShowHelpLinks = true}: FullNameStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -93,7 +95,7 @@ function FullNameStep({formID, formTitle, customValidate, onSubmit, stepFields, shouldSaveDraft={!isEditing} containerStyles={[styles.mb6]} /> - + {shouldShowHelpLinks && } ); diff --git a/src/pages/MissingPersonalDetails/substeps/LegalName.tsx b/src/pages/MissingPersonalDetails/substeps/LegalName.tsx index 01d4f52dee7d4..9c5fdd9062232 100644 --- a/src/pages/MissingPersonalDetails/substeps/LegalName.tsx +++ b/src/pages/MissingPersonalDetails/substeps/LegalName.tsx @@ -1,25 +1,25 @@ import React, {useCallback} from 'react'; -import {View} from 'react-native'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import Text from '@components/Text'; -import TextInput from '@components/TextInput'; +import type {FormInputErrors, FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; +import FullNameStep from '@components/FullNameStep'; import useLocalize from '@hooks/useLocalize'; import usePersonalDetailsFormSubmit from '@hooks/usePersonalDetailsFormSubmit'; -import useThemeStyles from '@hooks/useThemeStyles'; -import * as ErrorUtils from '@libs/ErrorUtils'; -import * as ValidationUtils from '@libs/ValidationUtils'; import type {CustomSubStepProps} from '@pages/MissingPersonalDetails/types'; -import CONST from '@src/CONST'; +import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/PersonalDetailsForm'; +import * as ValidationUtils from "@libs/ValidationUtils"; +import CONST from "@src/CONST"; +import * as ErrorUtils from "@libs/ErrorUtils"; -const STEP_FIELDS = [INPUT_IDS.LEGAL_FIRST_NAME, INPUT_IDS.LEGAL_LAST_NAME]; +const STEP_FIELDS = [INPUT_IDS.LEGAL_FIRST_NAME, INPUT_IDS.LEGAL_LAST_NAME] as Array>; -function LegalNameStep({isEditing, onNext, personalDetailsValues}: CustomSubStepProps) { +function LegalName({isEditing, onNext, onMove, personalDetailsValues}: CustomSubStepProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); + + const defaultValues = { + firstName: personalDetailsValues[INPUT_IDS.LEGAL_FIRST_NAME], + lastName: personalDetailsValues[INPUT_IDS.LEGAL_LAST_NAME], + }; const validate = useCallback( (values: FormOnyxValues): FormInputErrors => { @@ -59,49 +59,26 @@ function LegalNameStep({isEditing, onNext, personalDetailsValues}: CustomSubStep fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: true, - }); + }) as (values: FormOnyxValues) => void; return ( - - - {translate('privatePersonalDetails.enterLegalName')} - - - - - - - - + customValidate={validate as (values: FormOnyxValues) => Partial>} + stepFields={STEP_FIELDS} + firstNameInputID={INPUT_IDS.LEGAL_FIRST_NAME as keyof FormOnyxValues} + lastNameInputID={INPUT_IDS.LEGAL_LAST_NAME as keyof FormOnyxValues} + defaultValues={defaultValues} + shouldShowHelpLinks={false} + /> ); } -LegalNameStep.displayName = 'LegalNameStep'; +LegalName.defaultName = 'LegalName'; -export default LegalNameStep; +export default LegalName; From b262e8e55420f33f8323eab0054473c128682ffb Mon Sep 17 00:00:00 2001 From: burczu Date: Tue, 15 Oct 2024 14:48:19 +0200 Subject: [PATCH 12/52] switched to use common full name step component in beneficial owners info --- .../LegalNameUBO.tsx | 70 ++++++------------- .../BeneficialOwnersStep.tsx | 6 -- 2 files changed, 23 insertions(+), 53 deletions(-) diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/LegalNameUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/LegalNameUBO.tsx index b17bf641eca5b..d3528eadb42c7 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/LegalNameUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/LegalNameUBO.tsx @@ -1,18 +1,14 @@ import React from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import Text from '@components/Text'; -import TextInput from '@components/TextInput'; +import type {OnyxEntry} from 'react-native-onyx'; +import type {FormOnyxValues} from '@components/Form/types'; +import FullNameStep from '@components/FullNameStep'; import useLocalize from '@hooks/useLocalize'; import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; -import * as ValidationUtils from '@libs/ValidationUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; import type {ReimbursementAccountForm} from '@src/types/form'; const {FIRST_NAME, LAST_NAME} = CONST.BANK_ACCOUNT.BENEFICIAL_OWNER_INFO_STEP.BENEFICIAL_OWNER_DATA; @@ -24,60 +20,40 @@ type LegalNameUBOOnyxProps = { }; type LegalNameUBOProps = SubStepProps & LegalNameUBOOnyxProps & {beneficialOwnerBeingModifiedID: string}; -function LegalNameUBO({reimbursementAccountDraft, onNext, isEditing, beneficialOwnerBeingModifiedID}: LegalNameUBOProps) { +function LegalNameUBO({reimbursementAccountDraft, onNext, onMove, isEditing, beneficialOwnerBeingModifiedID}: LegalNameUBOProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); - const firstNameInputID = `${BENEFICIAL_OWNER_PREFIX}_${beneficialOwnerBeingModifiedID}_${FIRST_NAME}` as const; - const lastNameInputID = `${BENEFICIAL_OWNER_PREFIX}_${beneficialOwnerBeingModifiedID}_${LAST_NAME}` as const; + const firstNameInputID = `${BENEFICIAL_OWNER_PREFIX}_${beneficialOwnerBeingModifiedID}_${FIRST_NAME}` as keyof FormOnyxValues; + const lastNameInputID = `${BENEFICIAL_OWNER_PREFIX}_${beneficialOwnerBeingModifiedID}_${LAST_NAME}` as keyof FormOnyxValues; const stepFields = [firstNameInputID, lastNameInputID]; - const defaultFirstName = reimbursementAccountDraft?.[firstNameInputID] ?? ''; - const defaultLastName = reimbursementAccountDraft?.[lastNameInputID] ?? ''; - - const validate = (values: FormOnyxValues): FormInputErrors => - ValidationUtils.getFieldRequiredErrors(values, stepFields); + const defaultValues = { + firstName: reimbursementAccountDraft?.[firstNameInputID] ?? '', + lastName: reimbursementAccountDraft?.[lastNameInputID] ?? '', + }; const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: stepFields, onNext, shouldSaveDraft: isEditing, - }); + }) as (values: FormOnyxValues) => void; return ( - - {translate('beneficialOwnerInfoStep.enterLegalFirstAndLastName')} - - - + stepFields={stepFields} + firstNameInputID={firstNameInputID} + lastNameInputID={lastNameInputID} + defaultValues={defaultValues} + /> ); } -LegalNameUBO.displayName = 'LegalNameUBO'; +LegalNameUBO.defaultName = 'LegalNameUBO'; export default withOnyx({ reimbursementAccountDraft: { diff --git a/src/pages/ReimbursementAccount/BeneficialOwnersStep.tsx b/src/pages/ReimbursementAccount/BeneficialOwnersStep.tsx index 0e67a5f84c104..57d5f76cf8aad 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnersStep.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnersStep.tsx @@ -1,16 +1,11 @@ import {Str} from 'expensify-common'; import React, {useState} from 'react'; -import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; -import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useSubStep from '@hooks/useSubStep'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; import * as BankAccounts from '@userActions/BankAccounts'; import * as FormActions from '@userActions/FormActions'; import CONST from '@src/CONST'; @@ -46,7 +41,6 @@ const bodyContent: Array> = [Le function BeneficialOwnersStep({reimbursementAccount, reimbursementAccountDraft, onBackButtonPress}: BeneficialOwnersStepProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); const companyName = reimbursementAccount?.achData?.companyName ?? ''; const policyID = reimbursementAccount?.achData?.policyID ?? '-1'; const defaultValues = { From ac3fdbed9bd8340da022928500dc87fffb28df49 Mon Sep 17 00:00:00 2001 From: burczu Date: Wed, 16 Oct 2024 11:27:59 +0200 Subject: [PATCH 13/52] common full name step moved to dedicated folder --- src/components/{ => SubStepForms}/FullNameStep.tsx | 10 +++++----- .../PersonalInfo/substeps/LegalNameStep.tsx | 2 +- .../MissingPersonalDetails/substeps/LegalName.tsx | 2 +- .../LegalNameUBO.tsx | 2 +- .../PersonalInfo/substeps/FullName.tsx | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) rename src/components/{ => SubStepForms}/FullNameStep.tsx (94%) diff --git a/src/components/FullNameStep.tsx b/src/components/SubStepForms/FullNameStep.tsx similarity index 94% rename from src/components/FullNameStep.tsx rename to src/components/SubStepForms/FullNameStep.tsx index 55b71fe29a245..80ab10bf553f6 100644 --- a/src/components/FullNameStep.tsx +++ b/src/components/SubStepForms/FullNameStep.tsx @@ -7,11 +7,11 @@ import * as ValidationUtils from '@libs/ValidationUtils'; import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; -import FormProvider from './Form/FormProvider'; -import InputWrapper from './Form/InputWrapper'; -import type {FormOnyxKeys, FormOnyxValues} from './Form/types'; -import Text from './Text'; -import TextInput from './TextInput'; +import FormProvider from '../Form/FormProvider'; +import InputWrapper from '../Form/InputWrapper'; +import type {FormOnyxKeys, FormOnyxValues} from '../Form/types'; +import Text from '../Text'; +import TextInput from '../TextInput'; type FullNameStepProps = SubStepProps & { /** The ID of the form */ diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx index d15a8b6da0f7d..10c3d5e9d4d4b 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {useOnyx} from 'react-native-onyx'; import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; -import FullNameStep from '@components/FullNameStep'; +import FullNameStep from '@components/SubStepForms/FullNameStep'; import useLocalize from '@hooks/useLocalize'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; diff --git a/src/pages/MissingPersonalDetails/substeps/LegalName.tsx b/src/pages/MissingPersonalDetails/substeps/LegalName.tsx index 9c5fdd9062232..5fd7a88290d31 100644 --- a/src/pages/MissingPersonalDetails/substeps/LegalName.tsx +++ b/src/pages/MissingPersonalDetails/substeps/LegalName.tsx @@ -1,6 +1,6 @@ import React, {useCallback} from 'react'; import type {FormInputErrors, FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; -import FullNameStep from '@components/FullNameStep'; +import FullNameStep from '@components/SubStepForms/FullNameStep'; import useLocalize from '@hooks/useLocalize'; import usePersonalDetailsFormSubmit from '@hooks/usePersonalDetailsFormSubmit'; import type {CustomSubStepProps} from '@pages/MissingPersonalDetails/types'; diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/LegalNameUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/LegalNameUBO.tsx index d3528eadb42c7..e8d444e23fd96 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/LegalNameUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/LegalNameUBO.tsx @@ -2,7 +2,7 @@ import React from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import type {FormOnyxValues} from '@components/Form/types'; -import FullNameStep from '@components/FullNameStep'; +import FullNameStep from '@components/SubStepForms/FullNameStep'; import useLocalize from '@hooks/useLocalize'; import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx index 21b041f49bd48..34459187630f2 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx @@ -2,7 +2,7 @@ import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; -import FullNameStep from '@components/FullNameStep'; +import FullNameStep from '@components/SubStepForms/FullNameStep'; import useLocalize from '@hooks/useLocalize'; import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; From 6377b87b4a678b360a0a74f391c9b9a4439dd4d8 Mon Sep 17 00:00:00 2001 From: burczu Date: Wed, 16 Oct 2024 11:37:25 +0200 Subject: [PATCH 14/52] common date of birth step component added --- .../SubStepForms/DateOfBirthStep.tsx | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/components/SubStepForms/DateOfBirthStep.tsx diff --git a/src/components/SubStepForms/DateOfBirthStep.tsx b/src/components/SubStepForms/DateOfBirthStep.tsx new file mode 100644 index 0000000000000..188c23bed7852 --- /dev/null +++ b/src/components/SubStepForms/DateOfBirthStep.tsx @@ -0,0 +1,92 @@ +import {subYears} from 'date-fns'; +import React, {useCallback} from 'react'; +import DatePicker from '@components/DatePicker'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import type {SubStepProps} from '@hooks/useSubStep/types'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as ValidationUtils from '@libs/ValidationUtils'; +import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; +import CONST from '@src/CONST'; +import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; + +type DateOfBirthStepProps = SubStepProps & { + /** The ID of the form */ + formID: keyof OnyxFormValuesMapping; + + /** The title of the form */ + formTitle: string; + + /** The validation function to call when the form is submitted */ + customValidate?: (values: FormOnyxValues) => Partial>; + + /** A function to call when the form is submitted */ + onSubmit: (values: FormOnyxValues) => void; + + /** Fields list of the form */ + stepFields: Array>; + + /** The ID of the date of birth input */ + dobInputID: keyof FormOnyxValues; + + /** The default value for the date of birth input */ + dobDefaultValue: string; +}; + +function DateOfBirthStep({formID, formTitle, customValidate, onSubmit, stepFields, dobInputID, dobDefaultValue, isEditing}: DateOfBirthStepProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const minDate = subYears(new Date(), CONST.DATE_BIRTH.MAX_AGE); + const maxDate = subYears(new Date(), CONST.DATE_BIRTH.MIN_AGE_FOR_PAYMENT); + + const validate = useCallback( + (values: FormOnyxValues): Partial> => { + const errors = ValidationUtils.getFieldRequiredErrors(values, stepFields); + + if (values[dobInputID]) { + if (!ValidationUtils.isValidPastDate(values[dobInputID]) || !ValidationUtils.meetsMaximumAgeRequirement(values[dobInputID])) { + // @ts-expect-error type mismatch to be fixed + errors[dobInputID] = translate('bankAccount.error.dob'); + } else if (!ValidationUtils.meetsMinimumAgeRequirement(values[dobInputID])) { + // @ts-expect-error type mismatch to be fixed + errors[dobInputID] = translate('bankAccount.error.age'); + } + } + + return errors; + }, + [dobInputID, stepFields, translate], + ); + + return ( + + {formTitle} + + + + ); +} + +DateOfBirthStep.defaultName = 'DateOfBirthStep'; + +export default DateOfBirthStep; From bae4112f2f8401a20f469ff215ca19c6f8378f69 Mon Sep 17 00:00:00 2001 From: burczu Date: Wed, 16 Oct 2024 12:49:17 +0200 Subject: [PATCH 15/52] common date of birth step used in the enable payments page --- .../PersonalInfo/substeps/DateOfBirthStep.tsx | 72 +++++-------------- 1 file changed, 17 insertions(+), 55 deletions(-) diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx index d476fdcc5c863..6c91715e8eb91 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx @@ -1,82 +1,44 @@ -import {subYears} from 'date-fns'; -import React, {useCallback} from 'react'; +import React from 'react'; import {useOnyx} from 'react-native-onyx'; -import DatePicker from '@components/DatePicker'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import Text from '@components/Text'; +import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; +import CommonDateOfBirthStep from '@components/SubStepForms/DateOfBirthStep'; import useLocalize from '@hooks/useLocalize'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; -import * as ValidationUtils from '@libs/ValidationUtils'; -import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; const PERSONAL_INFO_DOB_KEY = INPUT_IDS.PERSONAL_INFO_STEP.DOB; -const STEP_FIELDS = [PERSONAL_INFO_DOB_KEY]; +const STEP_FIELDS = [PERSONAL_INFO_DOB_KEY] as Array>; -const minDate = subYears(new Date(), CONST.DATE_BIRTH.MAX_AGE); -const maxDate = subYears(new Date(), CONST.DATE_BIRTH.MIN_AGE_FOR_PAYMENT); - -function DateOfBirthStep({onNext, isEditing}: SubStepProps) { +function DateOfBirthStep({onNext, onMove, isEditing}: SubStepProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); - - const validate = useCallback( - (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - - if (values.dob) { - if (!ValidationUtils.isValidPastDate(values.dob) || !ValidationUtils.meetsMaximumAgeRequirement(values.dob)) { - errors.dob = translate('bankAccount.error.dob'); - } else if (!ValidationUtils.meetsMinimumAgeRequirement(values.dob)) { - errors.dob = translate('bankAccount.error.age'); - } - } - - return errors; - }, - [translate], - ); const [walletAdditionalDetails] = useOnyx(ONYXKEYS.WALLET_ADDITIONAL_DETAILS); - const dobDefaultValue = walletAdditionalDetails?.[PERSONAL_INFO_DOB_KEY] ?? walletAdditionalDetails?.[PERSONAL_INFO_DOB_KEY] ?? ''; const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: isEditing, - }); + }) as (values: FormOnyxValues) => void; return ( - - {translate('personalInfoStep.whatsYourDOB')} - - - + stepFields={STEP_FIELDS} + dobInputID={PERSONAL_INFO_DOB_KEY as keyof FormOnyxValues} + dobDefaultValue={dobDefaultValue} + /> ); } -DateOfBirthStep.displayName = 'DateOfBirthStep'; +DateOfBirthStep.defaultName = 'DateOfBirthStep'; export default DateOfBirthStep; From 544754cc4f0bee839297693f136ccad4c6de4bff Mon Sep 17 00:00:00 2001 From: burczu Date: Wed, 16 Oct 2024 13:12:50 +0200 Subject: [PATCH 16/52] common date of birth step component used in missing personal details page --- .../substeps/DateOfBirth.tsx | 71 +++++-------------- 1 file changed, 18 insertions(+), 53 deletions(-) diff --git a/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx b/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx index f9b54c1b4758d..a31146a0d13b0 100644 --- a/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx +++ b/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx @@ -1,74 +1,39 @@ -import {subYears} from 'date-fns'; -import React, {useCallback} from 'react'; -import {View} from 'react-native'; -import DatePicker from '@components/DatePicker'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import Text from '@components/Text'; +import React from 'react'; +import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; +import DateOfBirthStep from '@components/SubStepForms/DateOfBirthStep'; import useLocalize from '@hooks/useLocalize'; import usePersonalDetailsFormSubmit from '@hooks/usePersonalDetailsFormSubmit'; -import useThemeStyles from '@hooks/useThemeStyles'; -import * as ValidationUtils from '@libs/ValidationUtils'; import type {CustomSubStepProps} from '@pages/MissingPersonalDetails/types'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/PersonalDetailsForm'; -const STEP_FIELDS = [INPUT_IDS.DATE_OF_BIRTH]; +const STEP_FIELDS = [INPUT_IDS.DATE_OF_BIRTH] as Array>; -function DateOfBirthStep({isEditing, onNext, personalDetailsValues}: CustomSubStepProps) { +function DateOfBirth({isEditing, onNext, onMove, personalDetailsValues}: CustomSubStepProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); - - const minDate = subYears(new Date(), CONST.DATE_BIRTH.MAX_AGE); - const maxDate = subYears(new Date(), CONST.DATE_BIRTH.MIN_AGE_FOR_PAYMENT); const handleSubmit = usePersonalDetailsFormSubmit({ fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: true, - }); - - const validate = useCallback( - (values: FormOnyxValues): FormInputErrors => { - const errors: FormInputErrors = {}; - if (!ValidationUtils.isRequiredFulfilled(values[INPUT_IDS.DATE_OF_BIRTH])) { - errors[INPUT_IDS.DATE_OF_BIRTH] = translate('common.error.fieldRequired'); - } else if (!ValidationUtils.isValidPastDate(values[INPUT_IDS.DATE_OF_BIRTH]) || !ValidationUtils.meetsMaximumAgeRequirement(values[INPUT_IDS.DATE_OF_BIRTH])) { - errors[INPUT_IDS.DATE_OF_BIRTH] = translate('bankAccount.error.dob'); - } - return errors; - }, - [translate], - ); + }) as (values: FormOnyxValues) => void; return ( - - - {translate('privatePersonalDetails.enterDateOfBirth')} - - - + stepFields={STEP_FIELDS} + dobInputID={INPUT_IDS.DATE_OF_BIRTH as keyof FormOnyxValues} + dobDefaultValue={personalDetailsValues[INPUT_IDS.DATE_OF_BIRTH]} + /> ); } -DateOfBirthStep.displayName = 'DateOfBirthStep'; +DateOfBirth.defaultName = 'DateOfBirth'; -export default DateOfBirthStep; +export default DateOfBirth; From 77fe4453387ba5de2164fa43ebca6c02acae91d0 Mon Sep 17 00:00:00 2001 From: burczu Date: Wed, 16 Oct 2024 14:12:49 +0200 Subject: [PATCH 17/52] common date of birth step component added to reimbursement account --- .../PersonalInfo/substeps/DateOfBirth.tsx | 74 +++++-------------- 1 file changed, 18 insertions(+), 56 deletions(-) diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx index 8c68380d6e55d..efeab25b146b7 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx @@ -1,20 +1,13 @@ -import {subYears} from 'date-fns'; -import React, {useCallback} from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; +import React from 'react'; import {withOnyx} from 'react-native-onyx'; -import DatePicker from '@components/DatePicker'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import Text from '@components/Text'; +import type {OnyxEntry} from 'react-native-onyx'; +import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; +import DateOfBirthStep from '@components/SubStepForms/DateOfBirthStep'; import useLocalize from '@hooks/useLocalize'; import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; -import * as ValidationUtils from '@libs/ValidationUtils'; -import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; import type {ReimbursementAccountForm} from '@src/types/form'; import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; import type {ReimbursementAccount} from '@src/types/onyx'; @@ -30,66 +23,35 @@ type DateOfBirthOnyxProps = { type DateOfBirthProps = DateOfBirthOnyxProps & SubStepProps; const PERSONAL_INFO_DOB_KEY = INPUT_IDS.PERSONAL_INFO_STEP.DOB; -const STEP_FIELDS = [PERSONAL_INFO_DOB_KEY]; +const STEP_FIELDS = [PERSONAL_INFO_DOB_KEY] as Array>; -function DateOfBirth({reimbursementAccount, reimbursementAccountDraft, onNext, isEditing}: DateOfBirthProps) { +function DateOfBirth({reimbursementAccount, reimbursementAccountDraft, onNext, onMove, isEditing}: DateOfBirthProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); - - const validate = useCallback( - (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - - if (values.dob) { - if (!ValidationUtils.isValidPastDate(values.dob) || !ValidationUtils.meetsMaximumAgeRequirement(values.dob)) { - errors.dob = translate('bankAccount.error.dob'); - } else if (!ValidationUtils.meetsMinimumAgeRequirement(values.dob)) { - errors.dob = translate('bankAccount.error.age'); - } - } - - return errors; - }, - [translate], - ); const dobDefaultValue = reimbursementAccount?.achData?.[PERSONAL_INFO_DOB_KEY] ?? reimbursementAccountDraft?.[PERSONAL_INFO_DOB_KEY] ?? ''; - const minDate = subYears(new Date(), CONST.DATE_BIRTH.MAX_AGE); - const maxDate = subYears(new Date(), CONST.DATE_BIRTH.MIN_AGE_FOR_PAYMENT); - const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: isEditing, - }); + }) as (values: FormOnyxValues) => void; return ( - - {translate('personalInfoStep.enterYourDateOfBirth')} - - - + stepFields={STEP_FIELDS} + dobInputID={PERSONAL_INFO_DOB_KEY as keyof FormOnyxValues} + dobDefaultValue={dobDefaultValue} + /> ); } -DateOfBirth.displayName = 'DateOfBirth'; +DateOfBirth.defaultName = 'DateOfBirth'; export default withOnyx({ // @ts-expect-error: ONYXKEYS.REIMBURSEMENT_ACCOUNT is conflicting with ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM From 23e579f967a7439eaa02e88975cf9d85b827404c Mon Sep 17 00:00:00 2001 From: burczu Date: Wed, 16 Oct 2024 14:25:41 +0200 Subject: [PATCH 18/52] common date of birth component used in beneficial owner page --- .../DateOfBirthUBO.tsx | 63 +++++-------------- 1 file changed, 15 insertions(+), 48 deletions(-) diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx index b5a4a6a94bed2..4d1bd17d83e75 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx @@ -1,19 +1,14 @@ -import {subYears} from 'date-fns'; import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import DatePicker from '@components/DatePicker'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import Text from '@components/Text'; +import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; +import DateOfBirthStep from '@components/SubStepForms/DateOfBirthStep'; import useLocalize from '@hooks/useLocalize'; import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; -import * as ValidationUtils from '@libs/ValidationUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; import type {ReimbursementAccountForm} from '@src/types/form'; const DOB = CONST.BANK_ACCOUNT.BENEFICIAL_OWNER_INFO_STEP.BENEFICIAL_OWNER_DATA.DOB; @@ -25,62 +20,34 @@ type DateOfBirthUBOOnyxProps = { }; type DateOfBirthUBOProps = SubStepProps & DateOfBirthUBOOnyxProps & {beneficialOwnerBeingModifiedID: string}; -function DateOfBirthUBO({reimbursementAccountDraft, onNext, isEditing, beneficialOwnerBeingModifiedID}: DateOfBirthUBOProps) { +function DateOfBirthUBO({reimbursementAccountDraft, onNext, onMove, isEditing, beneficialOwnerBeingModifiedID}: DateOfBirthUBOProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); const dobInputID = `${BENEFICIAL_OWNER_PREFIX}_${beneficialOwnerBeingModifiedID}_${DOB}` as const; const dobDefaultValue = reimbursementAccountDraft?.[dobInputID] ?? ''; - const minDate = subYears(new Date(), CONST.DATE_BIRTH.MAX_AGE); - const maxDate = subYears(new Date(), CONST.DATE_BIRTH.MIN_AGE_FOR_PAYMENT); - - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, [dobInputID]); - - if (values[dobInputID]) { - if (!ValidationUtils.isValidPastDate(values[dobInputID]) || !ValidationUtils.meetsMaximumAgeRequirement(values[dobInputID])) { - errors[dobInputID] = translate('bankAccount.error.dob'); - } else if (!ValidationUtils.meetsMinimumAgeRequirement(values[dobInputID])) { - errors[dobInputID] = translate('bankAccount.error.age'); - } - } - - return errors; - }; - const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: [dobInputID], onNext, shouldSaveDraft: isEditing, - }); + }) as (values: FormOnyxValues) => void; return ( - - {translate('beneficialOwnerInfoStep.enterTheDateOfBirthOfTheOwner')} - - + stepFields={[dobInputID] as Array>} + dobInputID={dobInputID as keyof FormOnyxValues} + dobDefaultValue={dobDefaultValue} + /> ); } -DateOfBirthUBO.displayName = 'DateOfBirthUBO'; +DateOfBirthUBO.defaultName = 'DateOfBirthUBO'; export default withOnyx({ reimbursementAccountDraft: { From bba14053a3b22496c2f00d62c0af730d8e361a11 Mon Sep 17 00:00:00 2001 From: burczu Date: Fri, 18 Oct 2024 11:15:34 +0200 Subject: [PATCH 19/52] common address step component created --- src/components/SubStepForms/AddressStep.tsx | 109 ++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 src/components/SubStepForms/AddressStep.tsx diff --git a/src/components/SubStepForms/AddressStep.tsx b/src/components/SubStepForms/AddressStep.tsx new file mode 100644 index 0000000000000..1ad1f2794078e --- /dev/null +++ b/src/components/SubStepForms/AddressStep.tsx @@ -0,0 +1,109 @@ +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import FormProvider from '@components/Form/FormProvider'; +import type {FormInputErrors, FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import type {SubStepProps} from '@hooks/useSubStep/types'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as ValidationUtils from '@libs/ValidationUtils'; +import AddressFormFields from '@pages/ReimbursementAccount/AddressFormFields'; +import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; +import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; + +type AddressValues = { + street: string; + city: string; + state: string; + zipCode: string; +}; + +type AddressStepProps = SubStepProps & { + /** The ID of the form */ + formID: TFormID; + + /** The title of the form */ + formTitle: string; + + /** The disclaimer informing that PO box is not allowed */ + formPOBoxDisclaimer?: string; + + /** The validation function to call when the form is submitted */ + customValidate?: (values: FormOnyxValues) => FormInputErrors; + + /** A function to call when the form is submitted */ + onSubmit: (values: FormOnyxValues) => void; + + /** Fields list of the form */ + stepFields: Array>; + + /* The IDs of the input fields */ + inputFieldsIDs: AddressValues; + + /** The default values for the form */ + defaultValues: AddressValues; + + /** Should show help links */ + shouldShowHelpLinks?: boolean; +}; + +function AddressStep({ + formID, + formTitle, + formPOBoxDisclaimer, + customValidate, + onSubmit, + stepFields, + inputFieldsIDs, + defaultValues, + shouldShowHelpLinks, + isEditing, +}: AddressStepProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, stepFields); + + if (values[inputFieldsIDs.street] && !ValidationUtils.isValidAddress(values[inputFieldsIDs.street])) { + // @ts-expect-error type mismatch to be fixed + errors[inputFieldsIDs.street] = translate('bankAccount.error.addressStreet'); + } + + if (values[inputFieldsIDs.zipCode] && !ValidationUtils.isValidZipCode(values[inputFieldsIDs.zipCode])) { + // @ts-expect-error type mismatch to be fixed + errors[inputFieldsIDs.street] = translate('bankAccount.error.zipCode'); + } + + return errors; + }, + [inputFieldsIDs.street, inputFieldsIDs.zipCode, stepFields, translate], + ); + + return ( + + + {formTitle} + {formPOBoxDisclaimer && {formPOBoxDisclaimer}} + + {shouldShowHelpLinks && } + + + ); +} + +AddressStep.displayName = 'AddressStep'; + +export default AddressStep; From 9ecd3e18942dce6834d29fd3ab9b355315cbac08 Mon Sep 17 00:00:00 2001 From: burczu Date: Fri, 18 Oct 2024 14:49:24 +0200 Subject: [PATCH 20/52] common address step component typings fixed and improved --- src/components/SubStepForms/AddressStep.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/SubStepForms/AddressStep.tsx b/src/components/SubStepForms/AddressStep.tsx index 1ad1f2794078e..d32c4afa9a6e8 100644 --- a/src/components/SubStepForms/AddressStep.tsx +++ b/src/components/SubStepForms/AddressStep.tsx @@ -1,7 +1,7 @@ import React, {useCallback} from 'react'; import {View} from 'react-native'; import FormProvider from '@components/Form/FormProvider'; -import type {FormInputErrors, FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; +import type {FormInputErrors, FormOnyxKeys, FormOnyxValues, FormValue} from '@components/Form/types'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import type {SubStepProps} from '@hooks/useSubStep/types'; @@ -18,7 +18,7 @@ type AddressValues = { zipCode: string; }; -type AddressStepProps = SubStepProps & { +type AddressStepProps = SubStepProps & { /** The ID of the form */ formID: TFormID; @@ -35,7 +35,7 @@ type AddressStepProps = SubStepProps & { onSubmit: (values: FormOnyxValues) => void; /** Fields list of the form */ - stepFields: Array>; + stepFields: Array>; /* The IDs of the input fields */ inputFieldsIDs: AddressValues; @@ -47,7 +47,7 @@ type AddressStepProps = SubStepProps & { shouldShowHelpLinks?: boolean; }; -function AddressStep({ +function AddressStep({ formID, formTitle, formPOBoxDisclaimer, @@ -66,12 +66,14 @@ function AddressStep({ (values: FormOnyxValues): FormInputErrors => { const errors = ValidationUtils.getFieldRequiredErrors(values, stepFields); - if (values[inputFieldsIDs.street] && !ValidationUtils.isValidAddress(values[inputFieldsIDs.street])) { + const street = values[inputFieldsIDs.street as keyof typeof values]; + if (street && !ValidationUtils.isValidAddress(street as FormValue)) { // @ts-expect-error type mismatch to be fixed errors[inputFieldsIDs.street] = translate('bankAccount.error.addressStreet'); } - if (values[inputFieldsIDs.zipCode] && !ValidationUtils.isValidZipCode(values[inputFieldsIDs.zipCode])) { + const zipCode = values[inputFieldsIDs.zipCode as keyof typeof values]; + if (zipCode && !ValidationUtils.isValidZipCode(zipCode as string)) { // @ts-expect-error type mismatch to be fixed errors[inputFieldsIDs.street] = translate('bankAccount.error.zipCode'); } From 29e2b09f597579262f9e8d774eb5bb959f413d8a Mon Sep 17 00:00:00 2001 From: burczu Date: Sun, 20 Oct 2024 21:23:55 +0200 Subject: [PATCH 21/52] common address step component used in beneficial owner page --- .../AddressUBO.tsx | 54 ++++++------------- 1 file changed, 15 insertions(+), 39 deletions(-) diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/AddressUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/AddressUBO.tsx index 1b03432b7f3e5..5612f5cdbcde8 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/AddressUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/AddressUBO.tsx @@ -1,15 +1,10 @@ import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import FormProvider from '@components/Form/FormProvider'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import Text from '@components/Text'; +import AddressStep from '@components/SubStepForms/AddressStep'; import useLocalize from '@hooks/useLocalize'; import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; -import * as ValidationUtils from '@libs/ValidationUtils'; -import AddressFormFields from '@pages/ReimbursementAccount/AddressFormFields'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ReimbursementAccountForm} from '@src/types/form'; @@ -21,11 +16,11 @@ type AddressUBOOnyxProps = { /** The draft values of the bank account being setup */ reimbursementAccountDraft: OnyxEntry; }; + type AddressUBOProps = SubStepProps & AddressUBOOnyxProps & {beneficialOwnerBeingModifiedID: string}; -function AddressUBO({reimbursementAccountDraft, onNext, isEditing, beneficialOwnerBeingModifiedID}: AddressUBOProps) { +function AddressUBO({reimbursementAccountDraft, onNext, onMove, isEditing, beneficialOwnerBeingModifiedID}: AddressUBOProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); const inputKeys = { street: `${BENEFICIAL_OWNER_PREFIX}_${beneficialOwnerBeingModifiedID}_${BENEFICIAL_OWNER_INFO_KEY.STREET}`, @@ -34,8 +29,6 @@ function AddressUBO({reimbursementAccountDraft, onNext, isEditing, beneficialOwn zipCode: `${BENEFICIAL_OWNER_PREFIX}_${beneficialOwnerBeingModifiedID}_${BENEFICIAL_OWNER_INFO_KEY.ZIP_CODE}`, } as const; - const stepFields = [inputKeys.street, inputKeys.city, inputKeys.state, inputKeys.zipCode]; - const defaultValues = { street: reimbursementAccountDraft?.[inputKeys.street] ?? '', city: reimbursementAccountDraft?.[inputKeys.city] ?? '', @@ -43,19 +36,7 @@ function AddressUBO({reimbursementAccountDraft, onNext, isEditing, beneficialOwn zipCode: reimbursementAccountDraft?.[inputKeys.zipCode] ?? '', }; - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, stepFields); - - if (values[inputKeys.street] && !ValidationUtils.isValidAddress(values[inputKeys.street])) { - errors[inputKeys.street] = translate('bankAccount.error.addressStreet'); - } - - if (values[inputKeys.zipCode] && !ValidationUtils.isValidZipCode(values[inputKeys.zipCode])) { - errors[inputKeys.zipCode] = translate('bankAccount.error.zipCode'); - } - - return errors; - }; + const stepFields = [inputKeys.street, inputKeys.city, inputKeys.state, inputKeys.zipCode]; const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: stepFields, @@ -64,27 +45,22 @@ function AddressUBO({reimbursementAccountDraft, onNext, isEditing, beneficialOwn }); return ( - + isEditing={isEditing} + onNext={onNext} + onMove={onMove} formID={ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM} - submitButtonText={translate(isEditing ? 'common.confirm' : 'common.next')} - validate={validate} + formTitle={translate('beneficialOwnerInfoStep.enterTheOwnersAddress')} + formPOBoxDisclaimer={translate('common.noPO')} onSubmit={handleSubmit} - submitButtonStyles={[styles.mb0]} - style={[styles.mh5, styles.flexGrow1]} - > - {translate('beneficialOwnerInfoStep.enterTheOwnersAddress')} - {translate('common.noPO')} - - + stepFields={stepFields} + inputFieldsIDs={inputKeys} + defaultValues={defaultValues} + /> ); } -AddressUBO.displayName = 'AddressUBO'; +AddressUBO.defaultName = 'AddressUBO'; export default withOnyx({ reimbursementAccountDraft: { From f3aa42c1af4835a497839f96a7de22a82decd5e7 Mon Sep 17 00:00:00 2001 From: burczu Date: Sun, 20 Oct 2024 21:32:10 +0200 Subject: [PATCH 22/52] common address step component used in enable payments page --- .../PersonalInfo/substeps/AddressStep.tsx | 60 +++++-------------- 1 file changed, 14 insertions(+), 46 deletions(-) diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx index 0f9819f31c669..3364e625f3bf6 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx @@ -1,16 +1,9 @@ -import React, {useCallback} from 'react'; -import {View} from 'react-native'; +import React from 'react'; import {useOnyx} from 'react-native-onyx'; -import FormProvider from '@components/Form/FormProvider'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import Text from '@components/Text'; +import CommonAddressStep from '@components/SubStepForms/AddressStep'; import useLocalize from '@hooks/useLocalize'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; -import * as ValidationUtils from '@libs/ValidationUtils'; -import AddressFormFields from '@pages/ReimbursementAccount/AddressFormFields'; -import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; @@ -25,9 +18,8 @@ const INPUT_KEYS = { const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.STREET, PERSONAL_INFO_STEP_KEY.CITY, PERSONAL_INFO_STEP_KEY.STATE, PERSONAL_INFO_STEP_KEY.ZIP_CODE]; -function AddressStep({onNext, isEditing}: SubStepProps) { +function AddressStep({onNext, onMove, isEditing}: SubStepProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); const [walletAdditionalDetails] = useOnyx(ONYXKEYS.WALLET_ADDITIONAL_DETAILS); @@ -38,23 +30,6 @@ function AddressStep({onNext, isEditing}: SubStepProps) { zipCode: walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.ZIP_CODE] ?? '', }; - const validate = useCallback( - (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - - if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) { - errors.addressStreet = translate('bankAccount.error.addressStreet'); - } - - if (values.addressZipCode && !ValidationUtils.isValidZipCode(values.addressZipCode)) { - errors.addressZipCode = translate('bankAccount.error.zipCode'); - } - - return errors; - }, - [translate], - ); - const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, @@ -62,28 +37,21 @@ function AddressStep({onNext, isEditing}: SubStepProps) { }); return ( - + isEditing={isEditing} + onNext={onNext} + onMove={onMove} formID={ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS} - submitButtonText={translate(isEditing ? 'common.confirm' : 'common.next')} - validate={validate} + formTitle={translate('personalInfoStep.whatsYourAddress')} + formPOBoxDisclaimer={translate('personalInfoStep.noPOBoxesPlease')} onSubmit={handleSubmit} - style={[styles.mh5, styles.flexGrow1]} - > - - {translate('personalInfoStep.whatsYourAddress')} - {translate('personalInfoStep.noPOBoxesPlease')} - - - - + stepFields={STEP_FIELDS} + inputFieldsIDs={INPUT_KEYS} + defaultValues={defaultValues} + /> ); } -AddressStep.displayName = 'AddressStep'; +AddressStep.defaultName = 'AddressStep'; export default AddressStep; From d4cece160b4ca6bb37d46c9f2b5b9b631ffdbd72 Mon Sep 17 00:00:00 2001 From: burczu Date: Sun, 20 Oct 2024 21:37:37 +0200 Subject: [PATCH 23/52] common address step component used in personal info page --- .../PersonalInfo/substeps/Address.tsx | 63 +++++-------------- 1 file changed, 15 insertions(+), 48 deletions(-) diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/Address.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/Address.tsx index b37dd207ea37d..746b26082d5df 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/Address.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/Address.tsx @@ -1,17 +1,10 @@ -import React, {useCallback} from 'react'; -import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; +import React from 'react'; import {withOnyx} from 'react-native-onyx'; -import FormProvider from '@components/Form/FormProvider'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import Text from '@components/Text'; +import type {OnyxEntry} from 'react-native-onyx'; +import AddressStep from '@components/SubStepForms/AddressStep'; import useLocalize from '@hooks/useLocalize'; import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; -import * as ValidationUtils from '@libs/ValidationUtils'; -import AddressFormFields from '@pages/ReimbursementAccount/AddressFormFields'; -import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; import type {ReimbursementAccount} from '@src/types/onyx'; @@ -34,9 +27,8 @@ const INPUT_KEYS = { const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.STREET, PERSONAL_INFO_STEP_KEY.CITY, PERSONAL_INFO_STEP_KEY.STATE, PERSONAL_INFO_STEP_KEY.ZIP_CODE]; -function Address({reimbursementAccount, onNext, isEditing}: AddressProps) { +function Address({reimbursementAccount, onNext, onMove, isEditing}: AddressProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); const defaultValues = { street: reimbursementAccount?.achData?.[PERSONAL_INFO_STEP_KEY.STREET] ?? '', @@ -45,23 +37,6 @@ function Address({reimbursementAccount, onNext, isEditing}: AddressProps) { zipCode: reimbursementAccount?.achData?.[PERSONAL_INFO_STEP_KEY.ZIP_CODE] ?? '', }; - const validate = useCallback( - (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); - - if (values.requestorAddressStreet && !ValidationUtils.isValidAddress(values.requestorAddressStreet)) { - errors.requestorAddressStreet = translate('bankAccount.error.addressStreet'); - } - - if (values.requestorAddressZipCode && !ValidationUtils.isValidZipCode(values.requestorAddressZipCode)) { - errors.requestorAddressZipCode = translate('bankAccount.error.zipCode'); - } - - return errors; - }, - [translate], - ); - const handleSubmit = useReimbursementAccountStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, @@ -69,30 +44,22 @@ function Address({reimbursementAccount, onNext, isEditing}: AddressProps) { }); return ( - + isEditing={isEditing} + onNext={onNext} + onMove={onMove} formID={ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM} - submitButtonText={translate(isEditing ? 'common.confirm' : 'common.next')} - validate={validate} + formTitle={translate('personalInfoStep.enterYourAddress')} + formPOBoxDisclaimer={translate('common.noPO')} onSubmit={handleSubmit} - submitButtonStyles={[styles.mb0]} - style={[styles.mh5, styles.flexGrow1]} - > - - {translate('personalInfoStep.enterYourAddress')} - {translate('common.noPO')} - - - - + stepFields={STEP_FIELDS} + inputFieldsIDs={INPUT_KEYS} + defaultValues={defaultValues} + /> ); } -Address.displayName = 'Address'; +Address.defaultName = 'Address'; export default withOnyx({ // @ts-expect-error: ONYXKEYS.REIMBURSEMENT_ACCOUNT is conflicting with ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM From 3c283d503a3337c921a57b69bedce184fbf3b907 Mon Sep 17 00:00:00 2001 From: burczu Date: Sun, 20 Oct 2024 21:50:38 +0200 Subject: [PATCH 24/52] switched to generic form id typing for common full name comp usages --- src/components/SubStepForms/FullNameStep.tsx | 40 +++++++++++++------ .../PersonalInfo/substeps/LegalNameStep.tsx | 12 +++--- .../substeps/LegalName.tsx | 12 +++--- .../LegalNameUBO.tsx | 5 +-- .../PersonalInfo/substeps/FullName.tsx | 13 +++--- 5 files changed, 46 insertions(+), 36 deletions(-) diff --git a/src/components/SubStepForms/FullNameStep.tsx b/src/components/SubStepForms/FullNameStep.tsx index 80ab10bf553f6..e1048162847b0 100644 --- a/src/components/SubStepForms/FullNameStep.tsx +++ b/src/components/SubStepForms/FullNameStep.tsx @@ -9,31 +9,31 @@ import CONST from '@src/CONST'; import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; import FormProvider from '../Form/FormProvider'; import InputWrapper from '../Form/InputWrapper'; -import type {FormOnyxKeys, FormOnyxValues} from '../Form/types'; +import type {FormInputErrors, FormOnyxKeys, FormOnyxValues} from '../Form/types'; import Text from '../Text'; import TextInput from '../TextInput'; -type FullNameStepProps = SubStepProps & { +type FullNameStepProps = SubStepProps & { /** The ID of the form */ - formID: keyof OnyxFormValuesMapping; + formID: TFormID; /** The title of the form */ formTitle: string; /** The validation function to call when the form is submitted */ - customValidate?: (values: FormOnyxValues) => Partial>; + customValidate?: (values: FormOnyxValues) => FormInputErrors; /** A function to call when the form is submitted */ - onSubmit: (values: FormOnyxValues) => void; + onSubmit: (values: FormOnyxValues) => void; /** Fields list of the form */ - stepFields: Array>; + stepFields: Array>; /** The ID of the first name input */ - firstNameInputID: keyof FormOnyxValues; + firstNameInputID: string; /** The ID of the last name input */ - lastNameInputID: keyof FormOnyxValues; + lastNameInputID: string; /** The default values for the form */ defaultValues: { @@ -44,19 +44,33 @@ type FullNameStepProps = SubStepProps & { shouldShowHelpLinks?: boolean; }; -function FullNameStep({formID, formTitle, customValidate, onSubmit, stepFields, firstNameInputID, lastNameInputID, defaultValues, isEditing, shouldShowHelpLinks = true}: FullNameStepProps) { +function FullNameStep({ + formID, + formTitle, + customValidate, + onSubmit, + stepFields, + firstNameInputID, + lastNameInputID, + defaultValues, + isEditing, + shouldShowHelpLinks = true, +}: FullNameStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const validate = useCallback( - (values: FormOnyxValues): Partial> => { - const errors: Partial> = ValidationUtils.getFieldRequiredErrors(values, stepFields); - if (values[firstNameInputID] && !ValidationUtils.isValidLegalName(values[firstNameInputID])) { + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, stepFields); + + const firstName = values[firstNameInputID as keyof FormOnyxValues] as string; + if (firstName && !ValidationUtils.isValidLegalName(firstName)) { // @ts-expect-error type mismatch to be fixed errors[firstNameInputID] = translate('common.error.fieldRequired'); } - if (values[lastNameInputID] && !ValidationUtils.isValidLegalName(values[lastNameInputID])) { + const lastName = values[lastNameInputID as keyof FormOnyxValues] as string; + if (lastName && !ValidationUtils.isValidLegalName(lastName)) { // @ts-expect-error type mismatch to be fixed errors[lastNameInputID] = translate('common.error.fieldRequired'); } diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx index 10c3d5e9d4d4b..bdd146bd890d0 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx @@ -1,16 +1,14 @@ import React from 'react'; import {useOnyx} from 'react-native-onyx'; -import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; import FullNameStep from '@components/SubStepForms/FullNameStep'; import useLocalize from '@hooks/useLocalize'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; const PERSONAL_INFO_STEP_KEY = INPUT_IDS.PERSONAL_INFO_STEP; -const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.FIRST_NAME, PERSONAL_INFO_STEP_KEY.LAST_NAME] as Array>; +const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.FIRST_NAME, PERSONAL_INFO_STEP_KEY.LAST_NAME]; function LegalNameStep({onNext, onMove, isEditing}: SubStepProps) { const {translate} = useLocalize(); @@ -25,10 +23,10 @@ function LegalNameStep({onNext, onMove, isEditing}: SubStepProps) { fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: isEditing, - }) as (values: FormOnyxValues) => void; + }); return ( - isEditing={isEditing} onNext={onNext} onMove={onMove} @@ -36,8 +34,8 @@ function LegalNameStep({onNext, onMove, isEditing}: SubStepProps) { formTitle={translate('personalInfoStep.whatsYourLegalName')} onSubmit={handleSubmit} stepFields={STEP_FIELDS} - firstNameInputID={PERSONAL_INFO_STEP_KEY.FIRST_NAME as keyof FormOnyxValues} - lastNameInputID={PERSONAL_INFO_STEP_KEY.LAST_NAME as keyof FormOnyxValues} + firstNameInputID={PERSONAL_INFO_STEP_KEY.FIRST_NAME} + lastNameInputID={PERSONAL_INFO_STEP_KEY.LAST_NAME} defaultValues={defaultValues} /> ); diff --git a/src/pages/MissingPersonalDetails/substeps/LegalName.tsx b/src/pages/MissingPersonalDetails/substeps/LegalName.tsx index 5fd7a88290d31..7f098851fbcbb 100644 --- a/src/pages/MissingPersonalDetails/substeps/LegalName.tsx +++ b/src/pages/MissingPersonalDetails/substeps/LegalName.tsx @@ -11,7 +11,7 @@ import * as ValidationUtils from "@libs/ValidationUtils"; import CONST from "@src/CONST"; import * as ErrorUtils from "@libs/ErrorUtils"; -const STEP_FIELDS = [INPUT_IDS.LEGAL_FIRST_NAME, INPUT_IDS.LEGAL_LAST_NAME] as Array>; +const STEP_FIELDS = [INPUT_IDS.LEGAL_FIRST_NAME, INPUT_IDS.LEGAL_LAST_NAME]; function LegalName({isEditing, onNext, onMove, personalDetailsValues}: CustomSubStepProps) { const {translate} = useLocalize(); @@ -59,20 +59,20 @@ function LegalName({isEditing, onNext, onMove, personalDetailsValues}: CustomSub fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: true, - }) as (values: FormOnyxValues) => void; + }); return ( - isEditing={isEditing} onNext={onNext} onMove={onMove} formID={ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM} formTitle={translate('privatePersonalDetails.enterLegalName')} onSubmit={handleSubmit} - customValidate={validate as (values: FormOnyxValues) => Partial>} + customValidate={validate} stepFields={STEP_FIELDS} - firstNameInputID={INPUT_IDS.LEGAL_FIRST_NAME as keyof FormOnyxValues} - lastNameInputID={INPUT_IDS.LEGAL_LAST_NAME as keyof FormOnyxValues} + firstNameInputID={INPUT_IDS.LEGAL_FIRST_NAME} + lastNameInputID={INPUT_IDS.LEGAL_LAST_NAME} defaultValues={defaultValues} shouldShowHelpLinks={false} /> diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/LegalNameUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/LegalNameUBO.tsx index e8d444e23fd96..838ea6ac1a23b 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/LegalNameUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/LegalNameUBO.tsx @@ -8,7 +8,6 @@ import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccoun import type {SubStepProps} from '@hooks/useSubStep/types'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; import type {ReimbursementAccountForm} from '@src/types/form'; const {FIRST_NAME, LAST_NAME} = CONST.BANK_ACCOUNT.BENEFICIAL_OWNER_INFO_STEP.BENEFICIAL_OWNER_DATA; @@ -35,10 +34,10 @@ function LegalNameUBO({reimbursementAccountDraft, onNext, onMove, isEditing, ben fieldIds: stepFields, onNext, shouldSaveDraft: isEditing, - }) as (values: FormOnyxValues) => void; + }); return ( - isEditing={isEditing} onNext={onNext} onMove={onMove} diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx index 34459187630f2..1c212d8c78ee1 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx @@ -1,13 +1,12 @@ import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; +import type {FormOnyxValues} from '@components/Form/types'; import FullNameStep from '@components/SubStepForms/FullNameStep'; import useLocalize from '@hooks/useLocalize'; import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; import type {ReimbursementAccount} from '@src/types/onyx'; @@ -19,7 +18,7 @@ type FullNameOnyxProps = { type FullNameProps = FullNameOnyxProps & SubStepProps; const PERSONAL_INFO_STEP_KEY = INPUT_IDS.PERSONAL_INFO_STEP; -const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.FIRST_NAME, PERSONAL_INFO_STEP_KEY.LAST_NAME] as Array>; +const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.FIRST_NAME, PERSONAL_INFO_STEP_KEY.LAST_NAME]; function FullName({reimbursementAccount, onNext, onMove, isEditing}: FullNameProps) { const {translate} = useLocalize(); @@ -33,10 +32,10 @@ function FullName({reimbursementAccount, onNext, onMove, isEditing}: FullNamePro fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: isEditing, - }) as (values: FormOnyxValues) => void; + }); return ( - isEditing={isEditing} onNext={onNext} onMove={onMove} @@ -44,8 +43,8 @@ function FullName({reimbursementAccount, onNext, onMove, isEditing}: FullNamePro formTitle={translate('personalInfoStep.enterYourLegalFirstAndLast')} onSubmit={handleSubmit} stepFields={STEP_FIELDS} - firstNameInputID={PERSONAL_INFO_STEP_KEY.FIRST_NAME as keyof FormOnyxValues} - lastNameInputID={PERSONAL_INFO_STEP_KEY.LAST_NAME as keyof FormOnyxValues} + firstNameInputID={PERSONAL_INFO_STEP_KEY.FIRST_NAME} + lastNameInputID={PERSONAL_INFO_STEP_KEY.LAST_NAME} defaultValues={defaultValues} /> ); From 3d1e3ba9ec2e9ea2d78bec2e152f97058f8b8c64 Mon Sep 17 00:00:00 2001 From: burczu Date: Sun, 20 Oct 2024 21:58:09 +0200 Subject: [PATCH 25/52] prettier fix --- src/components/SubStepForms/FullNameStep.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/SubStepForms/FullNameStep.tsx b/src/components/SubStepForms/FullNameStep.tsx index e1048162847b0..f5c234650879b 100644 --- a/src/components/SubStepForms/FullNameStep.tsx +++ b/src/components/SubStepForms/FullNameStep.tsx @@ -1,5 +1,10 @@ import React, {useCallback} from 'react'; import {View} from 'react-native'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; +import Text from '@components/Text'; +import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -7,11 +12,6 @@ import * as ValidationUtils from '@libs/ValidationUtils'; import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; -import FormProvider from '../Form/FormProvider'; -import InputWrapper from '../Form/InputWrapper'; -import type {FormInputErrors, FormOnyxKeys, FormOnyxValues} from '../Form/types'; -import Text from '../Text'; -import TextInput from '../TextInput'; type FullNameStepProps = SubStepProps & { /** The ID of the form */ From 09494cc3874abb5a895598427f21024ef3ebaa5f Mon Sep 17 00:00:00 2001 From: burczu Date: Sun, 20 Oct 2024 21:58:46 +0200 Subject: [PATCH 26/52] switched to generic form id typing for common date of birth comp usages --- .../SubStepForms/DateOfBirthStep.tsx | 34 ++++++++++++------- .../substeps/DateOfBirth.tsx | 10 +++--- .../DateOfBirthUBO.tsx | 10 +++--- .../PersonalInfo/substeps/DateOfBirth.tsx | 8 ++--- 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/components/SubStepForms/DateOfBirthStep.tsx b/src/components/SubStepForms/DateOfBirthStep.tsx index 188c23bed7852..0b83e29641311 100644 --- a/src/components/SubStepForms/DateOfBirthStep.tsx +++ b/src/components/SubStepForms/DateOfBirthStep.tsx @@ -3,7 +3,7 @@ import React, {useCallback} from 'react'; import DatePicker from '@components/DatePicker'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; -import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; +import type {FormInputErrors, FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import type {SubStepProps} from '@hooks/useSubStep/types'; @@ -13,30 +13,39 @@ import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; -type DateOfBirthStepProps = SubStepProps & { +type DateOfBirthStepProps = SubStepProps & { /** The ID of the form */ - formID: keyof OnyxFormValuesMapping; + formID: TFormID; /** The title of the form */ formTitle: string; /** The validation function to call when the form is submitted */ - customValidate?: (values: FormOnyxValues) => Partial>; + customValidate?: (values: FormOnyxValues) => FormInputErrors; /** A function to call when the form is submitted */ - onSubmit: (values: FormOnyxValues) => void; + onSubmit: (values: FormOnyxValues) => void; /** Fields list of the form */ - stepFields: Array>; + stepFields: Array>; /** The ID of the date of birth input */ - dobInputID: keyof FormOnyxValues; + dobInputID: string; /** The default value for the date of birth input */ dobDefaultValue: string; }; -function DateOfBirthStep({formID, formTitle, customValidate, onSubmit, stepFields, dobInputID, dobDefaultValue, isEditing}: DateOfBirthStepProps) { +function DateOfBirthStep({ + formID, + formTitle, + customValidate, + onSubmit, + stepFields, + dobInputID, + dobDefaultValue, + isEditing, +}: DateOfBirthStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -44,14 +53,15 @@ function DateOfBirthStep({formID, formTitle, customValidate, onSubmit, stepField const maxDate = subYears(new Date(), CONST.DATE_BIRTH.MIN_AGE_FOR_PAYMENT); const validate = useCallback( - (values: FormOnyxValues): Partial> => { + (values: FormOnyxValues): FormInputErrors => { const errors = ValidationUtils.getFieldRequiredErrors(values, stepFields); - if (values[dobInputID]) { - if (!ValidationUtils.isValidPastDate(values[dobInputID]) || !ValidationUtils.meetsMaximumAgeRequirement(values[dobInputID])) { + const valuesToValidate = values[dobInputID as keyof FormOnyxValues] as string; + if (valuesToValidate) { + if (!ValidationUtils.isValidPastDate(valuesToValidate) || !ValidationUtils.meetsMaximumAgeRequirement(valuesToValidate)) { // @ts-expect-error type mismatch to be fixed errors[dobInputID] = translate('bankAccount.error.dob'); - } else if (!ValidationUtils.meetsMinimumAgeRequirement(values[dobInputID])) { + } else if (!ValidationUtils.meetsMinimumAgeRequirement(valuesToValidate)) { // @ts-expect-error type mismatch to be fixed errors[dobInputID] = translate('bankAccount.error.age'); } diff --git a/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx b/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx index a31146a0d13b0..ccfa8245fc0b9 100644 --- a/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx +++ b/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx @@ -1,14 +1,12 @@ import React from 'react'; -import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; import DateOfBirthStep from '@components/SubStepForms/DateOfBirthStep'; import useLocalize from '@hooks/useLocalize'; import usePersonalDetailsFormSubmit from '@hooks/usePersonalDetailsFormSubmit'; import type {CustomSubStepProps} from '@pages/MissingPersonalDetails/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/PersonalDetailsForm'; -const STEP_FIELDS = [INPUT_IDS.DATE_OF_BIRTH] as Array>; +const STEP_FIELDS = [INPUT_IDS.DATE_OF_BIRTH]; function DateOfBirth({isEditing, onNext, onMove, personalDetailsValues}: CustomSubStepProps) { const {translate} = useLocalize(); @@ -17,10 +15,10 @@ function DateOfBirth({isEditing, onNext, onMove, personalDetailsValues}: CustomS fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: true, - }) as (values: FormOnyxValues) => void; + }); return ( - isEditing={isEditing} onNext={onNext} onMove={onMove} @@ -28,7 +26,7 @@ function DateOfBirth({isEditing, onNext, onMove, personalDetailsValues}: CustomS formTitle={translate('privatePersonalDetails.enterDateOfBirth')} onSubmit={handleSubmit} stepFields={STEP_FIELDS} - dobInputID={INPUT_IDS.DATE_OF_BIRTH as keyof FormOnyxValues} + dobInputID={INPUT_IDS.DATE_OF_BIRTH} dobDefaultValue={personalDetailsValues[INPUT_IDS.DATE_OF_BIRTH]} /> ); diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx index 4d1bd17d83e75..37a028f52020c 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx @@ -1,14 +1,12 @@ import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; import DateOfBirthStep from '@components/SubStepForms/DateOfBirthStep'; import useLocalize from '@hooks/useLocalize'; import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; import type {ReimbursementAccountForm} from '@src/types/form'; const DOB = CONST.BANK_ACCOUNT.BENEFICIAL_OWNER_INFO_STEP.BENEFICIAL_OWNER_DATA.DOB; @@ -30,18 +28,18 @@ function DateOfBirthUBO({reimbursementAccountDraft, onNext, onMove, isEditing, b fieldIds: [dobInputID], onNext, shouldSaveDraft: isEditing, - }) as (values: FormOnyxValues) => void; + }); return ( - isEditing={isEditing} onNext={onNext} onMove={onMove} formID={ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM} formTitle={translate('beneficialOwnerInfoStep.enterTheDateOfBirthOfTheOwner')} onSubmit={handleSubmit} - stepFields={[dobInputID] as Array>} - dobInputID={dobInputID as keyof FormOnyxValues} + stepFields={[dobInputID]} + dobInputID={dobInputID} dobDefaultValue={dobDefaultValue} /> ); diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx index efeab25b146b7..e2c28c5584872 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx @@ -23,7 +23,7 @@ type DateOfBirthOnyxProps = { type DateOfBirthProps = DateOfBirthOnyxProps & SubStepProps; const PERSONAL_INFO_DOB_KEY = INPUT_IDS.PERSONAL_INFO_STEP.DOB; -const STEP_FIELDS = [PERSONAL_INFO_DOB_KEY] as Array>; +const STEP_FIELDS = [PERSONAL_INFO_DOB_KEY]; function DateOfBirth({reimbursementAccount, reimbursementAccountDraft, onNext, onMove, isEditing}: DateOfBirthProps) { const {translate} = useLocalize(); @@ -34,10 +34,10 @@ function DateOfBirth({reimbursementAccount, reimbursementAccountDraft, onNext, o fieldIds: STEP_FIELDS, onNext, shouldSaveDraft: isEditing, - }) as (values: FormOnyxValues) => void; + }); return ( - isEditing={isEditing} onNext={onNext} onMove={onMove} @@ -45,7 +45,7 @@ function DateOfBirth({reimbursementAccount, reimbursementAccountDraft, onNext, o formTitle={translate('personalInfoStep.enterYourDateOfBirth')} onSubmit={handleSubmit} stepFields={STEP_FIELDS} - dobInputID={PERSONAL_INFO_DOB_KEY as keyof FormOnyxValues} + dobInputID={PERSONAL_INFO_DOB_KEY} dobDefaultValue={dobDefaultValue} /> ); From 7c04b0ea5396d453c191d9d1d15f7b132d23342b Mon Sep 17 00:00:00 2001 From: burczu Date: Mon, 21 Oct 2024 10:08:12 +0200 Subject: [PATCH 27/52] common single field step component created --- .../SubStepForms/SingleFieldStep.tsx | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/components/SubStepForms/SingleFieldStep.tsx diff --git a/src/components/SubStepForms/SingleFieldStep.tsx b/src/components/SubStepForms/SingleFieldStep.tsx new file mode 100644 index 0000000000000..737463ce50e8b --- /dev/null +++ b/src/components/SubStepForms/SingleFieldStep.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import type {InputModeOptions} from 'react-native'; +import {View} from 'react-native'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import Text from '@components/Text'; +import TextInput from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; +import type {SubStepProps} from '@hooks/useSubStep/types'; +import useThemeStyles from '@hooks/useThemeStyles'; +import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; +import CONST from '@src/CONST'; +import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; + +type SingleFieldStepProps = SubStepProps & { + /** The ID of the form */ + formID: TFormID; + + /** The title of the form */ + formTitle: string; + + /** The disclaimer to show below the form title */ + formDisclaimer?: string; + + /** The validation function to call when the form is submitted */ + validate: (values: FormOnyxValues) => FormInputErrors; + + /** A function to call when the form is submitted */ + onSubmit: (values: FormOnyxValues) => void; + + /** The ID of the form input */ + inputId: string; + + /** The label of the input */ + inputLabel: string; + + /** The mode of the input */ + inputMode?: InputModeOptions; + + /** The default values for the form */ + defaultValue: string; + + /** Whether to show help links */ + shouldShowHelpLinks?: boolean; +}; + +function SingleFieldStep({ + formID, + formTitle, + formDisclaimer, + validate, + onSubmit, + inputId, + inputLabel, + inputMode, + defaultValue, + isEditing, + shouldShowHelpLinks = true, +}: SingleFieldStepProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + return ( + + + {formTitle} + {formDisclaimer && {formDisclaimer}} + + + + {shouldShowHelpLinks && } + + + ); +} + +SingleFieldStep.defaultName = 'SingleFieldStep'; + +export default SingleFieldStep; From ea4686234c1c90f6dc27750c21293cb0ec3042ab Mon Sep 17 00:00:00 2001 From: burczu Date: Mon, 21 Oct 2024 10:08:43 +0200 Subject: [PATCH 28/52] common single field step used for personal info page --- .../substeps/SocialSecurityNumber.tsx | 49 +++++-------------- 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx index 2f08980f2bd03..02a1468552eac 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx @@ -1,19 +1,12 @@ import React, {useCallback} from 'react'; -import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import Text from '@components/Text'; -import TextInput from '@components/TextInput'; +import SingleFieldStep from '@components/SubStepForms/SingleFieldStep'; import useLocalize from '@hooks/useLocalize'; import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; import * as ValidationUtils from '@libs/ValidationUtils'; -import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; import type {ReimbursementAccount} from '@src/types/onyx'; @@ -28,9 +21,8 @@ type SocialSecurityNumberProps = SocialSecurityNumberOnyxProps & SubStepProps; const PERSONAL_INFO_STEP_KEY = INPUT_IDS.PERSONAL_INFO_STEP; const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.SSN_LAST_4]; -function SocialSecurityNumber({reimbursementAccount, onNext, isEditing}: SocialSecurityNumberProps) { +function SocialSecurityNumber({reimbursementAccount, onNext, onMove, isEditing}: SocialSecurityNumberProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); const defaultSsnLast4 = reimbursementAccount?.achData?.[PERSONAL_INFO_STEP_KEY.SSN_LAST_4] ?? ''; @@ -54,38 +46,23 @@ function SocialSecurityNumber({reimbursementAccount, onNext, isEditing}: SocialS }); return ( - + isEditing={isEditing} + onNext={onNext} + onMove={onMove} formID={ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM} - submitButtonText={translate(isEditing ? 'common.confirm' : 'common.next')} + formTitle={translate('personalInfoStep.enterTheLast4')} + formDisclaimer={translate('personalInfoStep.dontWorry')} validate={validate} onSubmit={handleSubmit} - style={[styles.mh5, styles.flexGrow1]} - submitButtonStyles={[styles.mb0]} - > - - {translate('personalInfoStep.enterTheLast4')} - {translate('personalInfoStep.dontWorry')} - - - - - - + inputId={PERSONAL_INFO_STEP_KEY.SSN_LAST_4} + inputLabel={translate('personalInfoStep.last4SSN')} + defaultValue={defaultSsnLast4} + /> ); } -SocialSecurityNumber.displayName = 'SocialSecurityNumber'; +SocialSecurityNumber.defaultName = 'SocialSecurityNumber'; export default withOnyx({ // @ts-expect-error: ONYXKEYS.REIMBURSEMENT_ACCOUNT is conflicting with ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM From 1e991398f77e46fc6ed6121a108e0ed9caaf104a Mon Sep 17 00:00:00 2001 From: burczu Date: Mon, 21 Oct 2024 10:20:27 +0200 Subject: [PATCH 29/52] common single step component used in the beneficial owner page --- .../SocialSecurityNumberUBO.tsx | 47 +++++-------------- 1 file changed, 13 insertions(+), 34 deletions(-) diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/SocialSecurityNumberUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/SocialSecurityNumberUBO.tsx index 483d2750f3996..ed9310c7c38aa 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/SocialSecurityNumberUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/SocialSecurityNumberUBO.tsx @@ -1,16 +1,10 @@ import React from 'react'; -import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; +import {OnyxEntry, withOnyx} from 'react-native-onyx'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import Text from '@components/Text'; -import TextInput from '@components/TextInput'; +import SingleFieldStep from '@components/SubStepForms/SingleFieldStep'; import useLocalize from '@hooks/useLocalize'; import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; import * as ValidationUtils from '@libs/ValidationUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -25,9 +19,8 @@ type SocialSecurityNumberUBOOnyxProps = { }; type SocialSecurityNumberUBOProps = SubStepProps & SocialSecurityNumberUBOOnyxProps & {beneficialOwnerBeingModifiedID: string}; -function SocialSecurityNumberUBO({reimbursementAccountDraft, onNext, isEditing, beneficialOwnerBeingModifiedID}: SocialSecurityNumberUBOProps) { +function SocialSecurityNumberUBO({reimbursementAccountDraft, onNext, onMove, isEditing, beneficialOwnerBeingModifiedID}: SocialSecurityNumberUBOProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); const ssnLast4InputID = `${BENEFICIAL_OWNER_PREFIX}_${beneficialOwnerBeingModifiedID}_${SSN_LAST_4}` as const; const defaultSsnLast4 = reimbursementAccountDraft?.[ssnLast4InputID] ?? ''; @@ -48,33 +41,19 @@ function SocialSecurityNumberUBO({reimbursementAccountDraft, onNext, isEditing, }); return ( - + isEditing={isEditing} + onNext={onNext} + onMove={onMove} formID={ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM} - submitButtonText={translate(isEditing ? 'common.confirm' : 'common.next')} + formTitle={translate('beneficialOwnerInfoStep.enterTheLast4')} validate={validate} onSubmit={handleSubmit} - style={[styles.mh5, styles.flexGrow1]} - submitButtonStyles={[styles.mb0]} - > - - {translate('beneficialOwnerInfoStep.enterTheLast4')} - {translate('beneficialOwnerInfoStep.dontWorry')} - - - - - + inputId={ssnLast4InputID} + inputLabel={translate('beneficialOwnerInfoStep.last4SSN')} + defaultValue={defaultSsnLast4} + shouldShowHelpLinks={false} + /> ); } From c8401d492b013c621206c76b0a517851080aaafd Mon Sep 17 00:00:00 2001 From: burczu Date: Mon, 21 Oct 2024 10:23:41 +0200 Subject: [PATCH 30/52] fix: defaultName changed to displayName --- src/components/SubStepForms/DateOfBirthStep.tsx | 2 +- src/components/SubStepForms/SingleFieldStep.tsx | 2 +- src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx | 2 +- .../EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx | 2 +- .../EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx | 2 +- src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx | 2 +- src/pages/MissingPersonalDetails/substeps/LegalName.tsx | 2 +- .../substeps/BeneficialOwnerDetailsFormSubsteps/AddressUBO.tsx | 2 +- .../BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx | 2 +- .../BeneficialOwnerDetailsFormSubsteps/LegalNameUBO.tsx | 2 +- .../ReimbursementAccount/PersonalInfo/substeps/Address.tsx | 2 +- .../ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx | 2 +- .../ReimbursementAccount/PersonalInfo/substeps/FullName.tsx | 2 +- .../PersonalInfo/substeps/SocialSecurityNumber.tsx | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/components/SubStepForms/DateOfBirthStep.tsx b/src/components/SubStepForms/DateOfBirthStep.tsx index 0b83e29641311..01cd20895a5eb 100644 --- a/src/components/SubStepForms/DateOfBirthStep.tsx +++ b/src/components/SubStepForms/DateOfBirthStep.tsx @@ -97,6 +97,6 @@ function DateOfBirthStep({ ); } -DateOfBirthStep.defaultName = 'DateOfBirthStep'; +DateOfBirthStep.displayName = 'DateOfBirthStep'; export default DateOfBirthStep; diff --git a/src/components/SubStepForms/SingleFieldStep.tsx b/src/components/SubStepForms/SingleFieldStep.tsx index 737463ce50e8b..26029280d6928 100644 --- a/src/components/SubStepForms/SingleFieldStep.tsx +++ b/src/components/SubStepForms/SingleFieldStep.tsx @@ -93,6 +93,6 @@ function SingleFieldStep({ ); } -SingleFieldStep.defaultName = 'SingleFieldStep'; +SingleFieldStep.displayName = 'SingleFieldStep'; export default SingleFieldStep; diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx index 3364e625f3bf6..342fb041a6c9a 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/AddressStep.tsx @@ -52,6 +52,6 @@ function AddressStep({onNext, onMove, isEditing}: SubStepProps) { ); } -AddressStep.defaultName = 'AddressStep'; +AddressStep.displayName = 'AddressStep'; export default AddressStep; diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx index 6c91715e8eb91..1b1949e748417 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx @@ -39,6 +39,6 @@ function DateOfBirthStep({onNext, onMove, isEditing}: SubStepProps) { ); } -DateOfBirthStep.defaultName = 'DateOfBirthStep'; +DateOfBirthStep.displayName = 'DateOfBirthStep'; export default DateOfBirthStep; diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx index bdd146bd890d0..41348ec5b9ef0 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/LegalNameStep.tsx @@ -41,6 +41,6 @@ function LegalNameStep({onNext, onMove, isEditing}: SubStepProps) { ); } -LegalNameStep.defaultName = 'LegalNameStep'; +LegalNameStep.displayName = 'LegalNameStep'; export default LegalNameStep; diff --git a/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx b/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx index ccfa8245fc0b9..84788d0458d0c 100644 --- a/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx +++ b/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx @@ -32,6 +32,6 @@ function DateOfBirth({isEditing, onNext, onMove, personalDetailsValues}: CustomS ); } -DateOfBirth.defaultName = 'DateOfBirth'; +DateOfBirth.displayName = 'DateOfBirth'; export default DateOfBirth; diff --git a/src/pages/MissingPersonalDetails/substeps/LegalName.tsx b/src/pages/MissingPersonalDetails/substeps/LegalName.tsx index 7f098851fbcbb..b7567ad23f893 100644 --- a/src/pages/MissingPersonalDetails/substeps/LegalName.tsx +++ b/src/pages/MissingPersonalDetails/substeps/LegalName.tsx @@ -79,6 +79,6 @@ function LegalName({isEditing, onNext, onMove, personalDetailsValues}: CustomSub ); } -LegalName.defaultName = 'LegalName'; +LegalName.displayName = 'LegalName'; export default LegalName; diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/AddressUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/AddressUBO.tsx index 5612f5cdbcde8..c5f67960e18c0 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/AddressUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/AddressUBO.tsx @@ -60,7 +60,7 @@ function AddressUBO({reimbursementAccountDraft, onNext, onMove, isEditing, benef ); } -AddressUBO.defaultName = 'AddressUBO'; +AddressUBO.displayName = 'AddressUBO'; export default withOnyx({ reimbursementAccountDraft: { diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx index 37a028f52020c..b9bbe4f312212 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx @@ -45,7 +45,7 @@ function DateOfBirthUBO({reimbursementAccountDraft, onNext, onMove, isEditing, b ); } -DateOfBirthUBO.defaultName = 'DateOfBirthUBO'; +DateOfBirthUBO.displayName = 'DateOfBirthUBO'; export default withOnyx({ reimbursementAccountDraft: { diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/LegalNameUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/LegalNameUBO.tsx index 838ea6ac1a23b..ebfce3dcc741c 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/LegalNameUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/LegalNameUBO.tsx @@ -52,7 +52,7 @@ function LegalNameUBO({reimbursementAccountDraft, onNext, onMove, isEditing, ben ); } -LegalNameUBO.defaultName = 'LegalNameUBO'; +LegalNameUBO.displayName = 'LegalNameUBO'; export default withOnyx({ reimbursementAccountDraft: { diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/Address.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/Address.tsx index 746b26082d5df..1b7b471af61d2 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/Address.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/Address.tsx @@ -59,7 +59,7 @@ function Address({reimbursementAccount, onNext, onMove, isEditing}: AddressProps ); } -Address.defaultName = 'Address'; +Address.displayName = 'Address'; export default withOnyx({ // @ts-expect-error: ONYXKEYS.REIMBURSEMENT_ACCOUNT is conflicting with ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx index e2c28c5584872..fa18223c5e043 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx @@ -51,7 +51,7 @@ function DateOfBirth({reimbursementAccount, reimbursementAccountDraft, onNext, o ); } -DateOfBirth.defaultName = 'DateOfBirth'; +DateOfBirth.displayName = 'DateOfBirth'; export default withOnyx({ // @ts-expect-error: ONYXKEYS.REIMBURSEMENT_ACCOUNT is conflicting with ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx index 1c212d8c78ee1..138e721ea0429 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.tsx @@ -50,7 +50,7 @@ function FullName({reimbursementAccount, onNext, onMove, isEditing}: FullNamePro ); } -FullName.defaultName = 'FullName'; +FullName.displayName = 'FullName'; export default withOnyx({ // @ts-expect-error: ONYXKEYS.REIMBURSEMENT_ACCOUNT is conflicting with ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx index 02a1468552eac..40542a6d937d9 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx @@ -62,7 +62,7 @@ function SocialSecurityNumber({reimbursementAccount, onNext, onMove, isEditing}: ); } -SocialSecurityNumber.defaultName = 'SocialSecurityNumber'; +SocialSecurityNumber.displayName = 'SocialSecurityNumber'; export default withOnyx({ // @ts-expect-error: ONYXKEYS.REIMBURSEMENT_ACCOUNT is conflicting with ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM From edbf6a27a37f56b20a54f1ffaea3a9bf7b27f0ca Mon Sep 17 00:00:00 2001 From: burczu Date: Mon, 21 Oct 2024 11:07:41 +0200 Subject: [PATCH 31/52] common single field step component used wherever possible --- .../PersonalInfo/substeps/PhoneNumberStep.tsx | 47 +++++----------- .../substeps/SocialSecurityNumberStep.tsx | 49 +++++------------ .../substeps/PhoneNumber.tsx | 54 +++++++------------ 3 files changed, 47 insertions(+), 103 deletions(-) diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx index 60bfa431ca784..54843c2aaf4a3 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/PhoneNumberStep.tsx @@ -1,17 +1,11 @@ import React, {useCallback} from 'react'; -import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import Text from '@components/Text'; -import TextInput from '@components/TextInput'; +import SingleFieldStep from '@components/SubStepForms/SingleFieldStep'; import useLocalize from '@hooks/useLocalize'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; import * as ValidationUtils from '@libs/ValidationUtils'; -import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; @@ -19,12 +13,10 @@ import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; const PERSONAL_INFO_STEP_KEY = INPUT_IDS.PERSONAL_INFO_STEP; const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.PHONE_NUMBER]; -function PhoneNumberStep({onNext, isEditing}: SubStepProps) { +function PhoneNumberStep({onNext, onMove, isEditing}: SubStepProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); const [walletAdditionalDetails] = useOnyx(ONYXKEYS.WALLET_ADDITIONAL_DETAILS); - const defaultPhoneNumber = walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.PHONE_NUMBER] ?? ''; const validate = useCallback( @@ -46,33 +38,20 @@ function PhoneNumberStep({onNext, isEditing}: SubStepProps) { }); return ( - + isEditing={isEditing} + onNext={onNext} + onMove={onMove} formID={ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS} - submitButtonText={translate(isEditing ? 'common.confirm' : 'common.next')} + formTitle={translate('personalInfoStep.whatsYourPhoneNumber')} + formDisclaimer={translate('personalInfoStep.weNeedThisToVerify')} validate={validate} onSubmit={handleSubmit} - style={[styles.mh5, styles.flexGrow1]} - > - - {translate('personalInfoStep.whatsYourPhoneNumber')} - {translate('personalInfoStep.weNeedThisToVerify')} - - - - - - + inputId={PERSONAL_INFO_STEP_KEY.PHONE_NUMBER} + inputLabel={translate('common.phoneNumber')} + inputMode={CONST.INPUT_MODE.TEL} + defaultValue={defaultPhoneNumber} + /> ); } diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx index c12f90db072eb..95ffa13b6c5d4 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx @@ -1,17 +1,11 @@ import React, {useCallback} from 'react'; -import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import Text from '@components/Text'; -import TextInput from '@components/TextInput'; +import SingleFieldStep from '@components/SubStepForms/SingleFieldStep'; import useLocalize from '@hooks/useLocalize'; import type {SubStepProps} from '@hooks/useSubStep/types'; -import useThemeStyles from '@hooks/useThemeStyles'; import useWalletAdditionalDetailsStepFormSubmit from '@hooks/useWalletAdditionalDetailsStepFormSubmit'; import * as ValidationUtils from '@libs/ValidationUtils'; -import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; @@ -19,12 +13,12 @@ import INPUT_IDS from '@src/types/form/WalletAdditionalDetailsForm'; const PERSONAL_INFO_STEP_KEY = INPUT_IDS.PERSONAL_INFO_STEP; const STEP_FIELDS = [PERSONAL_INFO_STEP_KEY.SSN_LAST_4]; -function SocialSecurityNumberStep({onNext, isEditing}: SubStepProps) { +function SocialSecurityNumberStep({onNext, onMove, isEditing}: SubStepProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); const [walletAdditionalDetails] = useOnyx(ONYXKEYS.WALLET_ADDITIONAL_DETAILS); const shouldAskForFullSSN = walletAdditionalDetails?.errorCode === CONST.WALLET.ERROR.SSN; + const defaultSsnLast4 = walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.SSN_LAST_4] ?? ''; const validate = useCallback( (values: FormOnyxValues): FormInputErrors => { @@ -43,8 +37,6 @@ function SocialSecurityNumberStep({onNext, isEditing}: SubStepProps) { [translate, shouldAskForFullSSN], ); - const defaultSsnLast4 = walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.SSN_LAST_4] ?? ''; - const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({ fieldIds: STEP_FIELDS, onNext, @@ -52,33 +44,20 @@ function SocialSecurityNumberStep({onNext, isEditing}: SubStepProps) { }); return ( - + isEditing={isEditing} + onNext={onNext} + onMove={onMove} formID={ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS} - submitButtonText={translate(isEditing ? 'common.confirm' : 'common.next')} + formTitle={translate('personalInfoStep.whatsYourSSN')} + formDisclaimer={translate('personalInfoStep.noPersonalChecks')} validate={validate} onSubmit={handleSubmit} - style={[styles.mh5, styles.flexGrow1]} - > - - {translate('personalInfoStep.whatsYourSSN')} - {translate('personalInfoStep.noPersonalChecks')} - - - - - - + inputId={PERSONAL_INFO_STEP_KEY.SSN_LAST_4} + inputLabel={translate(shouldAskForFullSSN ? 'common.ssnFull9' : 'personalInfoStep.last4SSN')} + inputMode={CONST.INPUT_MODE.NUMERIC} + defaultValue={defaultSsnLast4} + /> ); } diff --git a/src/pages/MissingPersonalDetails/substeps/PhoneNumber.tsx b/src/pages/MissingPersonalDetails/substeps/PhoneNumber.tsx index 0ca220cf6e22f..81f359f403d16 100644 --- a/src/pages/MissingPersonalDetails/substeps/PhoneNumber.tsx +++ b/src/pages/MissingPersonalDetails/substeps/PhoneNumber.tsx @@ -1,14 +1,9 @@ import {Str} from 'expensify-common'; import React, {useCallback} from 'react'; -import {View} from 'react-native'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import Text from '@components/Text'; -import TextInput from '@components/TextInput'; +import SingleFieldStep from '@components/SubStepForms/SingleFieldStep'; import useLocalize from '@hooks/useLocalize'; import usePersonalDetailsFormSubmit from '@hooks/usePersonalDetailsFormSubmit'; -import useThemeStyles from '@hooks/useThemeStyles'; import * as LoginUtils from '@libs/LoginUtils'; import * as PhoneNumberUtils from '@libs/PhoneNumber'; import * as ValidationUtils from '@libs/ValidationUtils'; @@ -19,15 +14,8 @@ import INPUT_IDS from '@src/types/form/PersonalDetailsForm'; const STEP_FIELDS = [INPUT_IDS.PHONE_NUMBER]; -function PhoneNumberStep({isEditing, onNext, personalDetailsValues}: CustomSubStepProps) { +function PhoneNumberStep({isEditing, onNext, onMove, personalDetailsValues}: CustomSubStepProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); - - const handleSubmit = usePersonalDetailsFormSubmit({ - fieldIds: STEP_FIELDS, - onNext, - shouldSaveDraft: true, - }); const validate = useCallback( (values: FormOnyxValues): FormInputErrors => { @@ -45,29 +33,27 @@ function PhoneNumberStep({isEditing, onNext, personalDetailsValues}: CustomSubSt [translate], ); + const handleSubmit = usePersonalDetailsFormSubmit({ + fieldIds: STEP_FIELDS, + onNext, + shouldSaveDraft: true, + }); + return ( - + isEditing={isEditing} + onNext={onNext} + onMove={onMove} formID={ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM} - submitButtonText={translate(isEditing ? 'common.confirm' : 'common.next')} - onSubmit={handleSubmit} + formTitle={translate('privatePersonalDetails.enterPhoneNumber')} validate={validate} - style={[styles.flexGrow1, styles.mt3]} - submitButtonStyles={[styles.ph5, styles.mb0]} - enabledWhenOffline - > - - {translate('privatePersonalDetails.enterPhoneNumber')} - - - + onSubmit={handleSubmit} + inputId={INPUT_IDS.PHONE_NUMBER} + inputLabel={translate('common.phoneNumber')} + inputMode={CONST.INPUT_MODE.TEL} + defaultValue={personalDetailsValues[INPUT_IDS.PHONE_NUMBER]} + shouldShowHelpLinks={false} + /> ); } From 09779172497574e4df836c0f9a11ce9852b79ddb Mon Sep 17 00:00:00 2001 From: burczu Date: Mon, 21 Oct 2024 11:12:35 +0200 Subject: [PATCH 32/52] missing input mode added --- .../SocialSecurityNumberUBO.tsx | 2 ++ .../PersonalInfo/substeps/SocialSecurityNumber.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/SocialSecurityNumberUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/SocialSecurityNumberUBO.tsx index ed9310c7c38aa..8724aef0c927c 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/SocialSecurityNumberUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/SocialSecurityNumberUBO.tsx @@ -47,10 +47,12 @@ function SocialSecurityNumberUBO({reimbursementAccountDraft, onNext, onMove, isE onMove={onMove} formID={ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM} formTitle={translate('beneficialOwnerInfoStep.enterTheLast4')} + formDisclaimer={translate('beneficialOwnerInfoStep.dontWorry')} validate={validate} onSubmit={handleSubmit} inputId={ssnLast4InputID} inputLabel={translate('beneficialOwnerInfoStep.last4SSN')} + inputMode={CONST.INPUT_MODE.NUMERIC} defaultValue={defaultSsnLast4} shouldShowHelpLinks={false} /> diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx index 40542a6d937d9..0022a3d621981 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.tsx @@ -7,6 +7,7 @@ import useLocalize from '@hooks/useLocalize'; import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; import * as ValidationUtils from '@libs/ValidationUtils'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; import type {ReimbursementAccount} from '@src/types/onyx'; @@ -57,6 +58,7 @@ function SocialSecurityNumber({reimbursementAccount, onNext, onMove, isEditing}: onSubmit={handleSubmit} inputId={PERSONAL_INFO_STEP_KEY.SSN_LAST_4} inputLabel={translate('personalInfoStep.last4SSN')} + inputMode={CONST.INPUT_MODE.NUMERIC} defaultValue={defaultSsnLast4} /> ); From 8033f107a991e146c2757330230922e50e458f79 Mon Sep 17 00:00:00 2001 From: burczu Date: Mon, 21 Oct 2024 11:27:45 +0200 Subject: [PATCH 33/52] missing should show help links prop in date of birth comp fixed --- src/components/SubStepForms/DateOfBirthStep.tsx | 6 +++++- src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx | 1 + .../BeneficialOwnerDetailsFormSubsteps/AddressUBO.tsx | 1 + .../BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/SubStepForms/DateOfBirthStep.tsx b/src/components/SubStepForms/DateOfBirthStep.tsx index 01cd20895a5eb..295fb3d4562d4 100644 --- a/src/components/SubStepForms/DateOfBirthStep.tsx +++ b/src/components/SubStepForms/DateOfBirthStep.tsx @@ -34,6 +34,9 @@ type DateOfBirthStepProps = SubStep /** The default value for the date of birth input */ dobDefaultValue: string; + + /** Whether the component should show help links */ + shouldShowHelpLinks?: boolean; }; function DateOfBirthStep({ @@ -45,6 +48,7 @@ function DateOfBirthStep({ dobInputID, dobDefaultValue, isEditing, + shouldShowHelpLinks = true, }: DateOfBirthStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -92,7 +96,7 @@ function DateOfBirthStep({ maxDate={maxDate} shouldSaveDraft={!isEditing} /> - + {shouldShowHelpLinks && } ); } diff --git a/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx b/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx index 84788d0458d0c..f00fb912cce56 100644 --- a/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx +++ b/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx @@ -28,6 +28,7 @@ function DateOfBirth({isEditing, onNext, onMove, personalDetailsValues}: CustomS stepFields={STEP_FIELDS} dobInputID={INPUT_IDS.DATE_OF_BIRTH} dobDefaultValue={personalDetailsValues[INPUT_IDS.DATE_OF_BIRTH]} + shouldShowHelpLinks={false} /> ); } diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/AddressUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/AddressUBO.tsx index c5f67960e18c0..e7cb34cc07827 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/AddressUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/AddressUBO.tsx @@ -56,6 +56,7 @@ function AddressUBO({reimbursementAccountDraft, onNext, onMove, isEditing, benef stepFields={stepFields} inputFieldsIDs={inputKeys} defaultValues={defaultValues} + shouldShowHelpLinks={false} /> ); } diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx index b9bbe4f312212..7690959216d36 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx @@ -41,6 +41,7 @@ function DateOfBirthUBO({reimbursementAccountDraft, onNext, onMove, isEditing, b stepFields={[dobInputID]} dobInputID={dobInputID} dobDefaultValue={dobDefaultValue} + shouldShowHelpLinks={false} /> ); } From d141c3ee0537d5648c612316b10f51db3b7ff44c Mon Sep 17 00:00:00 2001 From: burczu Date: Mon, 21 Oct 2024 11:30:49 +0200 Subject: [PATCH 34/52] some cleanup --- .../EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx | 2 +- .../ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx index 1b1949e748417..6cdc592946d16 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/DateOfBirthStep.tsx @@ -25,7 +25,7 @@ function DateOfBirthStep({onNext, onMove, isEditing}: SubStepProps) { }) as (values: FormOnyxValues) => void; return ( - isEditing={isEditing} onNext={onNext} onMove={onMove} diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx index fa18223c5e043..6cca9e24e7515 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.tsx @@ -1,13 +1,11 @@ import React from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; -import type {FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; import DateOfBirthStep from '@components/SubStepForms/DateOfBirthStep'; import useLocalize from '@hooks/useLocalize'; import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; import type {SubStepProps} from '@hooks/useSubStep/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; import type {ReimbursementAccountForm} from '@src/types/form'; import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; import type {ReimbursementAccount} from '@src/types/onyx'; From 60853017db3150af360e42724e94edab13988827 Mon Sep 17 00:00:00 2001 From: burczu Date: Tue, 22 Oct 2024 08:56:09 +0200 Subject: [PATCH 35/52] common confirmation step component created --- .../SubStepForms/ConfirmationStep.tsx | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/components/SubStepForms/ConfirmationStep.tsx diff --git a/src/components/SubStepForms/ConfirmationStep.tsx b/src/components/SubStepForms/ConfirmationStep.tsx new file mode 100644 index 0000000000000..f544dfb56d967 --- /dev/null +++ b/src/components/SubStepForms/ConfirmationStep.tsx @@ -0,0 +1,117 @@ +import React from 'react'; +import {View} from 'react-native'; +import Button from '@components/Button'; +import DotIndicatorMessage from '@components/DotIndicatorMessage'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import SafeAreaConsumer from '@components/SafeAreaConsumer'; +import ScrollView from '@components/ScrollView'; +import Text from '@components/Text'; +import TextLink from '@components/TextLink'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; +import type {SubStepProps} from '@hooks/useSubStep/types'; +import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; + +type SummaryItem = { + description: string; + title: string; + shouldShowRightIcon: boolean; + onPress: () => void; +}; + +type ConfirmationStepProps = SubStepProps & { + /** The title of the step */ + pageTitle: string; + + /** The summary items to display */ + summaryItems: SummaryItem[]; + + /** Whether show additional section with Onfido terms etc. */ + showOnfidoLinks: boolean; + + /** The title of the Onfido section */ + onfidoLinksTitle?: string; + + /** Whether the data is loading */ + isLoading?: boolean; + + /** The error message to display */ + error?: string; +}; + +function ConfirmationStep({pageTitle, summaryItems, showOnfidoLinks, onfidoLinksTitle, isLoading, error, onNext}: ConfirmationStepProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const {isOffline} = useNetwork(); + + return ( + + {({safeAreaPaddingBottomStyle}) => ( + + {pageTitle} + {summaryItems.map(({description, title, shouldShowRightIcon, onPress}) => ( + + ))} + + {showOnfidoLinks && ( + + {onfidoLinksTitle} + + {translate('onfidoStep.facialScan')} + + {', '} + + {translate('common.privacy')} + + {` ${translate('common.and')} `} + + {translate('common.termsOfService')} + + + )} + + + {error && error.length > 0 && ( + + )} +