diff --git a/assets/images/bill.svg b/assets/images/bill.svg new file mode 100644 index 00000000000..b27e6776b0f --- /dev/null +++ b/assets/images/bill.svg @@ -0,0 +1,11 @@ + + + + + + diff --git a/assets/images/briefcase.svg b/assets/images/briefcase.svg new file mode 100644 index 00000000000..f73854bbb36 --- /dev/null +++ b/assets/images/briefcase.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/assets/images/circle-hourglass.svg b/assets/images/circle-hourglass.svg new file mode 100644 index 00000000000..1bba8b47410 --- /dev/null +++ b/assets/images/circle-hourglass.svg @@ -0,0 +1,10 @@ + + + + + + + diff --git a/assets/images/concierge.svg b/assets/images/concierge.svg new file mode 100644 index 00000000000..4b22f626a0e --- /dev/null +++ b/assets/images/concierge.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/assets/images/confetti-pop.gif b/assets/images/confetti-pop.gif new file mode 100644 index 00000000000..a4137b6e9dd Binary files /dev/null and b/assets/images/confetti-pop.gif differ diff --git a/assets/images/invoice.svg b/assets/images/invoice.svg new file mode 100644 index 00000000000..91391b0f9bc --- /dev/null +++ b/assets/images/invoice.svg @@ -0,0 +1,11 @@ + + + + + + diff --git a/assets/images/luggage.svg b/assets/images/luggage.svg new file mode 100644 index 00000000000..b7f8dc7fe93 --- /dev/null +++ b/assets/images/luggage.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/assets/images/paycheck.svg b/assets/images/paycheck.svg index 228faf7cee7..f81a5a9a86a 100644 --- a/assets/images/paycheck.svg +++ b/assets/images/paycheck.svg @@ -1,20 +1,17 @@ - + - - - - - + + + diff --git a/assets/images/product-illustrations/bank-arrow--pink.svg b/assets/images/product-illustrations/bank-arrow--pink.svg new file mode 100644 index 00000000000..c561bfd2790 --- /dev/null +++ b/assets/images/product-illustrations/bank-arrow--pink.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/product-illustrations/bank-mouse--green.svg b/assets/images/product-illustrations/bank-mouse--green.svg new file mode 100644 index 00000000000..99dfd1718c1 --- /dev/null +++ b/assets/images/product-illustrations/bank-mouse--green.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/product-illustrations/bank-user--green.svg b/assets/images/product-illustrations/bank-user--green.svg new file mode 100644 index 00000000000..676d05c3bc0 --- /dev/null +++ b/assets/images/product-illustrations/bank-user--green.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/product-illustrations/concierge--blue.svg b/assets/images/product-illustrations/concierge--blue.svg new file mode 100644 index 00000000000..d1d3fede1f6 --- /dev/null +++ b/assets/images/product-illustrations/concierge--blue.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/assets/images/product-illustrations/credit-cards--blue.svg b/assets/images/product-illustrations/credit-cards--blue.svg new file mode 100644 index 00000000000..008dbd20be3 --- /dev/null +++ b/assets/images/product-illustrations/credit-cards--blue.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/images/product-illustrations/invoice--orange.svg b/assets/images/product-illustrations/invoice--orange.svg new file mode 100644 index 00000000000..aebd5066066 --- /dev/null +++ b/assets/images/product-illustrations/invoice--orange.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/assets/images/product-illustrations/jewel-box--blue.svg b/assets/images/product-illustrations/jewel-box--blue.svg new file mode 100644 index 00000000000..b9d6a084bcb --- /dev/null +++ b/assets/images/product-illustrations/jewel-box--blue.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/product-illustrations/jewel-box--green.svg b/assets/images/product-illustrations/jewel-box--green.svg new file mode 100644 index 00000000000..ba1cade3dcc --- /dev/null +++ b/assets/images/product-illustrations/jewel-box--green.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/product-illustrations/jewel-box--pink.svg b/assets/images/product-illustrations/jewel-box--pink.svg new file mode 100644 index 00000000000..dd58151c913 --- /dev/null +++ b/assets/images/product-illustrations/jewel-box--pink.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/product-illustrations/jewel-box--yellow.svg b/assets/images/product-illustrations/jewel-box--yellow.svg new file mode 100644 index 00000000000..858d5b66688 --- /dev/null +++ b/assets/images/product-illustrations/jewel-box--yellow.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/product-illustrations/money-envelope--blue.svg b/assets/images/product-illustrations/money-envelope--blue.svg new file mode 100644 index 00000000000..199489af882 --- /dev/null +++ b/assets/images/product-illustrations/money-envelope--blue.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/images/product-illustrations/money-mouse--pink.svg b/assets/images/product-illustrations/money-mouse--pink.svg new file mode 100644 index 00000000000..72c21fc4675 --- /dev/null +++ b/assets/images/product-illustrations/money-mouse--pink.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + diff --git a/assets/images/product-illustrations/receipt--yellow.svg b/assets/images/product-illustrations/receipt--yellow.svg new file mode 100644 index 00000000000..f40f3e0a5aa --- /dev/null +++ b/assets/images/product-illustrations/receipt--yellow.svg @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/assets/images/product-illustrations/rocket--orange.svg b/assets/images/product-illustrations/rocket--orange.svg new file mode 100644 index 00000000000..a3bb9a67fb7 --- /dev/null +++ b/assets/images/product-illustrations/rocket--orange.svg @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/product-illustrations/tada--yellow.svg b/assets/images/product-illustrations/tada--yellow.svg new file mode 100644 index 00000000000..037baef7def --- /dev/null +++ b/assets/images/product-illustrations/tada--yellow.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/receipt-search.svg b/assets/images/receipt-search.svg new file mode 100644 index 00000000000..6272cfea321 --- /dev/null +++ b/assets/images/receipt-search.svg @@ -0,0 +1,15 @@ + + + + + + + diff --git a/assets/images/users.svg b/assets/images/users.svg index e74a77d7f4c..948998d2c35 100644 --- a/assets/images/users.svg +++ b/assets/images/users.svg @@ -13,12 +13,13 @@ - - - - - + + + + diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index ac19970dc63..4317a9ac6be 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -134,4 +134,7 @@ export default { // Notifies all tabs that they should sign out and clear storage. SHOULD_SIGN_OUT: 'shouldSignOut', + + // Set when we are loading payment methods + IS_LOADING_PAYMENT_METHODS: 'isLoadingPaymentMethods', }; diff --git a/src/ROUTES.js b/src/ROUTES.js index 860b1f597ef..71206888801 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -77,18 +77,29 @@ export default { LOGIN_WITH_VALIDATE_CODE_WORKSPACE_CARD: 'v/:accountID/:validateCode/workspace/:policyID/card', LOGIN_WITH_VALIDATE_CODE_2FA_WORKSPACE_CARD: 'v/:accountID/:validateCode/2fa/workspace/:policyID/card', ENABLE_PAYMENTS: 'enable-payments', - WORKSPACE: 'workspace', - WORKSPACE_CARD: ':policyID/card', - WORKSPACE_PEOPLE: ':policyID/people', WORKSPACE_NEW: 'workspace/new', - getWorkspaceCardRoute: policyID => `workspace/${policyID}/card`, - getWorkspacePeopleRoute: policyID => `workspace/${policyID}/people`, - getWorkspaceInviteRoute: policyID => `workspace/${policyID}/invite`, + WORKSPACE_INITIAL: 'workspace/:policyID', WORKSPACE_INVITE: 'workspace/:policyID/invite', + WORKSPACE_SETTINGS: 'workspace/:policyID/settings', + WORKSPACE_CARD: 'workspace/:policyID/card', + WORKSPACE_REIMBURSE: 'workspace/:policyID/reimburse', + WORKSPACE_BILLS: 'workspace/:policyID/bills', + WORKSPACE_INVOICES: 'workspace/:policyID/invoices', + WORKSPACE_TRAVEL: 'workspace/:policyID/travel', + WORKSPACE_MEMBERS: 'workspace/:policyID/members', + WORKSPACE_BANK_ACCOUNT: 'workspace/:policyID/bank-account', + getWorkspaceInitialRoute: policyID => `workspace/${policyID}`, + getWorkspaceInviteRoute: policyID => `workspace/${policyID}/invite`, + getWorkspaceSettingsRoute: policyID => `workspace/${policyID}/settings`, + getWorkspaceCardRoute: policyID => `workspace/${policyID}/card`, + getWorkspaceReimburseRoute: policyID => `workspace/${policyID}/reimburse`, + getWorkspaceBillsRoute: policyID => `workspace/${policyID}/bills`, + getWorkspaceInvoicesRoute: policyID => `workspace/${policyID}/invoices`, + getWorkspaceTravelRoute: policyID => `workspace/${policyID}/travel`, + getWorkspaceMembersRoute: policyID => `workspace/${policyID}/members`, + getWorkspaceBankAccountRoute: policyID => `workspace/${policyID}/bank-account`, getRequestCallRoute: taskID => `request-call/${taskID}`, REQUEST_CALL: 'request-call/:taskID', - getWorkspaceEditorRoute: policyID => `workspace/${policyID}/edit`, - WORKSPACE_EDITOR: 'workspace/:policyID/edit', /** * @param {String} route diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js index 180bf9ea39b..b88e2d58b09 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.js @@ -138,8 +138,11 @@ class AddPlaidBankAccount extends React.Component { } const account = this.getAccounts()[this.state.selectedIndex]; + const bankName = lodashGet(this.props.plaidBankAccounts, 'bankName'); this.props.onSubmit({ - account, plaidLinkToken: this.props.plaidLinkToken, + bankName, + account, + plaidLinkToken: this.props.plaidLinkToken, }); } diff --git a/src/components/AddressSearch.js b/src/components/AddressSearch.js index 3769cd2c0cf..536e144f090 100644 --- a/src/components/AddressSearch.js +++ b/src/components/AddressSearch.js @@ -108,7 +108,7 @@ class AddressSearch extends React.Component { styles.overflowAuto, ], description: [styles.googleSearchText], - separator: [styles.googleSearchSeperator], + separator: [styles.googleSearchSeparator], }} /> ); diff --git a/src/components/Avatar.js b/src/components/Avatar.js index aa56b6262d2..727ea7bbc1d 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -3,6 +3,7 @@ import {Image, View} from 'react-native'; import PropTypes from 'prop-types'; import styles from '../styles/styles'; import RoomAvatar from './RoomAvatar'; +import stylePropTypes from '../styles/stylePropTypes'; const propTypes = { /** Url source for the avatar */ @@ -12,7 +13,7 @@ const propTypes = { imageStyles: PropTypes.arrayOf(PropTypes.object), /** Extra styles to pass to View wrapper */ - containerStyles: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]), + containerStyles: stylePropTypes, /** Set the size of Avatar */ size: PropTypes.oneOf(['default', 'small']), diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index b677a9ab5f2..f0f703bc395 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -17,13 +17,14 @@ import withLocalize, {withLocalizePropTypes} from './withLocalize'; import variables from '../styles/variables'; import CONST from '../CONST'; import SpinningIndicatorAnimation from '../styles/animation/SpinningIndicatorAnimation'; +import stylePropTypes from '../styles/stylePropTypes'; const propTypes = { /** Avatar URL to display */ avatarURL: PropTypes.string, /** Additional style props */ - style: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]), + style: stylePropTypes, /** Executed once an image has been selected */ onImageSelected: PropTypes.func, diff --git a/src/components/CopyTextToClipboard.js b/src/components/CopyTextToClipboard.js new file mode 100644 index 00000000000..ef721eaac69 --- /dev/null +++ b/src/components/CopyTextToClipboard.js @@ -0,0 +1,65 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Text from './Text'; +import {Checkmark, Clipboard as ClipboardIcon} from './Icon/Expensicons'; +import Clipboard from '../libs/Clipboard'; +import Icon from './Icon'; +import styles from '../styles/styles'; + +const propTypes = { + /** The text to display and copy to the clipboard */ + text: PropTypes.string.isRequired, + + /** Styles to apply to the text */ + textStyles: PropTypes.arrayOf(PropTypes.object), +}; + +const defaultProps = { + textStyles: [], +}; + +class CopyTextToClipboard extends React.Component { + constructor(props) { + super(props); + + this.copyToClipboard = this.copyToClipboard.bind(this); + + this.state = { + showCheckmark: false, + }; + } + + componentWillUnmount() { + // Clear the interval when the component unmounts so that if the user navigates + // away quickly, then setState() won't try to update a component that's been unmounted + clearInterval(this.showCheckmarkInterval); + } + + copyToClipboard() { + Clipboard.setString(this.props.text); + this.setState({showCheckmark: true}, () => { + this.showCheckmarkInterval = setTimeout(() => { + this.setState({showCheckmark: false}); + }, 2000); + }); + } + + render() { + return ( + + {this.props.text} + {this.state.showCheckmark + ? + : } + + ); + } +} + +CopyTextToClipboard.propTypes = propTypes; +CopyTextToClipboard.defaultProps = defaultProps; + +export default CopyTextToClipboard; diff --git a/src/components/Icon/Expensicons.js b/src/components/Icon/Expensicons.js index 8555edc7395..ac2cd32c0c8 100644 --- a/src/components/Icon/Expensicons.js +++ b/src/components/Icon/Expensicons.js @@ -3,14 +3,18 @@ import Apple from '../../../assets/images/apple.svg'; import ArrowRight from '../../../assets/images/arrow-right.svg'; import BackArrow from '../../../assets/images/back-left.svg'; import Bank from '../../../assets/images/bank.svg'; +import Bill from '../../../assets/images/bill.svg'; +import Briefcase from '../../../assets/images/briefcase.svg'; import Bug from '../../../assets/images/bug.svg'; import Building from '../../../assets/images/building.svg'; import Camera from '../../../assets/images/camera.svg'; import Cash from '../../../assets/images/cash.svg'; import ChatBubble from '../../../assets/images/chatbubble.svg'; import Checkmark from '../../../assets/images/checkmark.svg'; +import CircleHourglass from '../../../assets/images/circle-hourglass.svg'; import Clipboard from '../../../assets/images/clipboard.svg'; import Close from '../../../assets/images/close.svg'; +import Concierge from '../../../assets/images/concierge.svg'; import CreditCard from '../../../assets/images/creditcard.svg'; import DownArrow from '../../../assets/images/down.svg'; import Download from '../../../assets/images/download.svg'; @@ -21,9 +25,11 @@ import ExpensifyCard from '../../../assets/images/expensifycard.svg'; import Gallery from '../../../assets/images/gallery.svg'; import Gear from '../../../assets/images/gear.svg'; import Info from '../../../assets/images/info.svg'; +import Invoice from '../../../assets/images/invoice.svg'; import Link from '../../../assets/images/link.svg'; import LinkCopy from '../../../assets/images/link-copy.svg'; import Lock from '../../../assets/images/lock.svg'; +import Luggage from '../../../assets/images/luggage.svg'; import MagnifyingGlass from '../../../assets/images/magnifyingglass.svg'; import Mail from '../../../assets/images/mail.svg'; import MoneyBag from '../../../assets/images/money-bag.svg'; @@ -43,6 +49,7 @@ import Plus from '../../../assets/images/plus.svg'; import Printer from '../../../assets/images/printer.svg'; import Profile from '../../../assets/images/profile.svg'; import Receipt from '../../../assets/images/receipt.svg'; +import ReceiptSearch from '../../../assets/images/receipt-search.svg'; import Send from '../../../assets/images/send.svg'; import SignOut from '../../../assets/images/sign-out.svg'; import Sync from '../../../assets/images/sync.svg'; @@ -60,14 +67,18 @@ export { ArrowRight, BackArrow, Bank, + Bill, + Briefcase, Building, Bug, Camera, Cash, ChatBubble, Checkmark, + CircleHourglass, Clipboard, Close, + Concierge, CreditCard, DownArrow, Download, @@ -78,9 +89,11 @@ export { Gallery, Gear, Info, + Invoice, Link, LinkCopy, Lock, + Luggage, MagnifyingGlass, Mail, MoneyBag, @@ -100,6 +113,7 @@ export { Printer, Profile, Receipt, + ReceiptSearch, Send, SignOut, Sync, diff --git a/src/components/Icon/Illustrations.js b/src/components/Icon/Illustrations.js new file mode 100644 index 00000000000..da565effa4a --- /dev/null +++ b/src/components/Icon/Illustrations.js @@ -0,0 +1,33 @@ +import BankArrowPink from '../../../assets/images/product-illustrations/bank-arrow--pink.svg'; +import BankMouseGreen from '../../../assets/images/product-illustrations/bank-mouse--green.svg'; +import BankUserGreen from '../../../assets/images/product-illustrations/bank-user--green.svg'; +import ConciergeBlue from '../../../assets/images/product-illustrations/concierge--blue.svg'; +import CreditCardsBlue from '../../../assets/images/product-illustrations/credit-cards--blue.svg'; +import InvoiceOrange from '../../../assets/images/product-illustrations/invoice--orange.svg'; +import JewelBoxBlue from '../../../assets/images/product-illustrations/jewel-box--blue.svg'; +import JewelBoxGreen from '../../../assets/images/product-illustrations/jewel-box--green.svg'; +import JewelBoxPink from '../../../assets/images/product-illustrations/jewel-box--pink.svg'; +import JewelBoxYellow from '../../../assets/images/product-illustrations/jewel-box--yellow.svg'; +import MoneyEnvelopeBlue from '../../../assets/images/product-illustrations/money-envelope--blue.svg'; +import MoneyMousePink from '../../../assets/images/product-illustrations/money-mouse--pink.svg'; +import ReceiptYellow from '../../../assets/images/product-illustrations/receipt--yellow.svg'; +import RocketOrange from '../../../assets/images/product-illustrations/rocket--orange.svg'; +import TadaYellow from '../../../assets/images/product-illustrations/tada--yellow.svg'; + +export { + BankArrowPink, + BankMouseGreen, + BankUserGreen, + ConciergeBlue, + CreditCardsBlue, + InvoiceOrange, + JewelBoxBlue, + JewelBoxGreen, + JewelBoxPink, + JewelBoxYellow, + MoneyEnvelopeBlue, + MoneyMousePink, + ReceiptYellow, + RocketOrange, + TadaYellow, +}; diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index 03b3f4847e4..4b22a620481 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -2,7 +2,6 @@ import React from 'react'; import { View, Pressable, } from 'react-native'; -import PropTypes from 'prop-types'; import Text from './Text'; import styles, {getButtonBackgroundColorStyle, getIconFillColor} from '../styles/styles'; import Icon from './Icon'; @@ -11,59 +10,10 @@ import getButtonState from '../libs/getButtonState'; import Avatar from './Avatar'; import Badge from './Badge'; import CONST from '../CONST'; +import menuItemPropTypes from './menuItemPropTypes'; const propTypes = { - /** Text to be shown as badge near the right end. */ - badgeText: PropTypes.string, - - /** Any additional styles to apply */ - // eslint-disable-next-line react/forbid-prop-types - wrapperStyle: PropTypes.object, - - /** Function to fire when component is pressed */ - onPress: PropTypes.func.isRequired, - - /** Icon to display on the left side of component */ - icon: PropTypes.oneOfType([PropTypes.elementType, PropTypes.string]), - - /** Icon Height */ - iconWidth: PropTypes.number, - - /** Icon Height */ - iconHeight: PropTypes.number, - - /** Text to display for the item */ - title: PropTypes.string.isRequired, - - /** Boolean whether to display the right icon */ - shouldShowRightIcon: PropTypes.bool, - - /** A boolean flag that gives the icon a green fill if true */ - success: PropTypes.bool, - - /** Overrides the icon for shouldShowRightIcon */ - iconRight: PropTypes.elementType, - - /** A description text to show under the title */ - description: PropTypes.string, - - /** Any additional styles to pass to the icon container. */ - iconStyles: PropTypes.arrayOf(PropTypes.object), - - /** The fill color to pass into the icon. */ - iconFill: PropTypes.string, - - /** Whether item is focused or active */ - focused: PropTypes.bool, - - /** Should we disable this menu item? */ - disabled: PropTypes.bool, - - /** A right-aligned subtitle for this menu option */ - subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - - /** Flag to choose between avatar image or an icon */ - iconType: PropTypes.oneOf([CONST.ICON_TYPE_AVATAR, CONST.ICON_TYPE_ICON]), + ...menuItemPropTypes, }; const defaultProps = { diff --git a/src/components/MenuItemList.js b/src/components/MenuItemList.js new file mode 100644 index 00000000000..3ffa6aa9806 --- /dev/null +++ b/src/components/MenuItemList.js @@ -0,0 +1,31 @@ +import React from 'react'; +import _ from 'underscore'; +import PropTypes from 'prop-types'; +import MenuItem from './MenuItem'; +import menuItemPropTypes from './menuItemPropTypes'; + +const propTypes = { + /** An array of props that are pass to individual MenuItem components */ + menuItems: PropTypes.arrayOf(PropTypes.shape(menuItemPropTypes)), +}; +const defaultProps = { + menuItems: [], +}; + +const MenuItemList = ({menuItems}) => ( + <> + {_.map(menuItems, menuItemProps => ( + + ))} + +); + +MenuItemList.displayName = 'MenuItemList'; +MenuItemList.propTypes = propTypes; +MenuItemList.defaultProps = defaultProps; + +export default MenuItemList; diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index e2b051bf5cf..6beb9c10c00 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -54,7 +54,7 @@ class PDFView extends PureComponent { style={[styles.PDFView, this.props.style]} > } + loading={} file={this.props.sourceURL} options={{ cMapUrl: 'cmaps/', diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index 57554a53e8d..962bf4e5224 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -32,7 +32,7 @@ const defaultProps = { const PDFView = props => ( } + activityIndicator={} source={{uri: props.sourceURL}} style={[ styles.imageModalPDF, diff --git a/src/components/TextLink.js b/src/components/TextLink.js index 4403c55715c..e8947f21434 100644 --- a/src/components/TextLink.js +++ b/src/components/TextLink.js @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import {Pressable, Linking} from 'react-native'; import Text from './Text'; import styles from '../styles/styles'; +import stylePropTypes from '../styles/stylePropTypes'; const propTypes = { /** Link to open in new tab */ @@ -17,7 +18,7 @@ const propTypes = { ]).isRequired, /** Additional style props */ - style: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]), + style: stylePropTypes, /** Overwrites the default link behavior with a custom callback */ onPress: PropTypes.func, diff --git a/src/components/UnorderedList.js b/src/components/UnorderedList.js new file mode 100644 index 00000000000..9c2ee04fc8f --- /dev/null +++ b/src/components/UnorderedList.js @@ -0,0 +1,34 @@ +import React from 'react'; +import _ from 'underscore'; +import {View} from 'react-native'; +import PropTypes from 'prop-types'; +import Text from './Text'; +import styles from '../styles/styles'; + +const propTypes = { + /** An array of strings to display as an unordered list */ + items: PropTypes.arrayOf(PropTypes.string), +}; +const defaultProps = { + items: [], +}; + +const UnorderedList = ({items}) => ( + <> + {_.map(items, itemText => ( + + {'\u2022'} + {itemText} + + ))} + +); + +UnorderedList.displayName = 'UnorderedList'; +UnorderedList.propTypes = propTypes; +UnorderedList.defaultProps = defaultProps; + +export default UnorderedList; diff --git a/src/components/bankAccountPropTypes.js b/src/components/bankAccountPropTypes.js new file mode 100644 index 00000000000..3331a617cbc --- /dev/null +++ b/src/components/bankAccountPropTypes.js @@ -0,0 +1,15 @@ +import PropTypes from 'prop-types'; + +export default PropTypes.shape({ + /** The name of the institution (bank of america, etc */ + addressName: PropTypes.string, + + /** The masked bank account number */ + accountNumber: PropTypes.string, + + /** The bankAccountID in the bankAccounts db */ + bankAccountID: PropTypes.number, + + /** The bank account type */ + type: PropTypes.string, +}); diff --git a/src/components/menuItemPropTypes.js b/src/components/menuItemPropTypes.js new file mode 100644 index 00000000000..5802dc9976b --- /dev/null +++ b/src/components/menuItemPropTypes.js @@ -0,0 +1,58 @@ +import PropTypes from 'prop-types'; +import CONST from '../CONST'; + +const propTypes = { + /** Text to be shown as badge near the right end. */ + badgeText: PropTypes.string, + + /** Any additional styles to apply */ + // eslint-disable-next-line react/forbid-prop-types + wrapperStyle: PropTypes.object, + + /** Function to fire when component is pressed */ + onPress: PropTypes.func.isRequired, + + /** Icon to display on the left side of component */ + icon: PropTypes.oneOfType([PropTypes.elementType, PropTypes.string]), + + /** Icon Width */ + iconWidth: PropTypes.number, + + /** Icon Height */ + iconHeight: PropTypes.number, + + /** Text to display for the item */ + title: PropTypes.string.isRequired, + + /** Boolean whether to display the right icon */ + shouldShowRightIcon: PropTypes.bool, + + /** A boolean flag that gives the icon a green fill if true */ + success: PropTypes.bool, + + /** Overrides the icon for shouldShowRightIcon */ + iconRight: PropTypes.elementType, + + /** A description text to show under the title */ + description: PropTypes.string, + + /** Any additional styles to pass to the icon container. */ + iconStyles: PropTypes.arrayOf(PropTypes.object), + + /** The fill color to pass into the icon. */ + iconFill: PropTypes.string, + + /** Whether item is focused or active */ + focused: PropTypes.bool, + + /** Should we disable this menu item? */ + disabled: PropTypes.bool, + + /** A right-aligned subtitle for this menu option */ + subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + + /** Flag to choose between avatar image or an icon */ + iconType: PropTypes.oneOf([CONST.ICON_TYPE_AVATAR, CONST.ICON_TYPE_ICON]), +}; + +export default propTypes; diff --git a/src/languages/en.js b/src/languages/en.js index 7f7ecf8c521..3cfaf952b2b 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -45,7 +45,7 @@ export default { saveAndContinue: 'Save & continue', settings: 'Settings', termsOfService: 'Terms of service', - people: 'People', + members: 'Members', invite: 'Invite', here: 'here', dob: 'Date of birth', @@ -411,10 +411,10 @@ export default { routingNumber: 'Routing number', addBankAccount: 'Add bank account', chooseAnAccount: 'Choose an account', - logIntoYourBank: 'Log into your bank', + connectOnlineWithPlaid: 'Connect online with Plaid', connectManually: 'Connect manually', yourDataIsSecure: 'Your data is secure', - toGetStarted: 'To get started with the Expensify Card, you first need to add a bank account.', + toGetStarted: 'Add a bank account and issue corporate cards, reimburse expenses, collect invoice payments, and pay bills, all from one place.', 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.', validateAccountError: 'In order to finish setting up your bank account, you must validate your account. Please check your email to validate your account, and return here to finish up!', @@ -592,7 +592,7 @@ export default { reviewingInfo: 'Thanks! We\'re reviewing your information, and will be in touch shortly. Please check your chat with Concierge ', forNextSteps: ' for next steps to finish setting up your bank account.', letsChatCTA: 'Yes, let\'s chat!', - letsChatText: 'Thanks for providing your information! We have a couple more things to work out, but it\'ll be easier over chat. Ready to get started?', + letsChatText: 'Thanks for doing that! We have a couple more things to work out, but it’ll be easier over chat. Ready to chat?', letsChatTitle: 'Let\'s chat!', }, beneficialOwnersStep: { @@ -621,9 +621,18 @@ export default { }, workspace: { common: { - card: 'Expensify Card', + card: 'Issue corporate cards', workspace: 'Workspace', edit: 'Edit workspace', + settings: 'General settings', + reimburse: 'Reimburse receipts', + bills: 'Pay bills', + invoices: 'Send invoices', + travel: 'Book travel', + members: 'Manage members', + bankAccount: 'Connect bank account', + issueAndManageCards: 'Issue and manage cards', + reconcileCards: 'Reconcile cards', }, new: { newWorkspace: 'New workspace', @@ -637,14 +646,55 @@ export default { selectAll: 'Select all', }, card: { - addEmail: 'Add email', - tagline: 'The smartest corporate card in the room.', - publicCopy: 'In order to use the Expensify Card you must use your company\'s private domain. Go ahead and add your private email address as a secondary login.', - privateCopy: 'Just swipe your Expensify Card and your expenses are done, it\'s that simple!', - getStarted: 'Get started', - finishSetup: 'Finish setup', - manageCards: 'Manage cards', - cardReadyTagline: 'Your Expensify Cards are ready to go!', + header: 'Unlock free Expensify Cards', + headerWithEcard: 'Cards are ready!', + noVBACopy: 'Connect a bank account to issue unlimited Expensify Cards for your workspace members and access all of these incredible benefits:', + VBANoECardCopy: 'Issue unlimited Expensify Cards for your workspace members, as well as all of these incredible benefits:', + conciergeCanHelp: 'Concierge can help you add a work email address to enable the Expensify Card.', + VBAWithECardCopy: 'Enjoy all these incredible benefits:', + benefit1: 'Up to 2% cash back', + benefit2: 'Digital and physical cards', + benefit3: 'No personal liability', + benefit4: 'Customizable limits', + chatWithConcierge: 'Chat with Concierge', + }, + reimburse: { + captureReceipts: 'Capture receipts', + fastReimbursementsHappyMembers: 'Fast reimbursements = happy members!', + viewAllReceipts: 'View all receipts', + reimburseReceipts: 'Reimburse receipts', + unlockNextDayReimbursements: 'Unlock next day reimbursements', + captureNoVBACopyBeforeEmail: 'Ask your workspace members to forward receipts to ', + captureNoVBACopyAfterEmail: ' and download the Expensify App to track cash expenses on the go.', + unlockNoVBACopy: 'Connect a bank account to reimburse your workspace members online.', + fastReimbursementsVBACopy: 'You\'re all set to reimburse receipts from your bank account!', + }, + bills: { + manageYourBills: 'Manage your bills', + askYourVendorsBeforeEmail: 'Ask your vendors to forward their invoices to ', + askYourVendorsAfterEmail: ' and we\'ll scan them for you to pay', + viewAllBills: 'View all bills', + unlockOnlineBillPayment: 'Unlock online bill payment', + unlockNoVBACopy: 'Connect your bank account to pay bills online for free!', + hassleFreeBills: 'Hassle-free bills!', + VBACopy: 'You\'re all set to make payments from your bank account!', + }, + invoices: { + invoiceClientsAndCustomers: 'Invoice clients and customers', + invoiceFirstSectionCopy: 'Send beautiful, professional invoices directly to your clients and customers right from within the Expensify app.', + viewAllInvoices: 'View all invoices', + unlockOnlineInvoicesCollection: 'Unlock online invoices collection', + unlockNoVBACopy: 'Connect your bank account to accept online payments for invoices - by ACH or credit card - to be deposited straight into your account.', + moneyBackInAFlash: 'Money back, in a flash!', + unlockVBACopy: 'You\'re all set to accept payments by ACH or credit card!', + viewUnpaidInvoices: 'View unpaid invoices', + }, + travel: { + unlockConciergeBookingTravel: 'Unlock Concierge travel booking', + noVBACopy: 'Connect your bank account to let workspace members book their flights, hotels, and cars by starting a chat with Concierge.', + packYourBags: 'Pack your bags!', + VBACopy: 'Members with the Expensify card can chat with Concierge to book travel!', + bookTravelWithConcierge: 'Book travel with Concierge', }, invite: { invitePeople: 'Invite new members', @@ -661,12 +711,24 @@ export default { editor: { nameInputLabel: 'Name', nameInputHelpText: 'This is the name you will see on your workspace.', + nameIsRequiredError: 'You need to define a name for your workspace', + currencyInputLabel: 'Default Currency', + currencyInputHelpText: 'All expenses on this workspace will be converted to this currency.', save: 'Save', genericFailureMessage: 'An error occurred updating the workspace, please try again.', avatarUploadFailureMessage: 'An error occurred uploading the avatar, please try again.', }, - error: { - growlMessageInvalidPolicy: 'Invalid workspace!', + bankAccount: { + continueWithSetup: 'Continue with setup', + youreAlmostDone: 'You\'re almost done setting up your bank account, which will let you issue corporate cards, reimburse expenses, collect invoices, and pay bills all from the same bank account.', + streamlinePayments: 'Streamline payments', + oneMoreThing: 'One more thing!', + allSet: 'All set!', + accountDescriptionNoCards: 'This bank account will be used to reimburse expenses, collect invoices, and pay bills all from the same account.\n\nConcierge can help you add a work email address to enable the Expensify Card.', + accountDescriptionWithCards: 'This bank account will be used to issue corporate cards, reimburse expenses, collect invoices, and pay bills all from the same account.', + chatWithConcierge: 'Chat with Concierge', + letsFinishInChat: 'Let\'s finish in chat!', + almostDone: 'Almost done!', }, }, requestCallPage: { diff --git a/src/languages/es.js b/src/languages/es.js index fa9b594af0e..27f5c81cd3a 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -45,7 +45,7 @@ export default { saveAndContinue: 'Guardar y continuar', settings: 'Configuración', termsOfService: 'Términos de servicio', - people: 'Personas', + members: 'Miembros', invite: 'Invitación', here: 'aquí', dob: 'Fecha de Nacimiento', @@ -411,10 +411,10 @@ export default { routingNumber: 'Número de ruta', addBankAccount: 'Agregar cuenta bancaria', chooseAnAccount: 'Elige una cuenta', - logIntoYourBank: 'Inicia sesión en su banco', + connectOnlineWithPlaid: 'Conéctate a Plaid online', connectManually: 'Conectar manualmente', yourDataIsSecure: 'Tus datos están seguros', - toGetStarted: 'Para comenzar con la tarjeta Expensify, primero debe agregar una cuenta bancaria.', + toGetStarted: 'Añade una cuenta bancaria y emite tarjetas corporativas, reembolsa gastos y cobra y paga facturas, todo desde un mismo sitio.', 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 de la cuenta bancaria.', validateAccountError: 'Para terminar de configurar tu cuenta bancaria, debes validar tu cuenta de Expensify. Por favor revisa tu correo electrónico para validar tu cuenta y regresa aquí para continuar.', @@ -594,7 +594,7 @@ export default { reviewingInfo: '¡Gracias! Estamos revisando tu información y nos comunicaremos contigo en breve. Consulte su chat con Concierge ', forNextSteps: ' para conocer los próximos pasos para terminar de configurar su cuenta bancaria.', letsChatCTA: '¡Sí, vamos a chatear!', - letsChatText: '¡Gracias por darnos tu información! Aún nos quedan algunas cosas por revisar, pero será mas fácil hacerlo por chat. ¿Estás listo para comenzar?', + letsChatText: '¡Gracias por hacer eso! Todavía tenemos que solucionar un par de cosas, pero será más fácil por chat. ¿Listo para charlar?', letsChatTitle: '¡Vamos a chatear!', }, beneficialOwnersStep: { @@ -623,9 +623,18 @@ export default { }, workspace: { common: { - card: 'Tarjeta Expensify', + card: 'Emitir tarjetas corporativas', workspace: 'Espacio de trabajo', edit: 'Editar espacio de trabajo', + settings: 'Configuración general', + reimburse: 'Reembolsar recibos', + bills: 'Pagar facturas', + invoices: 'Enviar facturas', + travel: 'Reservar viaje', + members: 'Gestionar miembros', + bankAccount: 'Conectar cuenta bancaria', + issueAndManageCards: 'Emitir y gestionar tarjetas', + reconcileCards: 'Reconciliar tarjetas', }, new: { newWorkspace: 'Nuevo espacio de trabajo', @@ -639,14 +648,55 @@ export default { selectAll: 'Seleccionar todo', }, card: { - addEmail: 'Agregar correo electrónico', - tagline: 'La tarjeta corporativa más inteligente de la habitación.', - publicCopy: 'Para utilizar la Tarjeta Expensify debe utilizar el dominio privado de su empresa. Continúe y agregue su dirección de correo electrónico privada como inicio de sesión secundario.', - privateCopy: 'Simplemente deslice su Tarjeta Expensify y sus gastos estarán listos, ¡es así de simple!', - getStarted: 'Empezar', - finishSetup: 'Finalizar configuración', - manageCards: 'Administrar tarjetas', - cardReadyTagline: 'Tus tarjetas Expensify están listas para usar!', + header: 'Desbloquea Tarjetas Expensify gratis', + headerWithEcard: '¡Tus tarjetas están listas!', + noVBACopy: 'Conecta una cuenta bancaria para emitir Tarjetas Expensify ilimitadas para los miembros de tu espacio de trabajo y acceder a todas estas increíbles ventajas:', + VBANoECardCopy: 'Emite Tarjetas Expensify ilimitadas para los miembros de tu espacio de trabajo y accede a todas estas increíbles ventajas:', + conciergeCanHelp: 'Concierge te puede ayudar a añadir un correo electrónico de trabajo para activar la Tarjeta Expensify.', + VBAWithECardCopy: 'Disfruta de todas estas increíbles ventajas:', + benefit1: 'Hasta un 2% de devolución en tus gastos', + benefit2: 'Tarjetas digitales y físicas', + benefit3: 'Sin responsabilidad personal', + benefit4: 'Límites personalizables', + chatWithConcierge: 'Chatea con Concierge', + }, + reimburse: { + captureReceipts: 'Captura recibos', + fastReimbursementsHappyMembers: '¡Reembolsos rápidos = miembros felices!', + viewAllReceipts: 'Ver todos los recibos', + reimburseReceipts: 'Reembolsar recibos', + unlockNextDayReimbursements: 'Desbloquea reembolsos diarios', + captureNoVBACopyBeforeEmail: 'Pide a los miembros de tu espacio de trabajo que envíen recibos a ', + captureNoVBACopyAfterEmail: ' y descarga la App de Expensify para controlar tus gastos en efectivo sobre la marcha.', + unlockNoVBACopy: 'Conecta una cuenta bancaria para reembolsar online a los miembros de tu espacio de trabajo.', + fastReimbursementsVBACopy: '¡Todo listo para reembolsar recibos desde tu cuenta bancaria!', + }, + bills: { + manageYourBills: 'Gestiona tus facturas', + askYourVendorsBeforeEmail: 'Pide a tus proveedores que envíen sus facturas a ', + askYourVendorsAfterEmail: ' y las escanearemos para que las pagues', + viewAllBills: 'Ver facturas recibidas', + unlockOnlineBillPayment: 'Desbloquea el pago de facturas online', + unlockNoVBACopy: '¡Conecta tu cuenta bancaria para pagar tus facturas online de manera gratuita!', + hassleFreeBills: '¡Facturas sin complicaciones!', + VBACopy: '¡Todo listo para realizar pagos desde tu cuenta bancaria!', + }, + invoices: { + invoiceClientsAndCustomers: 'Emite facturas a tus clientes', + invoiceFirstSectionCopy: 'Envía facturas detalladas y profesionales directamente a tus clientes desde la app de Expensify.', + viewAllInvoices: 'Ver facturas emitidas', + unlockOnlineInvoicesCollection: 'Desbloquea el cobro de facturas online', + unlockNoVBACopy: 'Conecta tu cuenta bancaria para recibir pagos online de facturas - por transferencia o con tarjeta - directamente en tu cuenta.', + moneyBackInAFlash: '¡Tu dinero de vuelta en un momento!', + unlockVBACopy: '¡Todo listo para recibir pagos por transferencia o con tarjeta!', + viewUnpaidInvoices: 'Ver facturas emitidas pendientes', + }, + travel: { + unlockConciergeBookingTravel: 'Desbloquea la reserva de viajes con Concierge', + noVBACopy: 'Conecta tu cuenta bancaria para permitir a los miembros de tu espacio de trabajo reservar sus vuelos, hoteles y coches empezando una conversación con Concierge.', + packYourBags: '¡Haz las maletas!', + VBACopy: '¡Miembros con la tarjeta Expensify pueden hablar con Concierge para reservar viajes!', + bookTravelWithConcierge: 'Reserva viajes con Concierge', }, invite: { invitePeople: 'Invitar nuevos miembros', @@ -663,12 +713,24 @@ export default { editor: { nameInputLabel: 'Nombre', nameInputHelpText: 'Este es el nombre que verás en tu espacio de trabajo.', + nameIsRequiredError: 'Debes definir un nombre para tu espacio de trabajo.', + currencyInputLabel: 'Moneda por defecto', + currencyInputHelpText: 'Todas los gastos en este epecio de trabajo serán convertidos a esta moneda.', save: 'Guardar', genericFailureMessage: 'Se produjo un error al guardar el espacio de trabajo. Por favor, inténtalo de nuevo.', avatarUploadFailureMessage: 'No se pudo subir el avatar. Por favor, inténtalo de nuevo.', }, - error: { - growlMessageInvalidPolicy: '¡Espacio de trabajo no válido!', + bankAccount: { + continueWithSetup: 'Continuar con la configuración', + youreAlmostDone: 'Casi has acabado de configurar tu cuenta bancaria, que te permitirá emitir tarjetas corporativas, reembolsar gastos y cobrar pagar facturas, todo desde la misma cuenta bancaria.', + streamlinePayments: 'Optimiza pagos', + oneMoreThing: '¡Una cosa más!', + allSet: '¡Todo listo!', + accountDescriptionNoCards: 'Esta cuenta bancaria se utilizará para reembolsar gastos y cobrar y pagar facturas, todo desde la misma cuenta. Concierge puede ayudarte a añadir tu correo de trabajo para activar la Tarjeta Expensify.', + accountDescriptionWithCards: 'Esta cuenta bancaria se utilizará para emitir tarjetas corporativas, reembolsar gastos y cobrar y pagar facturas, todo desde la misma cuenta.', + chatWithConcierge: 'Chat con Concierge', + letsFinishInChat: '¡Acabemos en el chat!', + almostDone: '¡Casi listo!', }, }, requestCallPage: { diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index 72f26868fa7..10f22a0ca3c 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -6,7 +6,7 @@ import Str from 'expensify-common/lib/str'; import moment from 'moment'; import _ from 'underscore'; import lodashGet from 'lodash/get'; -import styles, {getNavigationModalCardStyle} from '../../../styles/styles'; +import {getNavigationModalCardStyle} from '../../../styles/styles'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; import CONST from '../../../CONST'; import compose from '../../compose'; @@ -55,18 +55,13 @@ import { SettingsModalStackNavigator, EnablePaymentsStackNavigator, AddPersonalBankAccountModalStackNavigator, - ReimbursementAccountModalStackNavigator, WorkspaceInviteModalStackNavigator, RequestCallModalStackNavigator, ReportDetailsModalStackNavigator, - WorkspaceEditorNavigator, } from './ModalStackNavigators'; import SCREENS from '../../../SCREENS'; import Timers from '../../Timers'; import LogInWithShortLivedTokenPage from '../../../pages/LogInWithShortLivedTokenPage'; -import WorkspaceSettingsDrawerNavigator from './WorkspaceSettingsDrawerNavigator'; -import spacing from '../../../styles/utilities/spacing'; -import CardOverlay from '../../../components/CardOverlay'; import defaultScreenOptions from './defaultScreenOptions'; import * as API from '../../API'; import {setLocale} from '../../actions/App'; @@ -250,17 +245,6 @@ class AuthScreens extends React.Component { // when displaying a modal. This allows us to dismiss by clicking outside on web / large screens. isModal: true, }; - const fullscreenModalScreenOptions = { - ...commonModalScreenOptions, - cardStyle: { - ...styles.fullscreenCard, - padding: this.props.isSmallScreenWidth ? spacing.p0.padding : spacing.p5.padding, - }, - cardStyleInterpolator: props => modalCardStyleInterpolator(this.props.isSmallScreenWidth, true, props), - cardOverlayEnabled: !this.props.isSmallScreenWidth, - isFullScreenModal: true, - cardOverlay: CardOverlay, - }; return ( - - ); } diff --git a/src/libs/Navigation/AppNavigator/MainDrawerNavigator.js b/src/libs/Navigation/AppNavigator/MainDrawerNavigator.js index fafe54572ab..fac1a19acae 100644 --- a/src/libs/Navigation/AppNavigator/MainDrawerNavigator.js +++ b/src/libs/Navigation/AppNavigator/MainDrawerNavigator.js @@ -50,7 +50,7 @@ const MainDrawerNavigator = (props) => { // Wait until reports are fetched and there is a reportID in initialParams if (!initialParams.reportID) { - return ; + return ; } // After the app initializes and reports are available the home navigation is mounted diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index d64960f6feb..10fd8b38d11 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -28,7 +28,16 @@ import WorkspaceInvitePage from '../../../pages/workspace/WorkspaceInvitePage'; import ReimbursementAccountPage from '../../../pages/ReimbursementAccount/ReimbursementAccountPage'; import RequestCallPage from '../../../pages/RequestCallPage'; import ReportDetailsPage from '../../../pages/ReportDetailsPage'; -import WorkspaceEditorPage from '../../../pages/workspace/WorkspaceEditorPage'; +import WorkspaceSettingsPage from '../../../pages/workspace/WorkspaceSettingsPage'; +import WorkspaceInitialPage from '../../../pages/workspace/WorkspaceInitialPage'; +import WorkspaceCardPage from '../../../pages/workspace/card/WorkspaceCardPage'; +import WorkspaceReimbursePage from '../../../pages/workspace/reimburse/WorkspaceReimbursePage'; +import WorkspaceInvoicesPage from '../../../pages/workspace/invoices/WorkspaceInvoicesPage'; +import WorkspaceBillsPage from '../../../pages/workspace/bills/WorkspaceBillsPage'; +import WorkspaceTravelPage from '../../../pages/workspace/travel/WorkspaceTravelPage'; +import WorkspaceMembersPage from '../../../pages/workspace/WorkspaceMembersPage'; +import WorkspaceBankAccountPage from '../../../pages/workspace/WorkspaceBankAccountPage'; +import CONST from '../../../CONST'; const defaultSubRouteOptions = { cardStyle: styles.navigationScreenCardStyle, @@ -55,6 +64,7 @@ function createModalStackNavigator(screens) { key={screen.name} name={screen.name} component={screen.Component} + initialParams={screen.initialParams} /> ))} @@ -170,6 +180,47 @@ const SettingsModalStackNavigator = createModalStackNavigator([ Component: SettingsAddDebitCardPage, name: 'Settings_Add_Debit_Card', }, + { + Component: WorkspaceInitialPage, + name: 'Workspace_Initial', + }, + { + Component: WorkspaceSettingsPage, + name: 'Workspace_Settings', + }, + { + Component: WorkspaceCardPage, + name: 'Workspace_Card', + }, + { + Component: WorkspaceReimbursePage, + name: 'Workspace_Reimburse', + }, + { + Component: WorkspaceBillsPage, + name: 'Workspace_Bills', + }, + { + Component: WorkspaceInvoicesPage, + name: 'Workspace_Invoices', + }, + { + Component: WorkspaceTravelPage, + name: 'Workspace_Travel', + }, + { + Component: WorkspaceMembersPage, + name: 'Workspace_Members', + }, + { + Component: WorkspaceBankAccountPage, + name: 'Workspace_BankAccount', + }, + { + Component: ReimbursementAccountPage, + name: 'ReimbursementAccount', + initialParams: {stepToOpen: CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT}, + }, ]); const EnablePaymentsStackNavigator = createModalStackNavigator([{ @@ -197,11 +248,6 @@ const RequestCallModalStackNavigator = createModalStackNavigator([{ name: 'RequestCall_Root', }]); -const WorkspaceEditorNavigator = createModalStackNavigator([{ - Component: WorkspaceEditorPage, - name: 'WorkspaceEditor_Root', -}]); - export { IOUBillStackNavigator, IOURequestModalStackNavigator, @@ -219,5 +265,4 @@ export { ReimbursementAccountModalStackNavigator, WorkspaceInviteModalStackNavigator, RequestCallModalStackNavigator, - WorkspaceEditorNavigator, }; diff --git a/src/libs/Navigation/AppNavigator/WorkspaceSettingsDrawerNavigator.js b/src/libs/Navigation/AppNavigator/WorkspaceSettingsDrawerNavigator.js deleted file mode 100644 index 589dd6bda3c..00000000000 --- a/src/libs/Navigation/AppNavigator/WorkspaceSettingsDrawerNavigator.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; - -// Screens -import BaseDrawerNavigator from './BaseDrawerNavigator'; -import WorkspaceCardPage from '../../../pages/workspace/WorkspaceCardPage'; -import WorkspacePeoplePage from '../../../pages/workspace/WorkspacePeoplePage'; -import WorkspaceSidebar from '../../../pages/workspace/WorkspaceSidebar'; - -const WorkspaceSettingsDrawerNavigator = () => ( - } - screens={[ - { - name: 'WorkspaceCard', - component: WorkspaceCardPage, - initialParams: {}, - }, - { - name: 'WorkspacePeople', - component: WorkspacePeoplePage, - initialParams: {}, - }, - ]} - /> -); - -WorkspaceSettingsDrawerNavigator.displayName = 'WorkspaceSettingsDrawerNavigator'; - -export default WorkspaceSettingsDrawerNavigator; diff --git a/src/libs/Navigation/NavigationRoot.js b/src/libs/Navigation/NavigationRoot.js index ad516ebf0f5..fb8134d55f7 100644 --- a/src/libs/Navigation/NavigationRoot.js +++ b/src/libs/Navigation/NavigationRoot.js @@ -43,7 +43,7 @@ class NavigationRoot extends Component { render() { return ( } + fallback={} onStateChange={this.parseAndStoreRoute} ref={navigationRef} linking={linkingConfig} diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index 7f246559de0..a9a4fd1ceb9 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -75,6 +75,38 @@ export default { Settings_Add_Secondary_Login: { path: ROUTES.SETTINGS_ADD_LOGIN, }, + Workspace_Initial: { + path: ROUTES.WORKSPACE_INITIAL, + }, + Workspace_Settings: { + path: ROUTES.WORKSPACE_SETTINGS, + }, + Workspace_Card: { + path: ROUTES.WORKSPACE_CARD, + }, + Workspace_Reimburse: { + path: ROUTES.WORKSPACE_REIMBURSE, + }, + Workspace_Bills: { + path: ROUTES.WORKSPACE_BILLS, + }, + Workspace_Invoices: { + path: ROUTES.WORKSPACE_INVOICES, + }, + Workspace_Travel: { + path: ROUTES.WORKSPACE_TRAVEL, + }, + Workspace_Members: { + path: ROUTES.WORKSPACE_MEMBERS, + }, + Workspace_BankAccount: { + path: ROUTES.WORKSPACE_BANK_ACCOUNT, + exact: true, + }, + ReimbursementAccount: { + path: ROUTES.BANK_ACCOUNT, + exact: true, + }, }, }, Report_Details: { @@ -141,31 +173,11 @@ export default { EnablePayments_Root: ROUTES.ENABLE_PAYMENTS, }, }, - ReimbursementAccount: { - screens: { - ReimbursementAccount_Root: ROUTES.BANK_ACCOUNT, - }, - }, WorkspaceInvite: { screens: { WorkspaceInvite_Root: ROUTES.WORKSPACE_INVITE, }, }, - - WorkspaceSettings: { - path: ROUTES.WORKSPACE, - screens: { - WorkspaceCard: ROUTES.WORKSPACE_CARD, - WorkspacePeople: ROUTES.WORKSPACE_PEOPLE, - }, - }, - - WorkspaceEditor: { - screens: { - WorkspaceEditor_Root: ROUTES.WORKSPACE_EDITOR, - }, - }, - RequestCall: { screens: { RequestCall_Root: ROUTES.REQUEST_CALL, diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js index 83ae72680dd..d23a1f91168 100644 --- a/src/libs/actions/BankAccounts.js +++ b/src/libs/actions/BankAccounts.js @@ -64,7 +64,7 @@ function goToWithdrawalAccountSetupStep(stepID, achData) { if (!newACHData.useOnfido && stepID === CONST.BANK_ACCOUNT.STEP.REQUESTOR) { delete newACHData.questions; delete newACHData.answers; - if (lodashHas(achData, CONST.BANK_ACCOUNT.VERIFICATIONS.EXTERNAL_API_RESPONSES)) { + if (lodashHas(newACHData, CONST.BANK_ACCOUNT.VERIFICATIONS.EXTERNAL_API_RESPONSES)) { delete newACHData.verifications.externalApiResponses.requestorIdentityID; delete newACHData.verifications.externalApiResponses.requestorIdentityKBA; } @@ -115,6 +115,7 @@ function getPlaidBankAccounts(publicToken, bank) { ...account, accountNumber: Str.maskPAN(account.accountNumber), })), + bankName, }); }); } @@ -336,6 +337,9 @@ function fetchUserWallet() { * @param {String} [stepToOpen] */ function fetchFreePlanVerifiedBankAccount(stepToOpen) { + // Remember which account BankAccountStep subStep the user had before so we can set it later + const subStep = lodashGet(reimbursementAccountInSetup, 'subStep', ''); + // We are using set here since we will rely on data from the server (not local data) to populate the VBA flow // and determine which step to navigate to. Onyx.set(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: true, error: ''}); @@ -379,6 +383,11 @@ function fetchFreePlanVerifiedBankAccount(stepToOpen) { // If the user is already setting up a bank account we will continue the flow for them let currentStep = reimbursementAccountInSetup.currentStep; const achData = bankAccount ? bankAccount.toACHData() : {}; + if (!stepToOpen && achData.currentStep) { + // eslint-disable-next-line no-use-before-define + currentStep = getNextStepToComplete(achData); + } + achData.useOnfido = true; achData.policyID = reimbursementAccountWorkspaceID || ''; achData.isInSetup = !bankAccount || bankAccount.isInSetup(); @@ -386,9 +395,13 @@ function fetchFreePlanVerifiedBankAccount(stepToOpen) { achData.domainLimit = 0; // If the bank account has already been created in the db and is not yet open - // let's show the manual form with the previously added values - achData.subStep = bankAccount && bankAccount.isInSetup() - && CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL; + // let's show the manual form with the previously added values. Otherwise, we will + // make the subStep the previous value. + if (bankAccount && bankAccount.isInSetup()) { + achData.subStep = CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL; + } else { + achData.subStep = subStep; + } // If we're not in setup, it means we already have a withdrawal account // and we're upgrading it to a business bank account. So let the user @@ -504,16 +517,29 @@ function getIndexByStepID(stepID) { /** * Get next step ID + * @param {String} [stepID] * @return {String} */ -function getNextStepID() { +function getNextStepID(stepID) { const nextStepIndex = Math.min( - getIndexByStepID(reimbursementAccountInSetup.currentStep) + 1, + getIndexByStepID(stepID || reimbursementAccountInSetup.currentStep) + 1, WITHDRAWAL_ACCOUNT_STEPS.length - 1, ); return lodashGet(WITHDRAWAL_ACCOUNT_STEPS, [nextStepIndex, 'id'], CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT); } +/** + * @param {Object} achData + * @returns {String} + */ +function getNextStepToComplete(achData) { + if (achData.currentStep === CONST.BANK_ACCOUNT.STEP.REQUESTOR && !achData.isOnfidoSetupComplete) { + return CONST.BANK_ACCOUNT.STEP.REQUESTOR; + } + + return getNextStepID(achData.currentStep); +} + /** * @private * @param {Number} bankAccountID @@ -651,7 +677,7 @@ function setupWithdrawalAccount(data) { API.BankAccount_SetupWithdrawal(newACHData) .then((response) => { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false, achData: {...newACHData}}); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {...newACHData}}); const currentStep = newACHData.currentStep; let achData = lodashGet(response, 'achData', {}); let error = lodashGet(achData, CONST.BANK_ACCOUNT.VERIFICATIONS.ERROR_MESSAGE); @@ -720,6 +746,7 @@ function setupWithdrawalAccount(data) { || achData.state === BankAccount.STATE.VERIFYING; goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.VALIDATION, achData); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false}); }); return; } @@ -763,6 +790,7 @@ function setupWithdrawalAccount(data) { showBankAccountFormValidationError(error); showBankAccountErrorModal(error); } + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false}); }) .catch((response) => { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false, achData: {...newACHData}}); diff --git a/src/libs/actions/PaymentMethods.js b/src/libs/actions/PaymentMethods.js index 3deddf4fde1..d9467327244 100644 --- a/src/libs/actions/PaymentMethods.js +++ b/src/libs/actions/PaymentMethods.js @@ -15,6 +15,7 @@ import {maskCardNumber} from '../cardUtils'; * @returns {Promise} */ function getPaymentMethods() { + Onyx.set(ONYXKEYS.IS_LOADING_PAYMENT_METHODS, true); return API.Get({ returnValueList: 'bankAccountList, fundList, userWallet, nameValuePairs', name: 'paypalMeAddress', @@ -24,6 +25,7 @@ function getPaymentMethods() { }) .then((response) => { Onyx.multiSet({ + [ONYXKEYS.IS_LOADING_PAYMENT_METHODS]: false, [ONYXKEYS.USER_WALLET]: lodashGet(response, 'userWallet', {}), [ONYXKEYS.BANK_ACCOUNT_LIST]: lodashGet(response, 'bankAccountList', []), [ONYXKEYS.CARD_LIST]: lodashGet(response, 'fundList', []), diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 289436cfee3..64319477989 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -46,6 +46,7 @@ function getSimplifiedEmployeeList(employeeList) { * @param {String} fullPolicy.name * @param {String} fullPolicy.role * @param {String} fullPolicy.type + * @param {String} fullPolicy.value.outputCurrency * @param {Object} fullPolicy.value.employeeList * @param {String} [fullPolicy.value.avatarURL] * @returns {Object} @@ -56,6 +57,7 @@ function getSimplifiedPolicyObject(fullPolicy) { name: fullPolicy.name, role: fullPolicy.role, type: fullPolicy.type, + outputCurrency: lodashGet(fullPolicy, 'value.outputCurrency', ''), employeeList: getSimplifiedEmployeeList(lodashGet(fullPolicy, 'value.employeeList')), avatarURL: lodashGet(fullPolicy, 'value.avatarURL', ''), }; @@ -118,6 +120,7 @@ function create(name = '', shouldAutomaticallyReroute = true) { type: response.policy.type, name: response.policy.name, role: CONST.POLICY.ROLE.ADMIN, + outputCurrency: response.policy.outputCurrency, }); }).then(() => { const policyID = lodashGet(res, 'policyID'); @@ -305,13 +308,14 @@ function update(policyID, values) { // Show the user feedback const errorMessage = translateLocal('workspace.editor.genericFailureMessage'); Growl.error(errorMessage, 5000); + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {isPolicyUpdating: false}); return; } const updatedValues = {...values, ...{isPolicyUpdating: false}}; Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, updatedValues); - Navigation.dismissModal(); }).catch(() => { + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {isPolicyUpdating: false}); const errorMessage = translateLocal('workspace.editor.genericFailureMessage'); Growl.error(errorMessage, 5000); }); diff --git a/src/pages/DetailsPage.js b/src/pages/DetailsPage.js index ce36dad38b3..8921bc707a3 100755 --- a/src/pages/DetailsPage.js +++ b/src/pages/DetailsPage.js @@ -77,7 +77,7 @@ const DetailsPage = ({ Navigation.goBack()} onCloseButtonPress={() => Navigation.dismissModal()} /> setBankAccountSubStep(null)} - shouldShowBackButton={Boolean(subStep)} + onBackButtonPress={() => { + // If we have a subStep then we will remove otherwise we will go back + if (subStep) { + setBankAccountSubStep(null); + return; + } + Navigation.goBack(); + }} + shouldShowBackButton /> {!subStep && ( <> + {this.props.translate('bankAccount.toGetStarted')} setBankAccountSubStep(CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID)} disabled={this.props.isPlaidDisabled || !this.props.user.validated} shouldShowRightIcon diff --git a/src/pages/ReimbursementAccount/EnableStep.js b/src/pages/ReimbursementAccount/EnableStep.js index 45c991561b7..57409db3972 100644 --- a/src/pages/ReimbursementAccount/EnableStep.js +++ b/src/pages/ReimbursementAccount/EnableStep.js @@ -1,56 +1,134 @@ +import _ from 'underscore'; import React from 'react'; -import {Image, View} from 'react-native'; +import PropTypes from 'prop-types'; +import {View, Image} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; import styles from '../../styles/styles'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; -import {navigateToConciergeChat} from '../../libs/actions/Report'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; import Navigation from '../../libs/Navigation/Navigation'; import Text from '../../components/Text'; -import CONST from '../../CONST'; -import TextLink from '../../components/TextLink'; import compose from '../../libs/compose'; +import ONYXKEYS from '../../ONYXKEYS'; +import {ChatBubble} from '../../components/Icon/Expensicons'; +import MenuItem from '../../components/MenuItem'; +import getBankIcon from '../../components/Icon/BankIcons'; +import {getPaymentMethods} from '../../libs/actions/PaymentMethods'; +import FullScreenLoadingIndicator from '../../components/FullscreenLoadingIndicator'; +import bankAccountPropTypes from '../../components/bankAccountPropTypes'; +import {navigateToConciergeChat} from '../../libs/actions/Report'; +import confettiPop from '../../../assets/images/confetti-pop.gif'; +import Icon from '../../components/Icon'; +import WorkspaceSection from '../workspace/WorkspaceSection'; +import {ConciergeBlue} from '../../components/Icon/Illustrations'; const propTypes = { + /** Are we loading payment methods? */ + isLoadingPaymentMethods: PropTypes.bool, + + /** Array of bank account objects */ + bankAccountList: PropTypes.arrayOf(bankAccountPropTypes), + ...withLocalizePropTypes, }; -const EnableStep = ({translate}) => { - const verifyingUrl = `${CONST.CLOUDFRONT_URL}/images/icons/emptystates/emptystate_reviewing.gif`; - return ( - - - - + ); + } + + const isUsingExpensifyCard = user.isUsingExpensifyCard; + const account = _.find(bankAccountList, bankAccount => bankAccount.bankAccountID === reimbursementAccount.achData.bankAccountID); + if (!account) { + // This shouldn't happen as we can only end up here if we have successfully added a bank account. + // But in case it does we'll throw here directly so it can be caught by the error boundary. + throw new Error('Account not found in EnableStep'); + } + + const {icon, iconSize} = getBankIcon(account.additionalData.bankName); + const formattedBankAccountNumber = account.accountNumber + ? `${translate('paymentMethodList.accountLastFour')} ${ + account.accountNumber.slice(-4) + }` + : ''; + const bankName = account.addressName; + return ( + + Navigation.goBack()} /> - - {translate('validationStep.reviewingInfo')} - { - // There are two modals that must be dismissed before we can reveal the Concierge - // chat underneath these screens - Navigation.dismissModal(); - Navigation.dismissModal(); - navigateToConciergeChat(); - }} + + (!isUsingExpensifyCard ? : )} + menuItems={!isUsingExpensifyCard ? [{ + title: translate('workspace.bankAccount.chatWithConcierge'), + icon: ChatBubble, + onPress: () => { + Navigation.dismissModal(); + navigateToConciergeChat(); + }, + shouldShowRightIcon: true, + }] : []} > - {translate('common.here')} - - {translate('validationStep.forNextSteps')} - + {}} + wrapperStyle={{paddingHorizontal: 0, marginBottom: 12}} + /> + + {!isUsingExpensifyCard + ? translate('workspace.bankAccount.accountDescriptionNoCards') + : translate('workspace.bankAccount.accountDescriptionWithCards')} + + + - - ); -}; + ); + } +} EnableStep.propTypes = propTypes; -EnableStep.displayName = 'EnableStep'; +EnableStep.defaultProps = defaultProps; export default compose( withLocalize, + withOnyx({ + isLoadingPaymentMethods: { + key: ONYXKEYS.IS_LOADING_PAYMENT_METHODS, + initWithStoredValues: false, + }, + user: { + key: ONYXKEYS.USER, + }, + reimbursementAccount: { + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + }, + bankAccountList: { + key: ONYXKEYS.BANK_ACCOUNT_LIST, + initWithStoredValues: false, + }, + }), )(EnableStep); diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index 2f4b1bb9ee5..1586b1ce704 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -70,7 +70,10 @@ const defaultProps = { class ReimbursementAccountPage extends React.Component { componentDidMount() { // We can specify a step to navigate to by using route params when the component mounts. - fetchFreePlanVerifiedBankAccount(this.getStepToOpenFromRouteParams()); + const stepToOpen = this.getStepToOpenFromRouteParams(); + + // If we are trying to navigate to `/bank-account/new` and we already have a bank account then don't allow returning to `/new` + fetchFreePlanVerifiedBankAccount(stepToOpen !== CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT ? stepToOpen : ''); } componentDidUpdate(prevProps) { @@ -111,6 +114,8 @@ class ReimbursementAccountPage extends React.Component { return CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT; case 'validate': return CONST.BANK_ACCOUNT.STEP.VALIDATION; + case 'enable': + return CONST.BANK_ACCOUNT.STEP.ENABLE; default: return ''; } @@ -130,6 +135,8 @@ class ReimbursementAccountPage extends React.Component { return 'contract'; case CONST.BANK_ACCOUNT.STEP.VALIDATION: return 'validate'; + case CONST.BANK_ACCOUNT.STEP.ENABLE: + return 'enable'; case CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT: default: return 'new'; @@ -173,7 +180,7 @@ class ReimbursementAccountPage extends React.Component { return ( {errorComponent} diff --git a/src/pages/ReimbursementAccount/ValidationStep.js b/src/pages/ReimbursementAccount/ValidationStep.js index 02ba3dec2f3..1b0a108dd5b 100644 --- a/src/pages/ReimbursementAccount/ValidationStep.js +++ b/src/pages/ReimbursementAccount/ValidationStep.js @@ -10,7 +10,6 @@ import { validateBankAccount, updateReimbursementAccountDraft, setBankAccountFormValidationErrors, showBankAccountErrorModal, } from '../../libs/actions/BankAccounts'; import {navigateToConciergeChat} from '../../libs/actions/Report'; -import Button from '../../components/Button'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; import Navigation from '../../libs/Navigation/Navigation'; import ExpensiTextInput from '../../components/ExpensiTextInput'; @@ -24,7 +23,9 @@ import {isRequiredFulfilled} from '../../libs/ValidationUtils'; import EnableStep from './EnableStep'; import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; import ReimbursementAccountForm from './ReimbursementAccountForm'; -import LetsChatImage from '../../../assets/images/lets-chat.svg'; +import {ChatBubble} from '../../components/Icon/Expensicons'; +import {ConciergeBlue} from '../../components/Icon/Illustrations'; +import WorkspaceSection from '../workspace/WorkspaceSection'; const propTypes = { ...withLocalizePropTypes, @@ -150,9 +151,6 @@ class ValidationStep extends React.Component { } navigateToConcierge() { - // There are two modals that must be dismissed before we can reveal the Concierge - // chat underneath these screens - Navigation.dismissModal(); Navigation.dismissModal(); navigateToConciergeChat(); } @@ -171,9 +169,11 @@ class ValidationStep extends React.Component { return ( Navigation.goBack()} + shouldShowBackButton /> {maxAttemptsReached && ( @@ -231,18 +231,20 @@ class ValidationStep extends React.Component { )} {isVerifying && ( - - - - - {this.props.translate('validationStep.letsChatText')} - -