diff --git a/src/components/InteractiveStepWrapper.tsx b/src/components/InteractiveStepWrapper.tsx index 6ffe00b9bd5d2..5717acf87e7c1 100644 --- a/src/components/InteractiveStepWrapper.tsx +++ b/src/components/InteractiveStepWrapper.tsx @@ -1,4 +1,5 @@ -import React from 'react'; +import React, {forwardRef} from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; @@ -24,21 +25,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 +90,4 @@ function InteractiveStepWrapper({children, wrapperID, handleBackButtonPress, hea InteractiveStepWrapper.displayName = 'InteractiveStepWrapper'; -export default InteractiveStepWrapper; +export default forwardRef(InteractiveStepWrapper); diff --git a/src/components/SubStepForms/AddressStep.tsx b/src/components/SubStepForms/AddressStep.tsx new file mode 100644 index 0000000000000..fa7b56630c0c6 --- /dev/null +++ b/src/components/SubStepForms/AddressStep.tsx @@ -0,0 +1,111 @@ +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import FormProvider from '@components/Form/FormProvider'; +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'; +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); + + 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'); + } + + 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.zipCode] = translate('bankAccount.error.zipCode'); + } + + return errors; + }, + [inputFieldsIDs.street, inputFieldsIDs.zipCode, stepFields, translate], + ); + + return ( + + + {formTitle} + {formPOBoxDisclaimer && {formPOBoxDisclaimer}} + + {shouldShowHelpLinks && } + + + ); +} + +AddressStep.displayName = 'AddressStep'; + +export default AddressStep; diff --git a/src/components/SubStepForms/ConfirmationStep.tsx b/src/components/SubStepForms/ConfirmationStep.tsx new file mode 100644 index 0000000000000..7fbec82c4bbeb --- /dev/null +++ b/src/components/SubStepForms/ConfirmationStep.tsx @@ -0,0 +1,118 @@ +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 && ( + + )} +