diff --git a/src/CONST.ts b/src/CONST.ts index 132b49c16ef7..873e1ca3b358 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -535,6 +535,10 @@ const CONST = { TERMS_URL: `${USE_EXPENSIFY_URL}/terms`, PRIVACY_URL: `${USE_EXPENSIFY_URL}/privacy`, LICENSES_URL: `${USE_EXPENSIFY_URL}/licenses`, + ACH_TERMS_URL: `${USE_EXPENSIFY_URL}/achterms`, + WALLET_AGREEMENT_URL: `${USE_EXPENSIFY_URL}/walletagreement`, + HELP_LINK_URL: `${USE_EXPENSIFY_URL}/usa-patriot-act`, + ELECTRONIC_DISCLOSURES_URL: `${USE_EXPENSIFY_URL}/esignagreement`, GITHUB_RELEASE_URL: 'https://api.github.com/repos/expensify/app/releases/latest', ADD_SECONDARY_LOGIN_URL: encodeURI('settings?param={"section":"account","openModal":"secondaryLogin"}'), MANAGE_CARDS_URL: 'domain_companycards', diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index e91b4d491423..e14a880fcfa6 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -411,6 +411,8 @@ const ONYXKEYS = { EXIT_SURVEY_REASON_FORM_DRAFT: 'exitSurveyReasonFormDraft', EXIT_SURVEY_RESPONSE_FORM: 'exitSurveyResponseForm', EXIT_SURVEY_RESPONSE_FORM_DRAFT: 'exitSurveyResponseFormDraft', + WALLET_ADDITIONAL_DETAILS: 'walletAdditionalDetails', + WALLET_ADDITIONAL_DETAILS_DRAFT: 'walletAdditionalDetailsDraft', POLICY_TAG_NAME_FORM: 'policyTagNameForm', POLICY_TAG_NAME_FORM_DRAFT: 'policyTagNameFormDraft', WORKSPACE_NEW_TAX_FORM: 'workspaceNewTaxForm', @@ -462,6 +464,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM]: FormTypes.ReimbursementAccountForm; [ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT]: FormTypes.PersonalBankAccountForm; [ONYXKEYS.FORMS.WORKSPACE_DESCRIPTION_FORM]: FormTypes.WorkspaceDescriptionForm; + [ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS]: FormTypes.AdditionalDetailStepForm; [ONYXKEYS.FORMS.POLICY_TAG_NAME_FORM]: FormTypes.PolicyTagNameForm; [ONYXKEYS.FORMS.WORKSPACE_NEW_TAX_FORM]: FormTypes.WorkspaceNewTaxForm; [ONYXKEYS.FORMS.POLICY_CREATE_DISTANCE_RATE_FORM]: FormTypes.PolicyCreateDistanceRateForm; diff --git a/src/components/withCurrentUserPersonalDetails.tsx b/src/components/withCurrentUserPersonalDetails.tsx index 313bcad74f35..8431ededcb56 100644 --- a/src/components/withCurrentUserPersonalDetails.tsx +++ b/src/components/withCurrentUserPersonalDetails.tsx @@ -43,4 +43,4 @@ export default function - - PaymentMethods.continueSetup()} - /> - - ); -} - -ActivateStep.propTypes = propTypes; -ActivateStep.defaultProps = defaultProps; -ActivateStep.displayName = 'ActivateStep'; - -export default compose( - withLocalize, - withOnyx({ - walletTerms: { - key: ONYXKEYS.WALLET_TERMS, - }, - }), -)(ActivateStep); diff --git a/src/pages/EnablePayments/ActivateStep.tsx b/src/pages/EnablePayments/ActivateStep.tsx new file mode 100644 index 000000000000..e0bea7488140 --- /dev/null +++ b/src/pages/EnablePayments/ActivateStep.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import ConfirmationPage from '@components/ConfirmationPage'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import LottieAnimations from '@components/LottieAnimations'; +import useLocalize from '@hooks/useLocalize'; +import * as PaymentMethods from '@userActions/PaymentMethods'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {UserWallet, WalletTerms} from '@src/types/onyx'; + +type ActivateStepOnyxProps = { + /** Information about the user accepting the terms for payments */ + walletTerms: OnyxEntry; +}; + +type ActivateStepProps = ActivateStepOnyxProps & { + /** The user's wallet */ + userWallet: OnyxEntry; +}; + +function ActivateStep({userWallet, walletTerms}: ActivateStepProps) { + const {translate} = useLocalize(); + const isActivatedWallet = userWallet?.tierName && [CONST.WALLET.TIER_NAME.GOLD, CONST.WALLET.TIER_NAME.PLATINUM].some((name) => name === userWallet.tierName); + + const animation = isActivatedWallet ? LottieAnimations.Fireworks : LottieAnimations.ReviewingBankInfo; + let continueButtonText = ''; + + if (walletTerms?.chatReportID) { + continueButtonText = translate('activateStep.continueToPayment'); + } else if (walletTerms?.source === CONST.KYC_WALL_SOURCE.ENABLE_WALLET) { + continueButtonText = translate('common.continue'); + } else { + continueButtonText = translate('activateStep.continueToTransfer'); + } + + return ( + <> + + PaymentMethods.continueSetup()} + /> + + ); +} + +ActivateStep.displayName = 'ActivateStep'; + +export default withOnyx({ + walletTerms: { + key: ONYXKEYS.WALLET_TERMS, + }, +})(ActivateStep); diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.js b/src/pages/EnablePayments/AdditionalDetailsStep.tsx similarity index 71% rename from src/pages/EnablePayments/AdditionalDetailsStep.js rename to src/pages/EnablePayments/AdditionalDetailsStep.tsx index cf769bf58fd3..57b9c7c6ade4 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.js +++ b/src/pages/EnablePayments/AdditionalDetailsStep.tsx @@ -1,21 +1,21 @@ import {subYears} from 'date-fns'; -import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {OnyxEntry} 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 HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import TextLink from '@components/TextLink'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import {parsePhoneNumber} from '@libs/PhoneNumber'; import * as ValidationUtils from '@libs/ValidationUtils'; @@ -23,51 +23,25 @@ import AddressForm from '@pages/ReimbursementAccount/AddressForm'; import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import INPUT_IDS from '@src/types/form/AdditionalDetailStepForm'; +import type {WalletAdditionalDetails} from '@src/types/onyx'; import IdologyQuestions from './IdologyQuestions'; -const propTypes = { - ...withLocalizePropTypes, - ...withCurrentUserPersonalDetailsPropTypes, +const DEFAULT_WALLET_ADDITIONAL_DETAILS = { + errorFields: {}, + isLoading: false, + errors: {}, + questions: [], + idNumber: '', + errorCode: '', +}; +type AdditionalDetailsStepOnyxProps = { /** Stores additional information about the additional details step e.g. loading state and errors with fields */ - walletAdditionalDetails: PropTypes.shape({ - /** Are we waiting for a response? */ - isLoading: PropTypes.bool, - - /** Which field needs attention? */ - errorFields: PropTypes.objectOf(PropTypes.bool), - - /** Any additional error message to show */ - errors: PropTypes.objectOf(PropTypes.string), - - /** Questions returned by Idology */ - questions: PropTypes.arrayOf( - PropTypes.shape({ - prompt: PropTypes.string, - type: PropTypes.string, - answer: PropTypes.arrayOf(PropTypes.string), - }), - ), - - /** ExpectID ID number related to those questions */ - idNumber: PropTypes.string, - - /** Error code to determine additional behavior */ - errorCode: PropTypes.string, - }), + walletAdditionalDetails: OnyxEntry; }; -const defaultProps = { - walletAdditionalDetails: { - errorFields: {}, - isLoading: false, - errors: {}, - questions: [], - idNumber: '', - errorCode: '', - }, - ...withCurrentUserPersonalDetailsDefaultProps, -}; +type AdditionalDetailsStepProps = AdditionalDetailsStepOnyxProps & WithCurrentUserPersonalDetailsProps; const fieldNameTranslationKeys = { legalFirstName: 'additionalDetailsStep.legalFirstNameLabel', @@ -77,22 +51,28 @@ const fieldNameTranslationKeys = { dob: 'common.dob', ssn: 'common.ssnLast4', ssnFull9: 'common.ssnFull9', -}; - -function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserPersonalDetails}) { +} as const; +const STEP_FIELDS = [ + INPUT_IDS.LEGAL_FIRST_NAME, + INPUT_IDS.LEGAL_LAST_NAME, + INPUT_IDS.ADDRESS_STREET, + INPUT_IDS.ADDRESS_CITY, + INPUT_IDS.ADDRESS_ZIP_CODE, + INPUT_IDS.PHONE_NUMBER, + INPUT_IDS.DOB, + INPUT_IDS.ADDRESS_STATE, + INPUT_IDS.SSN, +]; +function AdditionalDetailsStep({walletAdditionalDetails = DEFAULT_WALLET_ADDITIONAL_DETAILS, currentUserPersonalDetails}: AdditionalDetailsStepProps) { + const {translate} = useLocalize(); const styles = useThemeStyles(); const currentDate = new Date(); const minDate = subYears(currentDate, CONST.DATE_BIRTH.MAX_AGE); const maxDate = subYears(currentDate, CONST.DATE_BIRTH.MIN_AGE_FOR_PAYMENT); - const shouldAskForFullSSN = walletAdditionalDetails.errorCode === CONST.WALLET.ERROR.SSN; + const shouldAskForFullSSN = walletAdditionalDetails?.errorCode === CONST.WALLET.ERROR.SSN; - /** - * @param {Object} values The values object is passed from FormProvider and contains info for each form element that has an inputID - * @returns {Object} - */ - const validate = (values) => { - const requiredFields = ['legalFirstName', 'legalLastName', 'addressStreet', 'addressCity', 'addressZipCode', 'phoneNumber', 'dob', 'ssn', 'addressState']; - const errors = ValidationUtils.getFieldRequiredErrors(values, requiredFields); + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); if (values.dob) { if (!ValidationUtils.isValidPastDate(values.dob) || !ValidationUtils.meetsMaximumAgeRequirement(values.dob)) { @@ -116,7 +96,7 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP // walletAdditionalDetails stores errors returned by the server. If the server returns an SSN error // then the user needs to provide the full 9 digit SSN. - if (walletAdditionalDetails.errorCode === CONST.WALLET.ERROR.SSN) { + if (walletAdditionalDetails?.errorCode === CONST.WALLET.ERROR.SSN) { if (values.ssn && !ValidationUtils.isValidSSNFullNine(values.ssn)) { errors.ssn = 'additionalDetailsStep.ssnFull9Error'; } @@ -127,26 +107,23 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP return errors; }; - /** - * @param {Object} values The values object is passed from FormProvider and contains info for each form element that has an inputID - */ - const activateWallet = (values) => { + const activateWallet = (values: FormOnyxValues) => { const personalDetails = { - phoneNumber: parsePhoneNumber(values.phoneNumber, {regionCode: CONST.COUNTRY.US}).number.significant || '', - legalFirstName: values.legalFirstName || '', - legalLastName: values.legalLastName || '', - addressStreet: values.addressStreet || '', - addressCity: values.addressCity || '', - addressState: values.addressState || '', - addressZip: values.addressZipCode || '', - dob: values.dob || '', - ssn: values.ssn || '', + phoneNumber: (values.phoneNumber && parsePhoneNumber(values.phoneNumber, {regionCode: CONST.COUNTRY.US}).number?.significant) ?? '', + legalFirstName: values.legalFirstName ?? '', + legalLastName: values.legalLastName ?? '', + addressStreet: values.addressStreet ?? '', + addressCity: values.addressCity ?? '', + addressState: values.addressState ?? '', + addressZip: values.addressZipCode ?? '', + dob: values.dob ?? '', + ssn: values.ssn ?? '', }; // Attempt to set the personal details Wallet.updatePersonalDetails(personalDetails); }; - if (!_.isEmpty(walletAdditionalDetails.questions)) { + if (walletAdditionalDetails?.questions && walletAdditionalDetails.questions.length > 0) { return ( ); @@ -174,13 +151,13 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP {translate('additionalDetailsStep.helpText')} {translate('additionalDetailsStep.helpLink')} - InputComponent={DatePicker} inputID="dob" containerStyles={[styles.mt4]} @@ -256,16 +234,13 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP ); } -AdditionalDetailsStep.propTypes = propTypes; -AdditionalDetailsStep.defaultProps = defaultProps; AdditionalDetailsStep.displayName = 'AdditionalDetailsStep'; -export default compose( - withLocalize, - withCurrentUserPersonalDetails, - withOnyx({ +export default withCurrentUserPersonalDetails( + withOnyx({ + // @ts-expect-error: ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS walletAdditionalDetails: { key: ONYXKEYS.WALLET_ADDITIONAL_DETAILS, }, - }), -)(AdditionalDetailsStep); + })(AdditionalDetailsStep), +); diff --git a/src/pages/EnablePayments/EnablePaymentsPage.js b/src/pages/EnablePayments/EnablePaymentsPage.tsx similarity index 76% rename from src/pages/EnablePayments/EnablePaymentsPage.js rename to src/pages/EnablePayments/EnablePaymentsPage.tsx index 257eab1d38d3..1384875fe031 100644 --- a/src/pages/EnablePayments/EnablePaymentsPage.js +++ b/src/pages/EnablePayments/EnablePaymentsPage.tsx @@ -1,6 +1,6 @@ import React, {useEffect} from 'react'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {OnyxEntry} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -11,34 +11,34 @@ import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {UserWallet} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import ActivateStep from './ActivateStep'; import AdditionalDetailsStep from './AdditionalDetailsStep'; import FailedKYC from './FailedKYC'; // Steps import OnfidoStep from './OnfidoStep'; import TermsStep from './TermsStep'; -import userWalletPropTypes from './userWalletPropTypes'; -const propTypes = { +type EnablePaymentsPageOnyxProps = { /** The user's wallet */ - userWallet: userWalletPropTypes, + userWallet: OnyxEntry; }; -const defaultProps = { - userWallet: {}, -}; +type EnablePaymentsPageProps = EnablePaymentsPageOnyxProps; -function EnablePaymentsPage({userWallet}) { +function EnablePaymentsPage({userWallet}: EnablePaymentsPageProps) { const {translate} = useLocalize(); const {isOffline} = useNetwork(); - const {isPendingOnfidoResult, hasFailedOnfido} = userWallet; + const {isPendingOnfidoResult, hasFailedOnfido} = userWallet ?? {}; useEffect(() => { if (isOffline) { return; } + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if (isPendingOnfidoResult || hasFailedOnfido) { Navigation.navigate(ROUTES.SETTINGS_WALLET, CONST.NAVIGATION.TYPE.UP); return; @@ -47,18 +47,18 @@ function EnablePaymentsPage({userWallet}) { Wallet.openEnablePaymentsPage(); }, [isOffline, isPendingOnfidoResult, hasFailedOnfido]); - if (_.isEmpty(userWallet)) { + if (isEmptyObject(userWallet)) { return ; } return ( {() => { - if (userWallet.errorCode === CONST.WALLET.ERROR.KYC) { + if (userWallet?.errorCode === CONST.WALLET.ERROR.KYC) { return ( <> ({ userWallet: { key: ONYXKEYS.USER_WALLET, diff --git a/src/pages/EnablePayments/FailedKYC.js b/src/pages/EnablePayments/FailedKYC.tsx similarity index 65% rename from src/pages/EnablePayments/FailedKYC.js rename to src/pages/EnablePayments/FailedKYC.tsx index fc54ea9c1074..6b393229d62f 100644 --- a/src/pages/EnablePayments/FailedKYC.js +++ b/src/pages/EnablePayments/FailedKYC.tsx @@ -2,35 +2,31 @@ import React from 'react'; import {View} from 'react-native'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; -const propTypes = { - ...withLocalizePropTypes, -}; - -function FailedKYC(props) { +function FailedKYC() { + const {translate} = useLocalize(); const styles = useThemeStyles(); return ( - {props.translate('additionalDetailsStep.failedKYCTextBefore')} + {translate('additionalDetailsStep.failedKYCTextBefore')} {CONST.EMAIL.CONCIERGE} - {props.translate('additionalDetailsStep.failedKYCTextAfter')} + {translate('additionalDetailsStep.failedKYCTextAfter')} ); } -FailedKYC.propTypes = propTypes; FailedKYC.displayName = 'FailedKYC'; -export default withLocalize(FailedKYC); +export default FailedKYC; diff --git a/src/pages/EnablePayments/IdologyQuestions.js b/src/pages/EnablePayments/IdologyQuestions.tsx similarity index 68% rename from src/pages/EnablePayments/IdologyQuestions.js rename to src/pages/EnablePayments/IdologyQuestions.tsx index a0c202b0bbbc..6baea2158613 100644 --- a/src/pages/EnablePayments/IdologyQuestions.js +++ b/src/pages/EnablePayments/IdologyQuestions.tsx @@ -1,64 +1,48 @@ -import PropTypes from 'prop-types'; import React, {useState} from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {WalletAdditionalQuestionDetails} from 'src/types/onyx'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import type {Choice} from '@components/RadioButtons'; import SingleChoiceQuestion from '@components/SingleChoiceQuestion'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as BankAccounts from '@userActions/BankAccounts'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Errors} from '@src/types/onyx/OnyxCommon'; const MAX_SKIP = 1; const SKIP_QUESTION_TEXT = 'Skip Question'; -const propTypes = { +type IdologyQuestionsProps = { /** Questions returned by Idology */ /** example: [{"answer":["1251","6253","113","None of the above","Skip Question"],"prompt":"Which number goes with your address on MASONIC AVE?","type":"street.number.b"}, ...] */ - questions: PropTypes.arrayOf( - PropTypes.shape({ - prompt: PropTypes.string, - type: PropTypes.string, - answer: PropTypes.arrayOf(PropTypes.string), - }), - ), + questions: WalletAdditionalQuestionDetails[]; /** ID from Idology, referencing those questions */ - idNumber: PropTypes.string, - - walletAdditionalDetails: PropTypes.shape({ - /** Are we waiting for a response? */ - isLoading: PropTypes.bool, - - /** Any additional error message to show */ - errors: PropTypes.objectOf(PropTypes.string), - - /** What error do we need to handle */ - errorCode: PropTypes.string, - }), + idNumber: string; }; -const defaultProps = { - questions: [], - idNumber: '', - walletAdditionalDetails: {}, +type Answer = { + question: string; + answer: string; }; -function IdologyQuestions({questions, idNumber}) { +function IdologyQuestions({questions, idNumber}: IdologyQuestionsProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); const [shouldHideSkipAnswer, setShouldHideSkipAnswer] = useState(false); - const [userAnswers, setUserAnswers] = useState([]); + const [userAnswers, setUserAnswers] = useState([]); const currentQuestion = questions[currentQuestionIndex] || {}; - const possibleAnswers = _.filter( - _.map(currentQuestion.answer, (answer) => { + const possibleAnswers: Choice[] = currentQuestion.answer + .map((answer) => { if (shouldHideSkipAnswer && answer === SKIP_QUESTION_TEXT) { return; } @@ -67,15 +51,11 @@ function IdologyQuestions({questions, idNumber}) { label: answer, value: answer, }; - }), - ); + }) + .filter((answer): answer is Choice => answer !== undefined); - /** - * Put question answer in the state. - * @param {String} answer - */ - const chooseAnswer = (answer) => { - const tempAnswers = _.map(userAnswers, _.clone); + const chooseAnswer = (answer: string) => { + const tempAnswers: Answer[] = userAnswers.map((userAnswer) => ({...userAnswer})); tempAnswers[currentQuestionIndex] = {question: currentQuestion.type, answer}; @@ -90,11 +70,11 @@ function IdologyQuestions({questions, idNumber}) { return; } // Get the number of questions that were skipped by the user. - const skippedQuestionsCount = _.filter(userAnswers, (answer) => answer.answer === SKIP_QUESTION_TEXT).length; + const skippedQuestionsCount = userAnswers.filter((answer) => answer.answer === SKIP_QUESTION_TEXT).length; // We have enough answers, let's call expectID KBA to verify them if (userAnswers.length - skippedQuestionsCount >= questions.length - MAX_SKIP) { - const tempAnswers = _.map(userAnswers, _.clone); + const tempAnswers: Answer[] = userAnswers.map((answer) => ({...answer})); // Auto skip any remaining questions if (tempAnswers.length < questions.length) { @@ -112,8 +92,8 @@ function IdologyQuestions({questions, idNumber}) { } }; - const validate = (values) => { - const errors = {}; + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors: Errors = {}; if (!values.answer) { errors.answer = translate('additionalDetailsStep.selectAnswer'); } @@ -126,13 +106,13 @@ function IdologyQuestions({questions, idNumber}) { {translate('additionalDetailsStep.helpTextIdologyQuestions')} {translate('additionalDetailsStep.helpLink')} { + chooseAnswer(String(value)); + }} + onInputChange={() => {}} /> @@ -155,11 +138,5 @@ function IdologyQuestions({questions, idNumber}) { } IdologyQuestions.displayName = 'IdologyQuestions'; -IdologyQuestions.propTypes = propTypes; -IdologyQuestions.defaultProps = defaultProps; - -export default withOnyx({ - walletAdditionalDetails: { - key: ONYXKEYS.WALLET_ADDITIONAL_DETAILS, - }, -})(IdologyQuestions); + +export default IdologyQuestions; diff --git a/src/pages/EnablePayments/OnfidoPrivacy.js b/src/pages/EnablePayments/OnfidoPrivacy.tsx similarity index 58% rename from src/pages/EnablePayments/OnfidoPrivacy.js rename to src/pages/EnablePayments/OnfidoPrivacy.tsx index 8de8bdb4bf07..cf6e6837df16 100644 --- a/src/pages/EnablePayments/OnfidoPrivacy.js +++ b/src/pages/EnablePayments/OnfidoPrivacy.tsx @@ -1,54 +1,57 @@ -import lodashGet from 'lodash/get'; import React, {useRef} from 'react'; import {View} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import type {ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {OnyxEntry} from 'react-native-onyx'; import FixedFooter from '@components/FixedFooter'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import FormScrollView from '@components/FormScrollView'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as BankAccounts from '@userActions/BankAccounts'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import walletOnfidoDataPropTypes from './walletOnfidoDataPropTypes'; +import type {WalletOnfido} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; -const propTypes = { - /** Stores various information used to build the UI and call any APIs */ - walletOnfidoData: walletOnfidoDataPropTypes, - - ...withLocalizePropTypes, +const DEFAULT_WALLET_ONFIDO_DATA = { + applicantID: '', + sdkToken: '', + loading: false, + errors: {}, + fixableErrors: [], + hasAcceptedPrivacyPolicy: false, }; -const defaultProps = { - walletOnfidoData: { - applicantID: '', - sdkToken: '', - loading: false, - errors: {}, - fixableErrors: [], - hasAcceptedPrivacyPolicy: false, - }, +type OnfidoPrivacyOnyxProps = { + /** Stores various information used to build the UI and call any APIs */ + walletOnfidoData: OnyxEntry; }; -function OnfidoPrivacy({walletOnfidoData, translate, form}) { +type OnfidoPrivacyProps = OnfidoPrivacyOnyxProps; + +function OnfidoPrivacy({walletOnfidoData = DEFAULT_WALLET_ONFIDO_DATA}: OnfidoPrivacyProps) { + const {translate} = useLocalize(); + const formRef = useRef(null); const styles = useThemeStyles(); + if (!walletOnfidoData) { + return; + } const {isLoading = false, hasAcceptedPrivacyPolicy} = walletOnfidoData; - const formRef = useRef(null); - const openOnfidoFlow = () => { BankAccounts.openOnfidoFlow(); }; - const onfidoError = ErrorUtils.getLatestErrorMessage(walletOnfidoData) || ''; - const onfidoFixableErrors = lodashGet(walletOnfidoData, 'fixableErrors', []); - if (_.isArray(onfidoError)) { - onfidoError[0] += !_.isEmpty(onfidoFixableErrors) ? `\n${onfidoFixableErrors.join('\n')}` : ''; + const onfidoError = ErrorUtils.getLatestErrorMessage(walletOnfidoData) ?? ''; + const onfidoFixableErrors = walletOnfidoData?.fixableErrors ?? []; + if (Array.isArray(onfidoError)) { + onfidoError[0] += !isEmptyObject(onfidoFixableErrors) ? `\n${onfidoFixableErrors.join('\n')}` : ''; } return ( @@ -59,11 +62,11 @@ function OnfidoPrivacy({walletOnfidoData, translate, form}) { {translate('onfidoStep.acceptTerms')} - {translate('onfidoStep.facialScan')} + {translate('onfidoStep.facialScan')} {', '} - {translate('common.privacy')} + {translate('common.privacy')} {` ${translate('common.and')} `} - {translate('common.termsOfService')}. + {translate('common.termsOfService')}. @@ -72,7 +75,7 @@ function OnfidoPrivacy({walletOnfidoData, translate, form}) { isAlertVisible={Boolean(onfidoError)} onSubmit={openOnfidoFlow} onFixTheErrorsLinkPressed={() => { - form.scrollTo({y: 0, animated: true}); + formRef.current?.scrollTo({y: 0, animated: true}); }} message={onfidoError} isLoading={isLoading} @@ -87,18 +90,13 @@ function OnfidoPrivacy({walletOnfidoData, translate, form}) { ); } -OnfidoPrivacy.propTypes = propTypes; -OnfidoPrivacy.defaultProps = defaultProps; OnfidoPrivacy.displayName = 'OnfidoPrivacy'; -export default compose( - withLocalize, - withOnyx({ - walletOnfidoData: { - key: ONYXKEYS.WALLET_ONFIDO, +export default withOnyx({ + walletOnfidoData: { + key: ONYXKEYS.WALLET_ONFIDO, - // Let's get a new onfido token each time the user hits this flow (as it should only be once) - initWithStoredValues: false, - }, - }), -)(OnfidoPrivacy); + // Let's get a new onfido token each time the user hits this flow (as it should only be once) + initWithStoredValues: false, + }, +})(OnfidoPrivacy); diff --git a/src/pages/EnablePayments/OnfidoStep.js b/src/pages/EnablePayments/OnfidoStep.tsx similarity index 68% rename from src/pages/EnablePayments/OnfidoStep.js rename to src/pages/EnablePayments/OnfidoStep.tsx index d6279020bca9..46c6e1c8e6ed 100644 --- a/src/pages/EnablePayments/OnfidoStep.js +++ b/src/pages/EnablePayments/OnfidoStep.tsx @@ -1,7 +1,9 @@ import React, {useCallback} from 'react'; import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +// @ts-expect-error TODO: Remove this once Onfido (https://github.com/Expensify/App/issues/25136) is migrated to TypeScript. import Onfido from '@components/Onfido'; import useLocalize from '@hooks/useLocalize'; import Growl from '@libs/Growl'; @@ -10,25 +12,25 @@ import * as BankAccounts from '@userActions/BankAccounts'; import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {WalletOnfido} from '@src/types/onyx'; import OnfidoPrivacy from './OnfidoPrivacy'; -import walletOnfidoDataPropTypes from './walletOnfidoDataPropTypes'; -const propTypes = { - /** Stores various information used to build the UI and call any APIs */ - walletOnfidoData: walletOnfidoDataPropTypes, +const DEFAULT_WALLET_ONFIDO_DATA = { + loading: false, + hasAcceptedPrivacyPolicy: false, }; -const defaultProps = { - walletOnfidoData: { - loading: false, - hasAcceptedPrivacyPolicy: false, - }, +type OnfidoStepOnyxProps = { + /** Stores various information used to build the UI and call any APIs */ + walletOnfidoData: OnyxEntry; }; -function OnfidoStep({walletOnfidoData}) { +type OnfidoStepProps = OnfidoStepOnyxProps; + +function OnfidoStep({walletOnfidoData = DEFAULT_WALLET_ONFIDO_DATA}: OnfidoStepProps) { const {translate} = useLocalize(); - const shouldShowOnfido = walletOnfidoData.hasAcceptedPrivacyPolicy && !walletOnfidoData.isLoading && !walletOnfidoData.error && walletOnfidoData.sdkToken; + const shouldShowOnfido = walletOnfidoData?.hasAcceptedPrivacyPolicy && !walletOnfidoData.isLoading && !walletOnfidoData.errors && walletOnfidoData.sdkToken; const goBack = useCallback(() => { Navigation.goBack(); @@ -43,15 +45,16 @@ function OnfidoStep({walletOnfidoData}) { }, [translate]); const verifyIdentity = useCallback( + // @ts-expect-error TODO: Remove this once Onfido (https://github.com/Expensify/App/issues/25136) is migrated to TypeScript. (data) => { BankAccounts.verifyIdentity({ onfidoData: JSON.stringify({ ...data, - applicantID: walletOnfidoData.applicantID, + applicantID: walletOnfidoData?.applicantID, }), }); }, - [walletOnfidoData.applicantID], + [walletOnfidoData?.applicantID], ); return ( @@ -69,18 +72,16 @@ function OnfidoStep({walletOnfidoData}) { onSuccess={verifyIdentity} /> ) : ( - + )} ); } -OnfidoStep.propTypes = propTypes; -OnfidoStep.defaultProps = defaultProps; OnfidoStep.displayName = 'OnfidoStep'; -export default withOnyx({ +export default withOnyx({ walletOnfidoData: { key: ONYXKEYS.WALLET_ONFIDO, diff --git a/src/pages/EnablePayments/TermsPage/LongTermsForm.js b/src/pages/EnablePayments/TermsPage/LongTermsForm.tsx similarity index 98% rename from src/pages/EnablePayments/TermsPage/LongTermsForm.js rename to src/pages/EnablePayments/TermsPage/LongTermsForm.tsx index fad19c5ecf6f..81d18c5dfc44 100644 --- a/src/pages/EnablePayments/TermsPage/LongTermsForm.js +++ b/src/pages/EnablePayments/TermsPage/LongTermsForm.tsx @@ -1,6 +1,5 @@ import React from 'react'; import {View} from 'react-native'; -import _ from 'underscore'; import CollapsibleSection from '@components/CollapsibleSection'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -66,7 +65,7 @@ function LongTermsForm() { ]; const getLongTermsSections = () => - _.map(termsData, (section, index) => ( + termsData.map((section, index) => ( // eslint-disable-next-line react/no-array-index-key @@ -105,7 +104,6 @@ function LongTermsForm() { ; }; -const defaultProps = { - userWallet: {}, -}; - -function ShortTermsForm(props) { +function ShortTermsForm(props: ShortTermsFormProps) { const styles = useThemeStyles(); const {translate, numberFormat} = useLocalize(); return ( @@ -25,7 +22,9 @@ function ShortTermsForm(props) { {translate('termsStep.shortTermsForm.expensifyPaymentsAccount', { walletProgram: - props.userWallet.walletProgramID === CONST.WALLET.MTL_WALLET_PROGRAM_ID ? CONST.WALLET.PROGRAM_ISSUERS.EXPENSIFY_PAYMENTS : CONST.WALLET.PROGRAM_ISSUERS.BANCORP_BANK, + props.userWallet?.walletProgramID === CONST.WALLET.MTL_WALLET_PROGRAM_ID + ? CONST.WALLET.PROGRAM_ISSUERS.EXPENSIFY_PAYMENTS + : CONST.WALLET.PROGRAM_ISSUERS.BANCORP_BANK, })} @@ -150,8 +149,6 @@ function ShortTermsForm(props) { ); } -ShortTermsForm.propTypes = propTypes; -ShortTermsForm.defaultProps = defaultProps; ShortTermsForm.displayName = 'ShortTermsForm'; export default ShortTermsForm; diff --git a/src/pages/EnablePayments/TermsStep.js b/src/pages/EnablePayments/TermsStep.tsx similarity index 69% rename from src/pages/EnablePayments/TermsStep.js rename to src/pages/EnablePayments/TermsStep.tsx index a55816d207be..916a5200a2e0 100644 --- a/src/pages/EnablePayments/TermsStep.js +++ b/src/pages/EnablePayments/TermsStep.tsx @@ -1,36 +1,30 @@ import React, {useEffect, useState} from 'react'; import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import CheckboxWithLabel from '@components/CheckboxWithLabel'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as BankAccounts from '@userActions/BankAccounts'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {UserWallet, WalletTerms} from '@src/types/onyx'; import LongTermsForm from './TermsPage/LongTermsForm'; import ShortTermsForm from './TermsPage/ShortTermsForm'; -import userWalletPropTypes from './userWalletPropTypes'; -import walletTermsPropTypes from './walletTermsPropTypes'; - -const propTypes = { - /** The user's wallet */ - userWallet: userWalletPropTypes, +type TermsStepOnyxProps = { /** Comes from Onyx. Information about the terms for the wallet */ - walletTerms: walletTermsPropTypes, - - ...withLocalizePropTypes, + walletTerms: OnyxEntry; }; -const defaultProps = { - userWallet: {}, - walletTerms: {}, +type TermsStepProps = TermsStepOnyxProps & { + /** The user's wallet */ + userWallet: OnyxEntry; }; function HaveReadAndAgreeLabel() { @@ -39,7 +33,7 @@ function HaveReadAndAgreeLabel() { return ( {`${translate('termsStep.haveReadAndAgree')}`} - {`${translate('termsStep.electronicDisclosures')}.`} + {`${translate('termsStep.electronicDisclosures')}.`} ); } @@ -50,20 +44,21 @@ function AgreeToTheLabel() { return ( {`${translate('termsStep.agreeToThe')} `} - {`${translate('common.privacy')} `} + {`${translate('common.privacy')} `} {`${translate('common.and')} `} - {`${translate('termsStep.walletAgreement')}.`} + {`${translate('termsStep.walletAgreement')}.`} ); } -function TermsStep(props) { +function TermsStep(props: TermsStepProps) { const styles = useThemeStyles(); const [hasAcceptedDisclosure, setHasAcceptedDisclosure] = useState(false); const [hasAcceptedPrivacyPolicyAndWalletAgreement, setHasAcceptedPrivacyPolicyAndWalletAgreement] = useState(false); const [error, setError] = useState(false); + const {translate} = useLocalize(); - const errorMessage = error ? 'common.error.acceptTerms' : ErrorUtils.getLatestErrorMessage(props.walletTerms) || ''; + const errorMessage = error ? 'common.error.acceptTerms' : ErrorUtils.getLatestErrorMessage(props.walletTerms ?? {}) ?? ''; const toggleDisclosure = () => { setHasAcceptedDisclosure(!hasAcceptedDisclosure); @@ -84,7 +79,7 @@ function TermsStep(props) { return ( <> - + { if (!hasAcceptedDisclosure || !hasAcceptedPrivacyPolicyAndWalletAgreement) { setError(true); @@ -114,12 +109,12 @@ function TermsStep(props) { setError(false); BankAccounts.acceptWalletTerms({ hasAcceptedTerms: hasAcceptedDisclosure && hasAcceptedPrivacyPolicyAndWalletAgreement, - reportID: props.walletTerms.chatReportID, + reportID: props.walletTerms?.chatReportID ?? '', }); }} message={errorMessage} isAlertVisible={error || Boolean(errorMessage)} - isLoading={!!props.walletTerms.isLoading} + isLoading={!!props.walletTerms?.isLoading} containerStyles={[styles.mh0, styles.mv4]} /> @@ -128,13 +123,9 @@ function TermsStep(props) { } TermsStep.displayName = 'TermsPage'; -TermsStep.propTypes = propTypes; -TermsStep.defaultProps = defaultProps; -export default compose( - withLocalize, - withOnyx({ - walletTerms: { - key: ONYXKEYS.WALLET_TERMS, - }, - }), -)(TermsStep); + +export default withOnyx({ + walletTerms: { + key: ONYXKEYS.WALLET_TERMS, + }, +})(TermsStep); diff --git a/src/pages/EnablePayments/walletAdditionalDetailsDraftPropTypes.js b/src/pages/EnablePayments/walletAdditionalDetailsDraftPropTypes.js deleted file mode 100644 index 747fa82f9fa3..000000000000 --- a/src/pages/EnablePayments/walletAdditionalDetailsDraftPropTypes.js +++ /dev/null @@ -1,13 +0,0 @@ -import PropTypes from 'prop-types'; - -export default PropTypes.shape({ - legalFirstName: PropTypes.string, - legalLastName: PropTypes.string, - addressStreet: PropTypes.string, - addressCity: PropTypes.string, - addressState: PropTypes.string, - addressZip: PropTypes.string, - phoneNumber: PropTypes.string, - dob: PropTypes.string, - ssn: PropTypes.string, -}); diff --git a/src/pages/EnablePayments/walletOnfidoDataPropTypes.js b/src/pages/EnablePayments/walletOnfidoDataPropTypes.js deleted file mode 100644 index cedc1f2777b5..000000000000 --- a/src/pages/EnablePayments/walletOnfidoDataPropTypes.js +++ /dev/null @@ -1,21 +0,0 @@ -import PropTypes from 'prop-types'; - -export default PropTypes.shape({ - /** Unique identifier returned from openOnfidoFlow then re-sent to ActivateWallet with Onfido response data */ - applicantID: PropTypes.string, - - /** Token used to initialize the Onfido SDK token */ - sdkToken: PropTypes.string, - - /** Loading state to provide feedback when we are waiting for a request to finish */ - loading: PropTypes.bool, - - /** Error message to inform the user of any problem that might occur */ - error: PropTypes.string, - - /** A list of Onfido errors that the user can fix in order to attempt the Onfido flow again */ - fixableErrors: PropTypes.arrayOf(PropTypes.string), - - /** Whether the user has accepted the privacy policy of Onfido or not */ - hasAcceptedPrivacyPolicy: PropTypes.bool, -}); diff --git a/src/pages/EnablePayments/walletTermsPropTypes.js b/src/pages/EnablePayments/walletTermsPropTypes.js deleted file mode 100644 index 4420a2dd0861..000000000000 --- a/src/pages/EnablePayments/walletTermsPropTypes.js +++ /dev/null @@ -1,18 +0,0 @@ -import PropTypes from 'prop-types'; -import _ from 'underscore'; -import CONST from '@src/CONST'; - -/** Prop types related to the Terms step of KYC flow */ -export default PropTypes.shape({ - /** Any error message to show */ - errors: PropTypes.objectOf(PropTypes.string), - - /** The source that triggered the KYC wall */ - source: PropTypes.oneOf(_.values(CONST.KYC_WALL_SOURCE)), - - /** When the user accepts the Wallet's terms in order to pay an IOU, this is the ID of the chatReport the IOU is linked to */ - chatReportID: PropTypes.string, - - /** Boolean to indicate whether the submission of wallet terms is being processed */ - isLoading: PropTypes.bool, -}); diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index 666e233196b6..e8aacadc6e1a 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -185,7 +185,7 @@ function BankAccountStep(props) { )} - {props.translate('common.privacy')} + {props.translate('common.privacy')} Link.openExternalLink('https://community.expensify.com/discussion/5677/deep-dive-how-expensify-protects-your-information/')} style={[styles.flexRow, styles.alignItemsCenter]} diff --git a/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx b/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx index 208f7a19ddf3..03a178f186ee 100644 --- a/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx +++ b/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx @@ -11,6 +11,7 @@ import useLocalize from '@hooks/useLocalize'; 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 INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; import type {ReimbursementAccount} from '@src/types/onyx'; @@ -62,7 +63,7 @@ function TermsAndConditionsLabel() { return ( {translate('common.iAcceptThe')} - {`${translate('completeVerificationStep.termsAndConditions')}`} + {`${translate('completeVerificationStep.termsAndConditions')}`} ); } diff --git a/src/types/form/AdditionalDetailStepForm.ts b/src/types/form/AdditionalDetailStepForm.ts new file mode 100644 index 000000000000..ec2838025d54 --- /dev/null +++ b/src/types/form/AdditionalDetailStepForm.ts @@ -0,0 +1,36 @@ +import type {ValueOf} from 'type-fest'; +import type Form from './Form'; + +const INPUT_IDS = { + LEGAL_FIRST_NAME: 'legalFirstName', + LEGAL_LAST_NAME: 'legalLastName', + PHONE_NUMBER: 'phoneNumber', + ADDRESS_STREET: 'addressStreet', + ADDRESS_CITY: 'addressCity', + ADDRESS_ZIP_CODE: 'addressZipCode', + ADDRESS_STATE: 'addressState', + DOB: 'dob', + SSN: 'ssn', + ANSWER: 'answer', +} as const; + +type InputID = ValueOf; + +type AdditionalDetailStepForm = Form< + InputID, + { + [INPUT_IDS.LEGAL_FIRST_NAME]: string; + [INPUT_IDS.LEGAL_LAST_NAME]: string; + [INPUT_IDS.PHONE_NUMBER]: string; + [INPUT_IDS.ADDRESS_STREET]: string; + [INPUT_IDS.ADDRESS_CITY]: string; + [INPUT_IDS.ADDRESS_ZIP_CODE]: string; + [INPUT_IDS.ADDRESS_STATE]: string; + [INPUT_IDS.DOB]: string; + [INPUT_IDS.SSN]: string; + [INPUT_IDS.ANSWER]: string; + } +>; + +export type {AdditionalDetailStepForm}; +export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index ddd460e46eac..d03b6cba974a 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -38,6 +38,7 @@ export type {WorkspaceCategoryForm} from './WorkspaceCategoryForm'; export type {WorkspaceSettingsForm} from './WorkspaceSettingsForm'; export type {ReportPhysicalCardForm} from './ReportPhysicalCardForm'; export type {WorkspaceDescriptionForm} from './WorkspaceDescriptionForm'; +export type {AdditionalDetailStepForm} from './AdditionalDetailStepForm'; export type {PolicyTagNameForm} from './PolicyTagNameForm'; export type {WorkspaceNewTaxForm} from './WorkspaceNewTaxForm'; export type {WorkspaceTagCreateForm} from './WorkspaceTagCreateForm';