diff --git a/.gitignore b/.gitignore index b3f18d309e6..e03ba726246 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ build/ local.properties *.iml android/*.hprof +android/app/src/main/java/com/expensify/chat/generated/ # Vscode .vscode diff --git a/android/app/build.gradle b/android/app/build.gradle index 3011237b111..52a02d50173 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -150,8 +150,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001008301 - versionName "1.0.83-1" + versionCode 1001008302 + versionName "1.0.83-2" } splits { abi { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6976c0f8c82..199dc57042f 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -55,7 +55,7 @@ - + diff --git a/ios/ExpensifyCash/Chat.entitlements b/ios/ExpensifyCash/Chat.entitlements index 2f8be2ab354..98e887b4423 100644 --- a/ios/ExpensifyCash/Chat.entitlements +++ b/ios/ExpensifyCash/Chat.entitlements @@ -6,9 +6,8 @@ development com.apple.developer.associated-domains - applinks:www.expensify.cash - applinks:staging.expensify.cash - applinks:expensify.cash + applinks:staging.new.expensify.com + applinks:new.expensify.com diff --git a/ios/ExpensifyCash/Info.plist b/ios/ExpensifyCash/Info.plist index bb9717379d1..ae4f4674d82 100644 --- a/ios/ExpensifyCash/Info.plist +++ b/ios/ExpensifyCash/Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 1.0.83.1 + 1.0.83.2 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/ExpensifyCashTests/Info.plist b/ios/ExpensifyCashTests/Info.plist index 88bbe484331..8de36bb3036 100644 --- a/ios/ExpensifyCashTests/Info.plist +++ b/ios/ExpensifyCashTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.0.83.1 + 1.0.83.2 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 266fa84eaa6..597a1d8fef3 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -872,7 +872,7 @@ SPEC CHECKSUMS: DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de EXHaptics: 337c160c148baa6f0e7166249f368965906e346b FBLazyVector: 7b423f9e248eae65987838148c36eec1dbfe0b53 - FBReactNativeSpec: 825b0f0851f5cc5c6268a920286281f62fc96c37 + FBReactNativeSpec: c783a75db87c963c60afcd461fc38358805fe5ba Firebase: 54cdc8bc9c9b3de54f43dab86e62f5a76b47034f FirebaseABTesting: 4cb61aeeb50f60680af1c01fff781dfaf9293916 FirebaseAnalytics: 4751d6a49598a2b58da678cc07df696bcd809ab9 @@ -899,9 +899,9 @@ SPEC CHECKSUMS: Onfido: 116a268e4cb8b767c15285e8071c2e8304673cdf onfido-react-native-sdk: b8f1b7cbe1adab6479d735275772390161630dcd OpenSSL-Universal: 1aa4f6a6ee7256b83db99ec1ccdaa80d10f9af9b - Permission-LocationAccuracy: 76669f87b4c276f5ae803cc0ddd1862a4c0e9dd8 - Permission-LocationAlways: a274bc04bb386068782468dbdaca3859f51634ca - Permission-LocationWhenInUse: 3a2b0dbc167d79e8e920a4377ff9520cdc108407 + Permission-LocationAccuracy: e8adff9ede1b23b43b7054a4500113d515fc87a8 + Permission-LocationAlways: 7f7f373d086af7a81b2f4f20d65d29266ca2043b + Permission-LocationWhenInUse: 3ae82a9feb5da4e94e386dba17c7dd3531af9feb Plaid: f55c6acdc249245c6778a4045757eb4e839cca61 PromisesObjC: 68159ce6952d93e17b2dfe273b8c40907db5ba58 Protobuf: 7327d4444215b5f18e560a97f879ff5503c4581c @@ -917,15 +917,15 @@ SPEC CHECKSUMS: React-jsiexecutor: 124e8f99992490d0d13e0649d950d3e1aae06fe9 React-jsinspector: 500a59626037be5b3b3d89c5151bc3baa9abf1a9 react-native-config: d8b45133fd13d4f23bd2064b72f6e2c08b2763ed - react-native-document-picker: f2f73db94328c84e22144e369fb4a3ede47bc1f5 + react-native-document-picker: 0e3602a4064da040321bafad6848d8b0edcb1d55 react-native-flipper: 1943b82f2e494c77b741eb1ed257b6734a334b83 - react-native-image-picker: 474cf2c33c2b6671da53d293a16c97995f0aec15 - react-native-netinfo: 30fb89fa913c342be82a887b56e96be6d71201dd + react-native-image-picker: 4089335b89b625d4e34d53fb249c48a7a791b3ea + react-native-netinfo: 52cf0ee8342548a485e28f4b09e56b477567244d react-native-pdf: 4b5a9e4465a6a3b399e91dc4838eb44ddf716d1f - react-native-plaid-link-sdk: 59b7376efca9f00e9693321c5cf7c6ab2c567635 - react-native-progress-bar-android: be43138ab7da30d51fc038bafa98e9ed594d0c40 - react-native-progress-view: 21b1e29e70c7559c16c9e0a04c4adc19fce6ede2 - react-native-safe-area-context: 79fea126c6830c85f65947c223a5e3058a666937 + react-native-plaid-link-sdk: 1a6593e2d3d790e8113c29178d883eb883f8c032 + react-native-progress-bar-android: ce95a69f11ac580799021633071368d08aaf9ad8 + react-native-progress-view: 5816e8a6be812c2b122c6225a2a3db82d9008640 + react-native-safe-area-context: 01158a92c300895d79dee447e980672dc3fb85a6 React-perflogger: aad6d4b4a267936b3667260d1f649b6f6069a675 React-RCTActionSheet: fc376be462c9c8d6ad82c0905442fd77f82a9d2a React-RCTAnimation: ba0a1c3a2738be224a08092fa7f1b444ab77d309 @@ -939,17 +939,17 @@ SPEC CHECKSUMS: React-runtimeexecutor: ff951a0c241bfaefc4940a3f1f1a229e7cb32fa6 ReactCommon: bedc99ed4dae329c4fcf128d0c31b9115e5365ca rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba - RNBootSplash: 24175aa28fe203b10c48dc34e78d946fd33c77af + RNBootSplash: 3123ba68fe44d8be09a014e89cc8f0f55b68a521 RNCAsyncStorage: 8324611026e8dc3706f829953aa6e3899f581589 - RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495 - RNCMaskedView: fc29d354a40316a990e8fb46391f08aea829c3aa + RNCClipboard: 5e299c6df8e0c98f3d7416b86ae563d3a9f768a3 + RNCMaskedView: 138134c4d8a9421b4f2bf39055a79aa05c2d47b1 RNCPicker: 6780c753e9e674065db90d9c965920516402579d RNFBAnalytics: 8ba84c2d31c64374d054c8621b998f25145ffddc RNFBApp: 64c90ab78b6010ed5c3ade026dfe5ff6442c21fd RNFBCrashlytics: 1de18b8cc36d9bcf86407c4a354399228cc84a61 RNFBPerf: e3a7269f573a4787810a32de51647cdcbe08dfb4 RNGestureHandler: 9b7e605a741412e20e13c512738a31bd1611759b - RNPermissions: 4c8a37b4dde50f1f152bf8cd08c4a43d2355829e + RNPermissions: eb94f9fdc0a8ecd02fcce0676d56ffb1395d41e1 RNReanimated: 833ebd229b31e18a8933ebd0cd744a0f47d88c42 RNScreens: e8e8dd0588b5da0ab57dcca76ab9b2d8987757e0 RNSVG: ce9d996113475209013317e48b05c21ee988d42e @@ -966,7 +966,7 @@ SPEC CHECKSUMS: UMReactNativeAdapter: 7b458ca3d4497b5114e6bb766b223432bad22d8a UMSensorsInterface: 50439b47826e716a514cbd7384aebe9ab4fde5f4 UMTaskManagerInterface: 482155764886069beb1bc7fcf6036f12e4ad0751 - urbanairship-react-native: a05a913d6f9559141d477ff4d380bcd616b5c59d + urbanairship-react-native: d415a12e67ba93bf3ce914df9a310b66a88a5cc3 Yoga: a7de31c64fe738607e7a3803e3f591a4b1df7393 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a diff --git a/package-lock.json b/package-lock.json index 9aa4ff70837..4c9c96b5b30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.83-1", + "version": "1.0.83-2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -23084,8 +23084,8 @@ } }, "expensify-common": { - "version": "git://github.com/Expensify/expensify-common.git#4016a786d66ff38ed76a191edef31ebd737a0190", - "from": "git://github.com/Expensify/expensify-common.git#4016a786d66ff38ed76a191edef31ebd737a0190", + "version": "git://github.com/Expensify/expensify-common.git#8f286b5826a041ecb739cff1ad6940ffb9324bee", + "from": "git://github.com/Expensify/expensify-common.git#8f286b5826a041ecb739cff1ad6940ffb9324bee", "requires": { "classnames": "2.3.1", "clipboard": "2.0.4", @@ -23105,11 +23105,6 @@ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" }, - "jquery": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", - "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -29605,6 +29600,11 @@ "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.2.tgz", "integrity": "sha512-+az2gi/hvex7eLTMTlbRLOhH6P6WFdk2ITI8HJsaH2VqYO0I594zXSYEP+tf4FW+8Cy68ScDXoAsQdyQanv3sw==" }, + "jquery": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", + "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" + }, "js-string-escape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", @@ -36094,56 +36094,43 @@ } }, "react-native-onyx": { - "version": "git+https://github.com/Expensify/react-native-onyx.git#d73900b7cb7bf82bed77cb6b6baabf8fe2eb3a0e", - "from": "git+https://github.com/Expensify/react-native-onyx.git#d73900b7cb7bf82bed77cb6b6baabf8fe2eb3a0e", + "version": "git+https://github.com/Expensify/react-native-onyx.git#d7553b95e982ab78f6bb2064f6b0549f0ace94c2", + "from": "git+https://github.com/Expensify/react-native-onyx.git#d7553b95e982ab78f6bb2064f6b0549f0ace94c2", "requires": { "ascii-table": "0.0.9", - "expensify-common": "git+https://github.com/Expensify/expensify-common.git#4016a786d66ff38ed76a191edef31ebd737a0190", + "expensify-common": "git+https://github.com/Expensify/expensify-common.git#2e5cff552cf132da90a3fb9756e6b4fb6ae7b40c", "lodash": "4.17.21", "underscore": "^1.13.1" }, "dependencies": { - "classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - }, "expensify-common": { - "version": "git+https://github.com/Expensify/expensify-common.git#4016a786d66ff38ed76a191edef31ebd737a0190", - "from": "git+https://github.com/Expensify/expensify-common.git#4016a786d66ff38ed76a191edef31ebd737a0190", + "version": "git+https://github.com/Expensify/expensify-common.git#2e5cff552cf132da90a3fb9756e6b4fb6ae7b40c", + "from": "git+https://github.com/Expensify/expensify-common.git#2e5cff552cf132da90a3fb9756e6b4fb6ae7b40c", "requires": { - "classnames": "2.3.1", + "classnames": "2.2.5", "clipboard": "2.0.4", "html-entities": "^1.3.1", - "jquery": "3.6.0", + "jquery": "3.3.1", "lodash": "4.17.21", "prop-types": "15.7.2", "react": "16.12.0", "react-dom": "16.12.0", - "semver": "^7.3.5", + "semver": "^7.3.4", "simply-deferred": "git+https://github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5", - "underscore": "1.13.1" + "underscore": "1.9.1" }, "dependencies": { "underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==" + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" } } }, "jquery": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", - "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", + "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" }, "react": { "version": "16.12.0", @@ -36166,23 +36153,10 @@ "scheduler": "^0.18.0" } }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "requires": { - "lru-cache": "^6.0.0" - } - }, "underscore": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } }, diff --git a/package.json b/package.json index ea9a248c11e..69c3a6b1581 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.83-1", + "version": "1.0.83-2", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "Expensify.cash is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -60,7 +60,7 @@ "electron-log": "^4.3.5", "electron-serve": "^1.0.0", "electron-updater": "^4.3.4", - "expensify-common": "git://github.com/Expensify/expensify-common.git#4016a786d66ff38ed76a191edef31ebd737a0190", + "expensify-common": "git://github.com/Expensify/expensify-common.git#8f286b5826a041ecb739cff1ad6940ffb9324bee", "expo-haptics": "^10.0.0", "file-loader": "^6.0.0", "html-entities": "^1.3.1", @@ -84,7 +84,7 @@ "react-native-image-picker": "^4.0.3", "react-native-keyboard-spacer": "^0.4.1", "react-native-modal": "^11.10.0", - "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#d73900b7cb7bf82bed77cb6b6baabf8fe2eb3a0e", + "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#d7553b95e982ab78f6bb2064f6b0549f0ace94c2", "react-native-pdf": "^6.2.2", "react-native-permissions": "^3.0.1", "react-native-picker-select": "8.0.4", diff --git a/src/CONST.js b/src/CONST.js index da45292973d..51b4c772020 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -122,7 +122,7 @@ const CONST = { NEW_GOOGLE_MEET_MEETING_URL: 'https://meet.google.com/new', PDF_VIEWER_URL: '/pdf/web/viewer.html', EXPENSIFY_ICON_URL: `${CLOUDFRONT_URL}/images/favicon-2019.png`, - UPWORK_URL: 'https://www.upwork.com/ab/jobs/search/?q=Expensify%20React%20Native&user_location_match=2', + UPWORK_URL: 'https://github.com/Expensify/App/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22', GITHUB_URL: 'https://github.com/Expensify/Expensify.cash', TERMS_URL: 'https://use.expensify.com/terms', PRIVACY_URL: 'https://use.expensify.com/privacy', @@ -343,6 +343,7 @@ const CONST = { ERROR: 'error', WARNING: 'warning', DURATION: 2000, + DURATION_LONG: 3500, }, DEFAULT_LOCALE: 'en', diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js index 8c1829e7910..995d83607b6 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.js @@ -3,7 +3,6 @@ import React from 'react'; import { ActivityIndicator, View, - TextInput, } from 'react-native'; import PropTypes from 'prop-types'; import lodashGet from 'lodash/get'; @@ -20,8 +19,9 @@ import canFocusInputOnScreenFocus from '../libs/canFocusInputOnScreenFocus'; import compose from '../libs/compose'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import Button from './Button'; -import Picker from './Picker'; +import ExpensiPicker from './ExpensiPicker'; import Text from './Text'; +import ExpensiTextInput from './ExpensiTextInput'; const propTypes = { ...withLocalizePropTypes, @@ -161,7 +161,7 @@ class AddPlaidBankAccount extends React.Component { https://d2k5nsl2zxldvw.cloudfront.net/images/plaid/bg_plaidLogos_12@2x.png */} {this.state.institution.name} - { this.setState({selectedIndex: Number(index)}); }} @@ -175,12 +175,9 @@ class AddPlaidBankAccount extends React.Component { {!_.isUndefined(this.state.selectedIndex) && ( - - {this.props.translate('addPersonalBankAccountPage.enterPassword')} - - ; + +export default CardOverlay; diff --git a/src/components/ExpensiPicker.js b/src/components/ExpensiPicker.js new file mode 100644 index 00000000000..c8074414629 --- /dev/null +++ b/src/components/ExpensiPicker.js @@ -0,0 +1,58 @@ +import React, {PureComponent} from 'react'; +import {Text, View} from 'react-native'; +import PropTypes from 'prop-types'; +import Picker from './Picker'; +import styles from '../styles/styles'; + +const propTypes = { + /** Picker label */ + label: PropTypes.string, + + /** Should the picker appear disabled? */ + isDisabled: PropTypes.bool, +}; + +const defaultProps = { + label: '', + isDisabled: false, +}; + +class ExpensiPicker extends PureComponent { + constructor() { + super(); + this.state = { + isOpen: false, + }; + } + + render() { + const { + label, isDisabled, ...pickerProps + } = this.props; + return ( + + {label && ( + {label} + )} + this.setState({isOpen: true})} + onClose={() => this.setState({isOpen: false})} + disabled={isDisabled} + // eslint-disable-next-line react/jsx-props-no-spreading + {...pickerProps} + /> + + ); + } +} + +ExpensiPicker.propTypes = propTypes; +ExpensiPicker.defaultProps = defaultProps; + +export default ExpensiPicker; diff --git a/src/components/ExpensiTextInput/BaseExpensiTextInput.js b/src/components/ExpensiTextInput/BaseExpensiTextInput.js new file mode 100644 index 00000000000..7194a35fe27 --- /dev/null +++ b/src/components/ExpensiTextInput/BaseExpensiTextInput.js @@ -0,0 +1,139 @@ +import React, {Component} from 'react'; +import { + Animated, TextInput, View, TouchableWithoutFeedback, +} from 'react-native'; +import ExpensiTextInputLabel from './ExpensiTextInputLabel'; +import {propTypes, defaultProps} from './propTypes'; +import themeColors from '../../styles/themes/default'; +import styles from '../../styles/styles'; + +const ACTIVE_LABEL_TRANSLATE_Y = -10; +const ACTIVE_LABEL_TRANSLATE_X = (translateX = -22) => translateX; +const ACTIVE_LABEL_SCALE = 0.8668; + +const INACTIVE_LABEL_TRANSLATE_Y = 0; +const INACTIVE_LABEL_TRANSLATE_X = 0; +const INACTIVE_LABEL_SCALE = 1; + +class BaseExpensiTextInput extends Component { + constructor(props) { + super(props); + + const hasValue = props.value.length > 0; + + this.state = { + isFocused: false, + labelTranslateY: new Animated.Value(hasValue ? ACTIVE_LABEL_TRANSLATE_Y : INACTIVE_LABEL_TRANSLATE_Y), + labelTranslateX: new Animated.Value(hasValue + ? ACTIVE_LABEL_TRANSLATE_X(props.translateX) : INACTIVE_LABEL_TRANSLATE_X), + labelScale: new Animated.Value(hasValue ? ACTIVE_LABEL_SCALE : INACTIVE_LABEL_SCALE), + }; + + this.input = null; + this.onFocus = this.onFocus.bind(this); + this.onBlur = this.onBlur.bind(this); + } + + onFocus() { + if (this.props.onFocus) { this.props.onFocus(); } + this.setState({isFocused: true}); + if (this.props.value.length === 0) { + this.animateLabel( + ACTIVE_LABEL_TRANSLATE_Y, + ACTIVE_LABEL_TRANSLATE_X(this.props.translateX), + ACTIVE_LABEL_SCALE, + ); + } + } + + onBlur() { + if (this.props.onBlur) { this.props.onBlur(); } + this.setState({isFocused: false}); + if (this.props.value.length === 0) { + this.animateLabel(INACTIVE_LABEL_TRANSLATE_Y, INACTIVE_LABEL_TRANSLATE_X, INACTIVE_LABEL_SCALE); + } + } + + animateLabel(translateY, translateX, scale) { + Animated.parallel([ + Animated.spring(this.state.labelTranslateY, { + toValue: translateY, + duration: 80, + useNativeDriver: true, + }), + Animated.spring(this.state.labelTranslateX, { + toValue: translateX, + duration: 80, + useNativeDriver: true, + }), + Animated.spring(this.state.labelScale, { + toValue: scale, + duration: 80, + useNativeDriver: true, + }), + ]).start(); + } + + render() { + const { + label, + value, + placeholder, + hasError, + containerStyles, + inputStyle, + ignoreLabelTranslateX, + innerRef, + ...inputProps + } = this.props; + + const hasLabel = Boolean(label.length); + return ( + + this.input.focus()}> + + {hasLabel ? ( + + ) : null} + { + if (typeof innerRef === 'function') { innerRef(ref); } + this.input = ref; + }} + // eslint-disable-next-line + {...inputProps} + value={value} + placeholder={(this.state.isFocused || !label) ? placeholder : null} + placeholderTextColor={themeColors.placeholderText} + underlineColorAndroid="transparent" + style={inputStyle} + onFocus={this.onFocus} + onBlur={this.onBlur} + /> + + + + ); + } +} + +BaseExpensiTextInput.propTypes = propTypes; +BaseExpensiTextInput.defaultProps = defaultProps; + +export default BaseExpensiTextInput; diff --git a/src/components/ExpensiTextInput/ExpensiTextInputLabel/index.js b/src/components/ExpensiTextInput/ExpensiTextInputLabel/index.js new file mode 100644 index 00000000000..b0ed11c58bb --- /dev/null +++ b/src/components/ExpensiTextInput/ExpensiTextInputLabel/index.js @@ -0,0 +1,30 @@ +import React, {memo} from 'react'; +import {Animated} from 'react-native'; +import styles from '../../../styles/styles'; +import propTypes from './propTypes'; + +const ExpensiTextInputLabel = ({ + label, + labelTranslateY, + labelTranslateX, + labelScale, +}) => ( + + {label} + +); + +ExpensiTextInputLabel.propTypes = propTypes; +ExpensiTextInputLabel.displayName = 'ExpensiTextInputLabel'; + +export default memo(ExpensiTextInputLabel); diff --git a/src/components/ExpensiTextInput/ExpensiTextInputLabel/index.native.js b/src/components/ExpensiTextInput/ExpensiTextInputLabel/index.native.js new file mode 100644 index 00000000000..4e8159d5e2a --- /dev/null +++ b/src/components/ExpensiTextInput/ExpensiTextInputLabel/index.native.js @@ -0,0 +1,29 @@ +import React, {memo} from 'react'; +import {Animated} from 'react-native'; +import styles from '../../../styles/styles'; +import propTypes from './propTypes'; + +const ExpensiTextInputLabel = ({ + label, + labelTranslateX, + labelTranslateY, + labelScale, +}) => ( + + {label} + +); + +ExpensiTextInputLabel.propTypes = propTypes; +ExpensiTextInputLabel.displayName = 'ExpensiTextInputLabel'; + +export default memo(ExpensiTextInputLabel); diff --git a/src/components/ExpensiTextInput/ExpensiTextInputLabel/propTypes.js b/src/components/ExpensiTextInput/ExpensiTextInputLabel/propTypes.js new file mode 100644 index 00000000000..cfaf5f14d37 --- /dev/null +++ b/src/components/ExpensiTextInput/ExpensiTextInputLabel/propTypes.js @@ -0,0 +1,18 @@ +import PropTypes from 'prop-types'; +import {Animated} from 'react-native'; + +const propTypes = { + /** Label */ + label: PropTypes.string, + + /** Label vertical translate */ + labelTranslateY: PropTypes.instanceOf(Animated.Value).isRequired, + + /** Label horizontal translate */ + labelTranslateX: PropTypes.instanceOf(Animated.Value).isRequired, + + /** Label scale */ + labelScale: PropTypes.instanceOf(Animated.Value).isRequired, +}; + +export default propTypes; diff --git a/src/components/ExpensiTextInput/index.android.js b/src/components/ExpensiTextInput/index.android.js new file mode 100644 index 00000000000..35b9a15cfd8 --- /dev/null +++ b/src/components/ExpensiTextInput/index.android.js @@ -0,0 +1,23 @@ +import React, {forwardRef} from 'react'; +import styles from '../../styles/styles'; +import BaseExpensiTextInput from './BaseExpensiTextInput'; +import {propTypes, defaultProps} from './propTypes'; + +const ExpensiTextInput = forwardRef((props, ref) => ( + +)); + +ExpensiTextInput.propTypes = propTypes; +ExpensiTextInput.defaultProps = defaultProps; +ExpensiTextInput.displayName = 'ExpensiTextInput'; + +export default ExpensiTextInput; diff --git a/src/components/ExpensiTextInput/index.ios.js b/src/components/ExpensiTextInput/index.ios.js new file mode 100644 index 00000000000..9e84b12d135 --- /dev/null +++ b/src/components/ExpensiTextInput/index.ios.js @@ -0,0 +1,19 @@ +import React, {forwardRef} from 'react'; +import styles from '../../styles/styles'; +import BaseExpensiTextInput from './BaseExpensiTextInput'; +import {propTypes, defaultProps} from './propTypes'; + +const ExpensiTextInput = forwardRef((props, ref) => ( + +)); + +ExpensiTextInput.propTypes = propTypes; +ExpensiTextInput.defaultProps = defaultProps; +ExpensiTextInput.displayName = 'ExpensiTextInput'; + +export default ExpensiTextInput; diff --git a/src/components/ExpensiTextInput/index.js b/src/components/ExpensiTextInput/index.js new file mode 100644 index 00000000000..c56aaee213e --- /dev/null +++ b/src/components/ExpensiTextInput/index.js @@ -0,0 +1,20 @@ +import React, {forwardRef} from 'react'; +import styles from '../../styles/styles'; +import BaseExpensiTextInput from './BaseExpensiTextInput'; +import {propTypes, defaultProps} from './propTypes'; + +const ExpensiTextInput = forwardRef((props, ref) => ( + +)); + +ExpensiTextInput.propTypes = propTypes; +ExpensiTextInput.defaultProps = defaultProps; +ExpensiTextInput.displayName = 'ExpensiTextInput'; + +export default ExpensiTextInput; diff --git a/src/components/ExpensiTextInput/propTypes.js b/src/components/ExpensiTextInput/propTypes.js new file mode 100644 index 00000000000..6231018ccee --- /dev/null +++ b/src/components/ExpensiTextInput/propTypes.js @@ -0,0 +1,39 @@ +import PropTypes from 'prop-types'; + +const propTypes = { + /** Input label */ + label: PropTypes.string, + + /** Input value */ + value: PropTypes.string.isRequired, + + /** Input value placeholder */ + placeholder: PropTypes.string, + + /** Should the input be styled for errors */ + hasError: PropTypes.bool, + + /** Customize the ExpensiTextInput container */ + containerStyles: PropTypes.arrayOf(PropTypes.object), + + /** label translate x */ + translateX: PropTypes.number, + + /** input style */ + inputStyle: PropTypes.arrayOf(PropTypes.object), + + /** should ignore labels translate x? */ + ignoreLabelTranslateX: PropTypes.bool, +}; + +const defaultProps = { + label: '', + placeholder: '', + error: false, + containerStyles: [], + translateX: -22, + inputStyle: [], + ignoreLabelTranslateX: false, +}; + +export {propTypes, defaultProps}; diff --git a/src/components/FullNameInputRow.js b/src/components/FullNameInputRow.js index 180a642c526..6ed98c5be04 100644 --- a/src/components/FullNameInputRow.js +++ b/src/components/FullNameInputRow.js @@ -1,11 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {TextInput, View} from 'react-native'; +import {View} from 'react-native'; import _ from 'underscore'; import styles from '../styles/styles'; -import Text from './Text'; -import themeColors from '../styles/themes/default'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; +import ExpensiTextInput from './ExpensiTextInput'; const propTypes = { ...withLocalizePropTypes, @@ -19,15 +18,9 @@ const propTypes = { /** Used to prefill the firstName input, can also be used to make it a controlled input */ firstName: PropTypes.string, - /** Placeholder text for the firstName input */ - firstNamePlaceholder: PropTypes.string, - /** Used to prefill the lastName input, can also be used to make it a controlled input */ lastName: PropTypes.string, - /** Placeholder text for the lastName input */ - lastNamePlaceholder: PropTypes.string, - /** Additional styles to add after local styles */ style: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.object), @@ -36,9 +29,7 @@ const propTypes = { }; const defaultProps = { firstName: '', - firstNamePlaceholder: null, lastName: '', - lastNamePlaceholder: null, style: {}, }; @@ -46,35 +37,27 @@ const FullNameInputRow = ({ translate, onChangeFirstName, onChangeLastName, firstName, lastName, - firstNamePlaceholder, - lastNamePlaceholder, style, }) => { const additionalStyles = _.isArray(style) ? style : [style]; return ( - - {translate('common.firstName')} - - - - {translate('common.lastName')} - - diff --git a/src/components/IOUConfirmationList.js b/src/components/IOUConfirmationList.js index 5d3de1ee96e..33ba326f204 100755 --- a/src/components/IOUConfirmationList.js +++ b/src/components/IOUConfirmationList.js @@ -1,7 +1,7 @@ import React, {Component} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; -import {ScrollView, TextInput} from 'react-native-gesture-handler'; +import {ScrollView} from 'react-native-gesture-handler'; import {withOnyx} from 'react-native-onyx'; import {withSafeAreaInsets} from 'react-native-safe-area-context'; import _ from 'underscore'; @@ -20,6 +20,7 @@ import SafeAreaInsetPropTypes from '../pages/SafeAreaInsetPropTypes'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import compose from '../libs/compose'; import FixedFooter from './FixedFooter'; +import ExpensiTextInput from './ExpensiTextInput'; import CONST from '../CONST'; const propTypes = { @@ -345,12 +346,9 @@ class IOUConfirmationList extends Component { disableRowInteractivity={!this.props.hasMultipleParticipants} optionHoveredStyle={hoverStyle} /> - - {this.props.translate('iOUConfirmationList.whatsItFor')} - - { + onStartShouldSetPanResponder={() => { const isDoubleClick = new Date().getTime() - this.lastClickTime <= this.doubleClickInterval; this.lastClickTime = new Date().getTime(); // Let ImageZoom handle the event if the tap is more than one touchPoint or if we are zoomed in - if (e.nativeEvent.touches.length === 2 || this.imageZoomScale !== 1) { + if (this.amountOfTouches === 2 || this.imageZoomScale !== 1) { return true; } @@ -95,6 +118,20 @@ class ImageView extends PureComponent { this.setState({imageHeight, imageWidth}); }} /> + {/** + Create an invisible view on top of the image so we can capture and set the amount of touches before + the ImageZoom's PanResponder does. Children will be triggered first, so this needs to be inside the + ImageZoom to work + */} + ); diff --git a/src/components/LocalePicker.js b/src/components/LocalePicker.js index b64707cc16a..6df228ca477 100644 --- a/src/components/LocalePicker.js +++ b/src/components/LocalePicker.js @@ -1,9 +1,6 @@ import React from 'react'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; -import styles from '../styles/styles'; -import Picker from './Picker'; -import Text from './Text'; import compose from '../libs/compose'; import {setLocale} from '../libs/actions/App'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; @@ -11,6 +8,7 @@ import ONYXKEYS from '../ONYXKEYS'; import CONST from '../CONST'; import Permissions from '../libs/Permissions'; import {translate} from '../libs/translate'; +import ExpensiPicker from './ExpensiPicker'; const propTypes = { /** Indicates which locale the user currently has selected */ @@ -51,23 +49,17 @@ const LocalePicker = ({ } return ( - <> - {size === 'normal' && ( - - {translate('preferencesPage.language')} - - )} - { - if (locale !== preferredLocale) { - setLocale(locale); - } - }} - items={Object.values(localesToLanguages)} - size={size} - value={preferredLocale} - /> - + { + if (locale !== preferredLocale) { + setLocale(locale); + } + }} + items={Object.values(localesToLanguages)} + size={size} + value={preferredLocale} + /> ); }; diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index f046068643e..77440b9e345 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -140,7 +140,7 @@ const MenuItem = ({ {title} {description && ( - + {description} )} @@ -151,7 +151,7 @@ const MenuItem = ({ {subtitle && ( {subtitle} diff --git a/src/components/OptionsSelector.js b/src/components/OptionsSelector.js index beb39197ac8..fe246d6b4f3 100755 --- a/src/components/OptionsSelector.js +++ b/src/components/OptionsSelector.js @@ -2,12 +2,11 @@ import _ from 'underscore'; import React, {Component} from 'react'; import PropTypes from 'prop-types'; import {View} from 'react-native'; -import TextInputWithFocusStyles from './TextInputWithFocusStyles'; import OptionsList from './OptionsList'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/default'; import optionPropTypes from './optionPropTypes'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; +import ExpensiTextInput from './ExpensiTextInput'; const propTypes = { /** Callback to fire when a row is tapped */ @@ -195,16 +194,13 @@ class OptionsSelector extends Component { return ( - this.textInput = el} - style={[styles.textInput]} value={this.props.value} onChangeText={this.props.onChangeText} onKeyPress={this.handleKeyPress} placeholder={this.props.placeholderText || this.props.translate('optionsSelector.nameEmailOrPhoneNumber')} - placeholderTextColor={themeColors.placeholderText} /> ), + size: 'normal', }; export { diff --git a/src/components/Picker/index.js b/src/components/Picker/index.js index b963c81601b..28ff3ff877e 100644 --- a/src/components/Picker/index.js +++ b/src/components/Picker/index.js @@ -2,40 +2,35 @@ import React from 'react'; import RNPickerSelect from 'react-native-picker-select'; import styles from '../../styles/styles'; -import pickerDisabledStyles from './pickerDisabledStyles'; import * as pickerPropTypes from './PickerPropTypes'; +import pickerStyles from './pickerStyles'; const Picker = ({ onChange, items, - useDisabledStyles, placeholder, value, icon, disabled, + onOpen, + onClose, size, -}) => { - let pickerStyles; - if (size === 'small') { - pickerStyles = styles.pickerSmall; - } else { - pickerStyles = useDisabledStyles ? pickerDisabledStyles : styles.picker; - } +}) => ( + icon(size)} + disabled={disabled} + fixAndroidTouchableBug + onOpen={onOpen} + onClose={onClose} + /> +); - return ( - icon(size)} - disabled={disabled} - fixAndroidTouchableBug - /> - ); -}; Picker.propTypes = pickerPropTypes.propTypes; Picker.defaultProps = pickerPropTypes.defaultProps; diff --git a/src/components/Picker/pickerDisabledStyles/index.android.js b/src/components/Picker/pickerDisabledStyles/index.android.js deleted file mode 100644 index d978155e75d..00000000000 --- a/src/components/Picker/pickerDisabledStyles/index.android.js +++ /dev/null @@ -1,6 +0,0 @@ -import styles from '../../../styles/styles'; - -export default { - ...styles.picker, - inputAndroid: [styles.picker.inputAndroid, styles.textInput, styles.disabledTextInput], -}; diff --git a/src/components/Picker/pickerDisabledStyles/index.ios.js b/src/components/Picker/pickerDisabledStyles/index.ios.js deleted file mode 100644 index 83a8b81de73..00000000000 --- a/src/components/Picker/pickerDisabledStyles/index.ios.js +++ /dev/null @@ -1,6 +0,0 @@ -import styles from '../../../styles/styles'; - -export default { - ...styles.picker, - inputIOS: [styles.picker.inputIOS, styles.textInput, styles.disabledTextInput], -}; diff --git a/src/components/Picker/pickerDisabledStyles/index.js b/src/components/Picker/pickerDisabledStyles/index.js deleted file mode 100644 index 6fc2e9f3bbd..00000000000 --- a/src/components/Picker/pickerDisabledStyles/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import styles from '../../../styles/styles'; - -export default { - ...styles.picker, - inputWeb: [styles.picker.inputWeb, styles.textInput, styles.disabledTextInput], -}; diff --git a/src/components/Picker/pickerStyles/index.android.js b/src/components/Picker/pickerStyles/index.android.js new file mode 100644 index 00000000000..ea9a2882a09 --- /dev/null +++ b/src/components/Picker/pickerStyles/index.android.js @@ -0,0 +1,8 @@ +import styles from '../../../styles/styles'; + +const pickerStyles = disabled => ({ + ...styles.expensiPicker(disabled), + inputAndroid: styles.expensiPicker(disabled).inputNative, +}); + +export default pickerStyles; diff --git a/src/components/Picker/pickerStyles/index.ios.js b/src/components/Picker/pickerStyles/index.ios.js new file mode 100644 index 00000000000..0c144fd8ad2 --- /dev/null +++ b/src/components/Picker/pickerStyles/index.ios.js @@ -0,0 +1,8 @@ +import styles from '../../../styles/styles'; + +const pickerStyles = disabled => ({ + ...styles.expensiPicker(disabled), + inputIOS: styles.expensiPicker(disabled).inputNative, +}); + +export default pickerStyles; diff --git a/src/components/Picker/pickerStyles/index.js b/src/components/Picker/pickerStyles/index.js new file mode 100644 index 00000000000..7c11a9f8ed8 --- /dev/null +++ b/src/components/Picker/pickerStyles/index.js @@ -0,0 +1,5 @@ +import styles from '../../../styles/styles'; + +const pickerStyles = disabled => styles.expensiPicker(disabled); + +export default pickerStyles; diff --git a/src/components/TextInputWithLabel.js b/src/components/TextInputWithLabel.js index 63cdd87dbb8..40ac02a9fcb 100644 --- a/src/components/TextInputWithLabel.js +++ b/src/components/TextInputWithLabel.js @@ -54,7 +54,7 @@ const TextInputWithLabel = props => ( )} diff --git a/src/components/VBALoadingIndicator.js b/src/components/VBALoadingIndicator.js new file mode 100644 index 00000000000..a53c83e38e3 --- /dev/null +++ b/src/components/VBALoadingIndicator.js @@ -0,0 +1,35 @@ +import React from 'react'; +import {Image, StyleSheet, View} from 'react-native'; +import styles from '../styles/styles'; +import CONST from '../CONST'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; +import Text from './Text'; + +const propTypes = { + ...withLocalizePropTypes, +}; + +const VBALoadingIndicator = ({translate}) => ( + + + + + + {translate('vbaLoadingAnimation.oneMoment')} + + + {translate('vbaLoadingAnimation.explanationLine')} + + + + +); + +VBALoadingIndicator.propTypes = propTypes; + +export default withLocalize(VBALoadingIndicator); diff --git a/src/languages/en.js b/src/languages/en.js index a2d9379d2a9..39fafd4d7da 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -14,6 +14,7 @@ export default { add: 'Add', resend: 'Resend', save: 'Save', + saveChanges: 'Save Changes', password: 'Password', profile: 'Profile', payments: 'Payments', @@ -368,7 +369,7 @@ export default { addressState: 'Please select a valid state', incorporationDate: 'Please enter a valid incorporation date', incorporationState: 'Please enter a valid Incorporation State', - industryCode: 'Please enter a valid industry classification code', + industryCode: 'Please enter a valid industry classification code. Must be 6 digits.', restrictedBusiness: 'Please confirm company is not on the list of restricted businesses', routingNumber: 'Please enter a valid Routing Number', companyType: 'Please enter a valid Company Type', @@ -546,6 +547,10 @@ export default { certify: 'Must certify information is true and accurate', }, }, + vbaLoadingAnimation: { + oneMoment: 'One moment...', + explanationLine: 'We’re taking a look at your information. You will be able to continue with next steps shortly.', + }, session: { offlineMessageRetry: 'Looks like you\'re offline. Please check your connection and try again.', offlineMessage: 'Looks like you\'re offline.', @@ -563,6 +568,7 @@ export default { helpText: 'Name your Workspace before enabling your Expensify Cards!', getStarted: 'Get started!', genericFailureMessage: 'An error occurred creating the workspace, please try again.', + successMessage: 'Workspace created', }, people: { assignee: 'Assignee', @@ -597,6 +603,9 @@ export default { genericFailureMessage: 'An error occurred updating the workspace, please try again.', avatarUploadFailureMessage: 'An error occurred uploading the avatar, please try again.', }, + error: { + growlMessageInvalidPolicy: 'Invalid workspace! You can create a new workspace!', + }, }, requestCallPage: { requestACall: 'Request a Call', diff --git a/src/languages/es.js b/src/languages/es.js index c93e0a4103e..c04745f3651 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -14,6 +14,7 @@ export default { add: 'Agregar', resend: 'Reenviar', save: 'Guardar', + saveChanges: 'Guardar cambios', password: 'Contraseña', profile: 'Perfil', payments: 'Pagos', @@ -548,6 +549,10 @@ export default { certify: 'Debe certificar que la información es verdadera y precisa', }, }, + vbaLoadingAnimation: { + oneMoment: 'Un momento...', + explanationLine: 'Estamos verificando tu información y podrás continuar con los siguientes pasos en unos momentos.', + }, session: { offlineMessageRetry: 'Parece que estás desconectado. Por favor chequea tu conexión e inténtalo otra vez', offlineMessage: 'Parece que estás desconectado.', @@ -565,6 +570,7 @@ export default { helpText: 'Elige un nombre para el espacio de trabajo antes de activar las tarjetas Expensify', getStarted: '¡Empezar!', genericFailureMessage: 'Se ha producido un error al intentar crear el Workspace. Por favor, inténtalo de nuevo.', + successMessage: 'Espacio de trabajo creado', }, people: { assignee: 'Persona asignada', @@ -599,6 +605,9 @@ export default { 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! ¡Puedes crear un nuevo espacio de trabajo!', + }, }, requestCallPage: { requestACall: 'Llámame por teléfono', diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index f4f1379628d..45f6077b922 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -65,6 +65,8 @@ import Timers from '../../Timers'; import ValidateLoginNewWorkspacePage from '../../../pages/ValidateLoginNewWorkspacePage'; import ValidateLogin2FANewWorkspacePage from '../../../pages/ValidateLogin2FANewWorkspacePage'; import WorkspaceSettingsDrawerNavigator from './WorkspaceSettingsDrawerNavigator'; +import spacing from '../../../styles/utilities/spacing'; +import CardOverlay from '../../../components/CardOverlay'; import defaultScreenOptions from './defaultScreenOptions'; Onyx.connect({ @@ -253,10 +255,14 @@ class AuthScreens extends React.Component { }; const fullscreenModalScreenOptions = { ...commonModalScreenOptions, - cardStyle: {...styles.fullscreenCard}, + cardStyle: { + ...styles.fullscreenCard, + padding: this.props.isSmallScreenWidth ? spacing.p0.padding : spacing.p5.padding, + }, cardStyleInterpolator: props => modalCardStyleInterpolator(this.props.isSmallScreenWidth, true, props), - cardOverlayEnabled: false, + cardOverlayEnabled: !this.props.isSmallScreenWidth, isFullScreenModal: true, + cardOverlay: CardOverlay, }; return ( @@ -279,6 +285,7 @@ class AuthScreens extends React.Component { // prevent unnecessary scrolling cardStyle: { overflow: 'hidden', + height: '100%', }, }} component={MainDrawerNavigator} diff --git a/src/libs/Navigation/AppNavigator/BaseDrawerNavigator.js b/src/libs/Navigation/AppNavigator/BaseDrawerNavigator.js index e794db66960..5a6d277b195 100644 --- a/src/libs/Navigation/AppNavigator/BaseDrawerNavigator.js +++ b/src/libs/Navigation/AppNavigator/BaseDrawerNavigator.js @@ -2,6 +2,7 @@ import React from 'react'; import _ from 'underscore'; import PropTypes from 'prop-types'; import {createDrawerNavigator} from '@react-navigation/drawer'; +import {View} from 'react-native'; import styles, {getNavigationDrawerStyle, getNavigationDrawerType} from '../../../styles/styles'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; @@ -22,38 +23,51 @@ const propTypes = { /** Drawer content Component */ drawerContent: PropTypes.elementType.isRequired, + /** If it's the main screen, don't wrap the content even if it's a full screen modal. */ + isMainScreen: PropTypes.bool, + /** Window Dimensions props */ ...windowDimensionsPropTypes, }; const Drawer = createDrawerNavigator(); -const BaseDrawerNavigator = props => ( - - {_.map(props.screens, screen => ( - - ))} - -); +const BaseDrawerNavigator = (props) => { + const content = ( + + {_.map(props.screens, screen => ( + + ))} + + ); + + if (!props.isMainScreen && !props.isSmallScreenWidth) { + return ( + + {content} + + ); + } + + return content; +}; BaseDrawerNavigator.propTypes = propTypes; BaseDrawerNavigator.displayName = 'BaseDrawerNavigator'; diff --git a/src/libs/Navigation/AppNavigator/MainDrawerNavigator.js b/src/libs/Navigation/AppNavigator/MainDrawerNavigator.js index de10b3fc936..382aad6fef8 100644 --- a/src/libs/Navigation/AppNavigator/MainDrawerNavigator.js +++ b/src/libs/Navigation/AppNavigator/MainDrawerNavigator.js @@ -54,6 +54,7 @@ const MainDrawerNavigator = (props) => { initialParams, }, ]} + isMainScreen /> ); }; diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js index b3f016ac3ab..bd5ebe15bf8 100644 --- a/src/libs/actions/BankAccounts.js +++ b/src/libs/actions/BankAccounts.js @@ -10,6 +10,7 @@ import BankAccount from '../models/BankAccount'; import promiseAllSettled from '../promiseAllSettled'; import Growl from '../Growl'; import {translateLocal} from '../translate'; +import Navigation from '../Navigation/Navigation'; /** * List of bank accounts. This data should not be stored in Onyx since it contains unmasked PANs. @@ -328,6 +329,11 @@ function fetchUserWallet() { * @param {String} [stepToOpen] */ function fetchFreePlanVerifiedBankAccount(stepToOpen) { + const oldACHData = { + accountNumber: reimbursementAccountInSetup.accountNumber || '', + routingNumber: reimbursementAccountInSetup.routingNumber || '', + }; + // 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}); @@ -478,7 +484,18 @@ function fetchFreePlanVerifiedBankAccount(stepToOpen) { goToWithdrawalAccountSetupStep(currentStep, achData); }) .finally(() => { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false}); + const dataToMerge = { + loading: false, + }; + + // If we didn't get a routingNumber and accountNumber from the response and we have previously saved + // values, autofill them + if (!reimbursementAccountInSetup.routingNumber && !reimbursementAccountInSetup.accountNumber + && oldACHData.routingNumber && oldACHData.accountNumber) { + dataToMerge.achData = oldACHData; + } + + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, dataToMerge); }); }); } @@ -553,8 +570,20 @@ function validateBankAccount(bankAccountID, validateCode) { Growl.show('Bank Account successfully validated!', CONST.GROWL.SUCCESS, 3000); API.User_IsUsingExpensifyCard() .then(({isUsingExpensifyCard}) => { + const reimbursementAccount = { + loading: false, + error: '', + achData: {state: BankAccount.STATE.OPEN}, + }; + + if (isUsingExpensifyCard) { + Navigation.dismissModal(); + } else { + reimbursementAccount.achData.currentStep = CONST.BANK_ACCOUNT.STEP.ENABLE; + } + Onyx.merge(ONYXKEYS.USER, {isUsingExpensifyCard}); - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false, error: ''}); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, reimbursementAccount); }); return; } @@ -602,9 +631,18 @@ function setupWithdrawalAccount(data) { } API.BankAccount_SetupWithdrawal(newACHData) + /* eslint-disable arrow-body-style */ + .then((response) => { + // Without this block, we can call merge again with the achData before this merge finishes, resulting in + // the original achData overwriting the data we're trying to set here. With this block, we ensure that the + // newACHData is set in Onyx before we call merge on the reimbursementAccount key again. + return Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, { + loading: false, + achData: {...newACHData}, + }) + .then(() => Promise.resolve(response)); + }) .then((response) => { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false, achData: {...newACHData}}); - const currentStep = newACHData.currentStep; let achData = lodashGet(response, 'achData', {}); let error = lodashGet(achData, CONST.BANK_ACCOUNT.VERIFICATIONS.ERROR_MESSAGE); diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index fd3f3ecc063..2f5bf480e14 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -87,7 +87,14 @@ function getPolicyList() { avatarURL: lodashGet(policy, 'value.avatarURL', ''), }, }), {}); - Onyx.mergeCollection(ONYXKEYS.COLLECTION.POLICY, policyDataToStore); + + Onyx.mergeCollection(ONYXKEYS.COLLECTION.POLICY, { + // Erase all policies in Onyx + ...(_.reduce(_.keys(allPolicies), (memo, key) => ({...memo, [key]: null}), {})), + + // And overwrite them with only the ones returned by the API call + ...policyDataToStore, + }); } }); } @@ -185,6 +192,7 @@ function invite(logins, welcomeNote, policyID) { * @param {String} [name] */ function create(name = '') { + let res = null; API.Policy_Create({type: CONST.POLICY.TYPE.FREE, policyName: name}) .then((response) => { if (response.jsonCode !== 200) { @@ -193,16 +201,18 @@ function create(name = '') { Growl.error(errorMessage, 5000); return; } + res = response; - Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${response.policyID}`, { + return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${response.policyID}`, { employeeList: getSimplifiedEmployeeList(response.policy.employeeList), id: response.policyID, type: response.policy.type, name: response.policy.name, role: CONST.POLICY.ROLE.ADMIN, }); + }).then(() => { Navigation.dismissModal(); - Navigation.navigate(ROUTES.getWorkspaceCardRoute(response.policyID)); + Navigation.navigate(ROUTES.getWorkspaceCardRoute(res.policyID)); Growl.success(translateLocal('workspace.new.successMessage')); }); } diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 9976e32ca4a..4f9a766e8f3 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -855,6 +855,7 @@ function fetchOrCreateChatReport(participants, shouldNavigate = true) { .then((data) => { if (data.jsonCode !== 200) { console.error(data.message); + Growl.error(data.message); return; } diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.js b/src/pages/EnablePayments/AdditionalDetailsStep.js index e647bb1f3d8..dd65d99b9a4 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.js +++ b/src/pages/EnablePayments/AdditionalDetailsStep.js @@ -12,12 +12,12 @@ import Navigation from '../../libs/Navigation/Navigation'; import styles from '../../styles/styles'; import Button from '../../components/Button'; import Text from '../../components/Text'; -import TextInputWithLabel from '../../components/TextInputWithLabel'; import {activateWallet} from '../../libs/actions/BankAccounts'; import CONST from '../../CONST'; import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; import TextLink from '../../components/TextLink'; +import ExpensiTextInput from '../../components/ExpensiTextInput'; const propTypes = { ...withLocalizePropTypes, @@ -128,7 +128,7 @@ class AdditionalDetailsStep extends React.Component { {_.map(this.fields, field => ( - this.setState({[field.fieldName]: val})} diff --git a/src/pages/EnablePayments/TermsPage/LongTermsForm.js b/src/pages/EnablePayments/TermsPage/LongTermsForm.js index af7e7b1363b..ae22740a3de 100644 --- a/src/pages/EnablePayments/TermsPage/LongTermsForm.js +++ b/src/pages/EnablePayments/TermsPage/LongTermsForm.js @@ -85,7 +85,7 @@ const getLongTermsSections = () => termsData.map((section, index) => ( } - + {section.details} diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index b38563acdef..bcedfdb74cd 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -15,12 +15,12 @@ import Icon from '../../components/Icon'; import colors from '../../styles/colors'; import Navigation from '../../libs/Navigation/Navigation'; import CONST from '../../CONST'; -import TextInputWithLabel from '../../components/TextInputWithLabel'; import AddPlaidBankAccount from '../../components/AddPlaidBankAccount'; import CheckboxWithLabel from '../../components/CheckboxWithLabel'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import exampleCheckImage from '../../../assets/images/example-check-image.png'; import Text from '../../components/Text'; +import ExpensiTextInput from '../../components/ExpensiTextInput'; import { goToWithdrawalAccountSetupStep, hideExistingOwnersError, @@ -207,14 +207,14 @@ class BankAccountStep extends React.Component { style={[styles.exampleCheckImage, styles.mb5]} source={exampleCheckImage} /> - this.setState({routingNumber})} disabled={shouldDisableInputs} /> - acc || !this.state[curr].trim(), false); + const missingRequiredFields = this.requiredFields.reduce((acc, curr) => acc || !this.state[curr].trim(), false); + const shouldDisableSubmitButton = !this.state.hasNoConnectionToCannabis || missingRequiredFields; + return ( <> - - {this.props.translate('companyStep.subtitle')} - - {this.props.translate('companyStep.subtitle')} + this.setState({companyName})} value={this.state.companyName} disabled={shouldDisableCompanyName} /> - this.setState({addressStreet})} @@ -155,7 +148,7 @@ class CompanyStep extends React.Component { /> - this.setState({addressCity})} value={this.state.addressCity} @@ -169,13 +162,13 @@ class CompanyStep extends React.Component { /> - this.setState({addressZipCode})} value={this.state.addressZipCode} /> - - this.setState({website})} value={this.state.website} /> - - - {this.props.translate('companyStep.companyType')} - - ({value, label}))} - onChange={incorporationType => this.setState({incorporationType})} - value={this.state.incorporationType} - placeholder={{value: '', label: 'Type'}} - /> + + ({value, label}))} + onChange={incorporationType => this.setState({incorporationType})} + value={this.state.incorporationType} + placeholder={{value: '', label: 'Type'}} + /> + {/* TODO: Replace with date picker */} - this.setState({incorporationDate})} value={this.state.incorporationDate} @@ -225,7 +218,7 @@ class CompanyStep extends React.Component { {/* TODO: Replace with NAICS picker */} - this.setState({industryCode})} value={this.state.industryCode} /> - this.setState({password})} value={this.state.password} @@ -263,6 +256,15 @@ class CompanyStep extends React.Component { /> + this.setState({isConfirmModalOpen: false})} + prompt="Please double check any highlighted fields and try again." + isVisible={this.state.isConfirmModalOpen} + confirmText="Got it" + shouldShowCancelButton={false} + /> +