diff --git a/src/components/TextLink.js b/src/components/TextLink.js index b80007ab1c2e..fd3596f83ba1 100644 --- a/src/components/TextLink.js +++ b/src/components/TextLink.js @@ -1,22 +1,31 @@ import _ from 'underscore'; import React from 'react'; import PropTypes from 'prop-types'; -import {Text, Pressable, Linking} from 'react-native'; +import {Pressable, Linking} from 'react-native'; +import Text from './Text'; import styles from '../styles/styles'; const propTypes = { /** Link to open in new tab */ - href: PropTypes.string.isRequired, + href: PropTypes.string, /** Text content child */ - children: PropTypes.string.isRequired, + children: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.array, + ]).isRequired, /** Additional style props */ style: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]), + + /** Overwrites the default link behavior with a custom callback */ + onPress: PropTypes.func, }; const defaultProps = { + href: '', style: [], + onPress: undefined, }; const TextLink = (props) => { @@ -25,6 +34,11 @@ const TextLink = (props) => { { e.preventDefault(); + if (props.onPress) { + props.onPress(); + return; + } + Linking.openURL(props.href); }} accessibilityRole="link" diff --git a/src/languages/en.js b/src/languages/en.js index 38d0b6b12d8f..5d6d6ddbb897 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -47,6 +47,7 @@ export default { isRequiredField: 'is a required field', whatThis: 'What\'s this?', invite: 'Invite', + iAcceptThe: 'I accept the ', passwordCannotBeBlank: 'Password cannot be blank', }, attachmentPicker: { @@ -287,7 +288,6 @@ export default { toGetStarted: 'To get started with the Expensify Card, you first need to add a bank account.', plaidBodyCopy: 'Give your employees an easier way to pay - and get paid back - for company expenses.', checkHelpLine: 'Your routing number and account number can be found on a check for the account.', - iAcceptThe: 'I accept the ', hasPhoneLoginError: 'To add a verified bank account please ensure your primary login is a valid email and try again. You can add your phone number as a secondary login.', hasBeenThrottledError: ({fromNow}) => `For security reasons, we're taking a break from bank account setup so you can double-check your company information. Please try again ${fromNow}. Sorry!`, }, @@ -363,6 +363,19 @@ export default { ssnLast4: 'Last 4 Digits of SSN', isAuthorized: 'I am authorized to use my company bank account for business spend', }, + beneficialOwnersStep: { + beneficialOwners: 'Beneficial Owners', + additionalInformation: 'Additional Information', + checkAllThatApply: '(check all that apply, otherwise leave blank)', + iOwnMoreThan25Percent: 'I own more than 25% of ', + someoneOwnsMoreThan25Percent: 'Somebody else owns more than 25% of ', + additionalOwner: 'Additional Beneficial Owner', + removeOwner: 'Remove this beneficial owner', + addAnotherIndividual: 'Add another individual who owns more than 25% of ', + agreement: 'Agreement:', + termsAndConditions: 'terms and conditions', + certifyTrueAndAccurate: 'I certify that the information provided is true and accurate', + }, session: { offlineMessage: 'Looks like you\'re not connected to internet. Can you check your connection and try again?', }, diff --git a/src/languages/es.js b/src/languages/es.js index f3a30f8b526c..8a0fd6565c1e 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -43,6 +43,7 @@ export default { isRequiredField: 'es un campo obligatorio', whatThis: '¿Qué es esto?', invite: 'Invitar', + iAcceptThe: 'Acepto los ', passwordCannotBeBlank: 'La contraseña no puede estar vacía', }, attachmentPicker: { @@ -280,7 +281,6 @@ export default { toGetStarted: 'Para comenzar con la tarjeta Expensify, primero debe agregar una cuenta bancaria.', plaidBodyCopy: 'Ofrezca a sus empleados una forma más sencilla de pagar - y recuperar - los gastos de la empresa.', checkHelpLine: 'Su número de ruta y número de cuenta se pueden encontrar en un cheque para la cuenta.', - iAcceptThe: 'Acepto los ', hasPhoneLoginError: 'Para agregar una cuenta bancaria verificada, asegúrese de que su inicio de sesión principal sea un correo electrónico válido y vuelva a intentarlo. Puede agregar su número de teléfono como inicio de sesión secundario.', hasBeenThrottledError: ({fromNow}) => `Por razones de seguridad, nos tomamos un descanso de la configuración de la cuenta bancaria para que pueda verificar la información de su empresa. Inténtalo de nuevo ${fromNow}. ¡Lo siento!`, }, @@ -304,6 +304,19 @@ export default { noPhoneNumber: 'Por favor escribe un número de teléfono que incluya el código de país e.g +447814266907', maxParticipantsReached: 'Has llegado al número máximo de participantes para un grupo.', }, + beneficialOwnersStep: { + beneficialOwners: 'Beneficial Owners', + additionalInformation: 'Additional Information', + checkAllThatApply: '(check all that apply, otherwise leave blank)', + iOwnMoreThan25Percent: 'I own more than 25% of ', + someoneOwnsMoreThan25Percent: 'Somebody else owns more than 25% of ', + additionalOwner: 'Additional Beneficial Owner', + removeOwner: 'Remove this beneficial owner', + addAnotherIndividual: 'Add another individual who owns more than 25% of ', + agreement: 'Agreement:', + termsAndConditions: 'terms and conditions', + certifyTrueAndAccurate: 'I certify that the information provided is true and accurate', + }, session: { offlineMessage: 'Parece que no estás conectado a internet. Comprueba tu conexión e inténtalo de nuevo.', }, diff --git a/src/pages/ReimbursementAccount/ACHContractStep.js b/src/pages/ReimbursementAccount/ACHContractStep.js deleted file mode 100644 index 2434bd9e9f3e..000000000000 --- a/src/pages/ReimbursementAccount/ACHContractStep.js +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; -import {View} from 'react-native'; - -const ACHContractStep = () => ; -export default ACHContractStep; diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index 86b1ab045ccd..15674eeee1aa 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -199,7 +199,7 @@ class BankAccountStep extends React.Component { LabelComponent={() => ( - {this.props.translate('bankAccount.iAcceptThe')} + {this.props.translate('common.iAcceptThe')} {`Expensify ${this.props.translate('common.termsOfService')}`} diff --git a/src/pages/ReimbursementAccount/BeneficialOwnersStep.js b/src/pages/ReimbursementAccount/BeneficialOwnersStep.js new file mode 100644 index 000000000000..1b03c18ca5d1 --- /dev/null +++ b/src/pages/ReimbursementAccount/BeneficialOwnersStep.js @@ -0,0 +1,198 @@ +import _ from 'underscore'; +import React from 'react'; +import PropTypes from 'prop-types'; +import {ScrollView, View} from 'react-native'; +import Text from '../../components/Text'; +import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; +import styles from '../../styles/styles'; +import CheckboxWithLabel from '../../components/CheckboxWithLabel'; +import TextLink from '../../components/TextLink'; +import Button from '../../components/Button'; +import IdentityForm from './IdentityForm'; +import FixedFooter from '../../components/FixedFooter'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import {goToWithdrawalAccountSetupStep, setupWithdrawalAccount} from '../../libs/actions/BankAccounts'; +import Navigation from '../../libs/Navigation/Navigation'; +import CONST from '../../CONST'; + +const propTypes = { + companyName: PropTypes.string, + + ...withLocalizePropTypes, +}; + +const defaultProps = { + companyName: 'Company Name', +}; + +class BeneficialOwnersStep extends React.Component { + constructor(props) { + super(props); + + this.addBeneficialOwner = this.addBeneficialOwner.bind(this); + this.submit = this.submit.bind(this); + + this.state = { + ownsMoreThan25Percent: false, + hasOtherBeneficialOwners: false, + acceptTermsAndConditions: false, + certifyTrueInformation: false, + beneficialOwners: [], + }; + } + + removeBeneficialOwner(beneficialOwner) { + this.setState(prevState => ({beneficialOwners: _.without(prevState.beneficialOwners, beneficialOwner)})); + } + + addBeneficialOwner() { + this.setState(prevState => ({beneficialOwners: [...prevState.beneficialOwners, {}]})); + } + + /** + * @returns {Boolean} + */ + canAddMoreBeneficialOwners() { + return _.size(this.state.beneficialOwners) < 3 + || (_.size(this.state.beneficialOwners) === 3 && !this.state.ownsMoreThan25Percent); + } + + submit() { + setupWithdrawalAccount({...this.state}); + } + + render() { + return ( + <> + goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR)} + shouldShowBackButton + /> + + + + {`${this.props.translate('beneficialOwnersStep.additionalInformation')}: `} + + {this.props.translate('beneficialOwnersStep.checkAllThatApply')} + + this.setState(prevState => ({ + ownsMoreThan25Percent: !prevState.ownsMoreThan25Percent, + }))} + LabelComponent={() => ( + + {this.props.translate('beneficialOwnersStep.iOwnMoreThan25Percent')} + {this.props.companyName} + + )} + /> + { + this.setState((prevState) => { + const hasOtherBeneficialOwners = !prevState.hasOtherBeneficialOwners; + return { + hasOtherBeneficialOwners, + beneficialOwners: hasOtherBeneficialOwners && _.isEmpty(prevState.beneficialOwners) + ? [{}] + : prevState.beneficialOwners, + }; + }); + }} + LabelComponent={() => ( + + {this.props.translate('beneficialOwnersStep.someoneOwnsMoreThan25Percent')} + {this.props.companyName} + + )} + /> + {this.state.hasOtherBeneficialOwners && ( + + {_.map(this.state.beneficialOwners, (owner, index) => ( + + + {this.props.translate('beneficialOwnersStep.additionalOwner')} + + this.setState((prevState) => { + const beneficialOwners = [...prevState.beneficialOwners]; + beneficialOwners[index][fieldName] = value; + return {beneficialOwners}; + })} + values={{ + firstName: owner.firstName || '', + lastName: owner.lastName || '', + street: owner.street || '', + city: owner.city || '', + state: owner.state || '', + zipCode: owner.zipCode || '', + dob: owner.dob || '', + ssnLast4: owner.ssnLast4 || '', + }} + /> + {this.state.beneficialOwners.length > 1 && ( + this.removeBeneficialOwner(owner)}> + {this.props.translate('beneficialOwnersStep.removeOwner')} + + )} + + ))} + {this.canAddMoreBeneficialOwners() && ( + + {this.props.translate('beneficialOwnersStep.addAnotherIndividual')} + {this.props.companyName} + + )} + + )} + + {this.props.translate('beneficialOwnersStep.agreement')} + + this.setState(prevState => ({ + acceptTermsAndConditions: !prevState.acceptTermsAndConditions, + }))} + LabelComponent={() => ( + + {this.props.translate('common.iAcceptThe')} + + {`${this.props.translate('beneficialOwnersStep.termsAndConditions')}.`} + + + )} + /> + this.setState(prevState => ({ + certifyTrueInformation: !prevState.certifyTrueInformation, + }))} + LabelComponent={() => ( + {this.props.translate('beneficialOwnersStep.certifyTrueAndAccurate')} + )} + /> + + +