diff --git a/.github/actions/getMergeCommitForPullRequest/getMergeCommitForPullRequest.js b/.github/actions/getMergeCommitForPullRequest/getMergeCommitForPullRequest.js index 70e31e22966a..7d0e0b1f0e17 100644 --- a/.github/actions/getMergeCommitForPullRequest/getMergeCommitForPullRequest.js +++ b/.github/actions/getMergeCommitForPullRequest/getMergeCommitForPullRequest.js @@ -9,8 +9,8 @@ const DEFAULT_PAYLOAD = { }; const pullRequestNumber = ActionUtils.getJSONInput('PULL_REQUEST_NUMBER', {required: false}, null); -const user = ActionUtils.getJSONInput('USER', {required: false}, null); -const titleRegex = ActionUtils.getJSONInput('TITLE_REGEX', {required: false}, null); +const user = core.getInput('USER', {required: false}); +let titleRegex = core.getInput('TITLE_REGEX', {required: false}); if (pullRequestNumber) { console.log(`Looking for pull request w/ number: ${pullRequestNumber}`); @@ -21,6 +21,7 @@ if (user) { } if (titleRegex) { + titleRegex = new RegExp(titleRegex); console.log(`Looking for pull request w/ title matching: ${titleRegex.toString()}`); } @@ -58,7 +59,10 @@ if (pullRequestNumber) { .then(({data}) => outputMergeCommitHash(data)) .catch(handleUnknownError); } else { - GithubUtils.octokit.pulls.list(DEFAULT_PAYLOAD) + GithubUtils.octokit.pulls.list({ + ...DEFAULT_PAYLOAD, + state: 'all', + }) .then(({data}) => { const matchingPR = _.find(data, PR => PR.user.login === user && titleRegex.test(PR.title)); outputMergeCommitHash(matchingPR); diff --git a/.github/actions/getMergeCommitForPullRequest/index.js b/.github/actions/getMergeCommitForPullRequest/index.js index 091d750852f3..5eaa9ed6f954 100644 --- a/.github/actions/getMergeCommitForPullRequest/index.js +++ b/.github/actions/getMergeCommitForPullRequest/index.js @@ -19,8 +19,8 @@ const DEFAULT_PAYLOAD = { }; const pullRequestNumber = ActionUtils.getJSONInput('PULL_REQUEST_NUMBER', {required: false}, null); -const user = ActionUtils.getJSONInput('USER', {required: false}, null); -const titleRegex = ActionUtils.getJSONInput('TITLE_REGEX', {required: false}, null); +const user = core.getInput('USER', {required: false}); +let titleRegex = core.getInput('TITLE_REGEX', {required: false}); if (pullRequestNumber) { console.log(`Looking for pull request w/ number: ${pullRequestNumber}`); @@ -31,6 +31,7 @@ if (user) { } if (titleRegex) { + titleRegex = new RegExp(titleRegex); console.log(`Looking for pull request w/ title matching: ${titleRegex.toString()}`); } @@ -68,7 +69,10 @@ if (pullRequestNumber) { .then(({data}) => outputMergeCommitHash(data)) .catch(handleUnknownError); } else { - GithubUtils.octokit.pulls.list(DEFAULT_PAYLOAD) + GithubUtils.octokit.pulls.list({ + ...DEFAULT_PAYLOAD, + state: 'all', + }) .then(({data}) => { const matchingPR = _.find(data, PR => PR.user.login === user && titleRegex.test(PR.title)); outputMergeCommitHash(matchingPR); diff --git a/.github/workflows/cherryPick.yml b/.github/workflows/cherryPick.yml index dfc2f8703cfc..b3d1ad81d304 100644 --- a/.github/workflows/cherryPick.yml +++ b/.github/workflows/cherryPick.yml @@ -71,6 +71,7 @@ jobs: - name: Create branch for new pull request run: | + git config user.name ${{ github.actor }} git checkout -b cherry-pick-staging-${{ github.event.inputs.PULL_REQUEST_NUMBER }} git push --set-upstream origin cherry-pick-staging-${{ github.event.inputs.PULL_REQUEST_NUMBER }} @@ -99,9 +100,11 @@ jobs: USER: OSBotify TITLE_REGEX: Update version to ${{ env.NEW_VERSION }} - - name: Cherry-pick the merge commit to new branch + - name: Cherry-pick the merge commits to new branch id: cherryPick - run: git cherry-pick ${{ steps.getCPMergeCommit.outputs.MERGE_COMMIT_SHA }} ${{ steps.getVersionBumpMergeCommit.MERGE_COMMIT_SHA }} --mainline 1 + run: | + git fetch + git cherry-pick ${{ steps.getCPMergeCommit.outputs.MERGE_COMMIT_SHA }} ${{ steps.getVersionBumpMergeCommit.outputs.MERGE_COMMIT_SHA }} --mainline 1 continue-on-error: true # If there is a merge conflict, we'll just commit what we have, diff --git a/android/app/build.gradle b/android/app/build.gradle index 019db8a31492..3dd124d7a524 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -148,8 +148,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001006100 - versionName "1.0.61-0" + versionCode 1001006300 + versionName "1.0.63-0" } splits { abi { diff --git a/desktop/main.js b/desktop/main.js index 07282bd0109c..3e7b7db379e8 100644 --- a/desktop/main.js +++ b/desktop/main.js @@ -130,7 +130,6 @@ const mainWindow = (() => { width: 1200, height: 900, webPreferences: { - enableRemoteModule: true, nodeIntegration: true, }, titleBarStyle: 'hidden', @@ -229,7 +228,7 @@ const mainWindow = (() => { ipcMain.on(ELECTRON_EVENTS.REQUEST_VISIBILITY, (event) => { // This is how synchronous messages work in Electron // eslint-disable-next-line no-param-reassign - event.returnValue = browserWindow.isFocused(); + event.returnValue = browserWindow && browserWindow.isFocused(); }); // This allows the renderer process to bring the app diff --git a/ios/ExpensifyCash/Info.plist b/ios/ExpensifyCash/Info.plist index b903c22c401f..403a68a328bf 100644 --- a/ios/ExpensifyCash/Info.plist +++ b/ios/ExpensifyCash/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0.61 + 1.0.63 CFBundleSignature ???? CFBundleURLTypes @@ -30,7 +30,7 @@ CFBundleVersion - 1.0.61.0 + 1.0.63.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/ExpensifyCashTests/Info.plist b/ios/ExpensifyCashTests/Info.plist index 403d6368bd7f..f09a35ebe0c0 100644 --- a/ios/ExpensifyCashTests/Info.plist +++ b/ios/ExpensifyCashTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.0.61 + 1.0.63 CFBundleSignature ???? CFBundleVersion - 1.0.61.0 + 1.0.63.0 diff --git a/package-lock.json b/package-lock.json index da11ce8b7825..654b62486c5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.61-0", + "version": "1.0.63-0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2393,9 +2393,9 @@ } }, "@electron/get": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.12.2.tgz", - "integrity": "sha512-vAuHUbfvBQpYTJ5wB7uVIDq5c/Ry0fiTBMs7lnEYAo/qXXppIVcWdfBr57u6eRnKdVso7KSiH6p/LbQAG6Izrg==", + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.12.4.tgz", + "integrity": "sha512-6nr9DbJPUR9Xujw6zD3y+rS95TyItEVM0NVjt1EehY2vUWfIgPiIPVHxCvaTS0xr2B+DRxovYVKbuOWqC35kjg==", "dev": true, "requires": { "debug": "^4.1.1", @@ -2405,7 +2405,7 @@ "global-tunnel-ng": "^2.7.1", "got": "^9.6.0", "progress": "^2.0.3", - "sanitize-filename": "^1.6.2", + "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "dependencies": { @@ -2429,6 +2429,12 @@ "graceful-fs": "^4.1.6" } }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -15325,9 +15331,9 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, "boolean": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.1.tgz", - "integrity": "sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.1.0.tgz", + "integrity": "sha512-K6r5tvO1ykeYerI7jIyTvSFw2l6D6DzqkljGj2E2uyYAAdDo2SV4qGJIV75cHIQpTFyb6BB0BEHiDdDrFsNI+g==", "dev": true, "optional": true }, @@ -16800,9 +16806,9 @@ } }, "config-chain": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", - "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", "dev": true, "optional": true, "requires": { @@ -18526,9 +18532,9 @@ } }, "electron": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/electron/-/electron-9.3.2.tgz", - "integrity": "sha512-0lleEf9msAXGDi2GukAuiGdw3VDgSTlONOnJgqDEz1fuSEVsXz5RX+hNPKDsVDerLTFg/C34RuJf4LwHvkKcBA==", + "version": "11.4.8", + "resolved": "https://registry.npmjs.org/electron/-/electron-11.4.8.tgz", + "integrity": "sha512-NrxlDZN1sWiDCWWOm5aX+tPGtiLgsCUwNqNFP3eJfY+RPdYLsxYRJDFa1vc4GcuCZEp9kZusINjmpPWsvJdspQ==", "dev": true, "requires": { "@electron/get": "^1.0.1", @@ -18537,9 +18543,9 @@ }, "dependencies": { "@types/node": { - "version": "12.12.68", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.68.tgz", - "integrity": "sha512-3RW2s24ewB7F9dAHvgb9FRvNHn6nO9IK6Eaknbz7HTOe2a5GVne5XbUh5+YA+kcCn67glyHhClUUdFP73LWrgQ==", + "version": "12.20.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.14.tgz", + "integrity": "sha512-iFJOS5Q470FF+r4Ol2pSley7/wCNVqf+jgjhtxLLaJcDs+To2iCxlXIkJXrGLD9w9G/oJ9ibySu7z92DCwr7Pg==", "dev": true } } @@ -19038,9 +19044,9 @@ "integrity": "sha512-8zDbrc7ocusTL1ZGmvgy0cTwdyCaM7sGZoYLRmnWJalLQzmftDtce+uDU91gafOTo9MCtgjSIxyMv/F4+Hcchw==" }, "env-paths": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", - "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true }, "envinfo": { @@ -22266,9 +22272,9 @@ } }, "global-agent": { - "version": "2.1.12", - "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.1.12.tgz", - "integrity": "sha512-caAljRMS/qcDo69X9BfkgrihGUgGx44Fb4QQToNQjsiWh+YlQ66uqYVAdA8Olqit+5Ng0nkz09je3ZzANMZcjg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.2.0.tgz", + "integrity": "sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg==", "dev": true, "optional": true, "requires": { @@ -22282,9 +22288,9 @@ }, "dependencies": { "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.13.1.tgz", + "integrity": "sha512-JqveUc4igkqwStL2RTRn/EPFGBOfEZHxJl/8ej1mXJR75V3go2mFF4bmUYkEIT1rveHKnkUlcJX/c+f1TyIovQ==", "dev": true, "optional": true }, @@ -23118,10 +23124,9 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "inline-style-parser": { "version": "0.1.1", @@ -30482,7 +30487,8 @@ "normalize-url": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==" + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "dev": true }, "npm-conf": { "version": "1.1.3", diff --git a/package.json b/package.json index 40efa81d9eb7..e6ea00966653 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.61-0", + "version": "1.0.63-0", "author": "Expensify, Inc.", "homepage": "https://expensify.cash", "description": "Expensify.cash is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -133,7 +133,7 @@ "css-loader": "^5.2.4", "detox": "^17.8.3", "diff-so-fancy": "^1.3.0", - "electron": "^9.2.0", + "electron": "^11.4.8", "electron-builder": "^22.8.0", "electron-notarize": "^1.0.0", "electron-reloader": "^1.2.0", diff --git a/src/CONST.js b/src/CONST.js index 9b573e97e394..ccdbe11abab4 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -10,6 +10,7 @@ const CONST = { ALL: 'all', CHRONOS_IN_CASH: 'chronosInCash', IOU: 'IOU', + PAY_WITH_EXPENSIFY: 'payWithExpensify', }, BUTTON_STATES: { DEFAULT: 'default', @@ -211,6 +212,7 @@ const CONST = { // not be changed. PAYMENT_TYPE: { ELSEWHERE: 'Elsewhere', + EXPENSIFY: 'Expensify', PAYPAL_ME: 'PayPal.me', VENMO: 'Venmo', }, diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index 84f7dbad828d..d7fcd65074f3 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -81,6 +81,7 @@ export default { REPORT_ACTIONS_DRAFTS: 'reportActionsDrafts_', REPORT_USER_IS_TYPING: 'reportUserIsTyping_', REPORT_IOUS: 'reportIOUs_', + POLICY: 'policy_', }, // Indicates which locale should be used diff --git a/src/components/FAB/FAB.js b/src/components/FAB/FAB.js index e6f675242ab8..635dc6a86d99 100644 --- a/src/components/FAB/FAB.js +++ b/src/components/FAB/FAB.js @@ -59,6 +59,8 @@ class FAB extends PureComponent { return ( ( ( @@ -21,5 +25,6 @@ const GrowlNotificationContainer = ({children, translateY}) => ( ); GrowlNotificationContainer.propTypes = propTypes; +GrowlNotificationContainer.displayName = 'GrowlNotificationContainer'; export default GrowlNotificationContainer; diff --git a/src/components/HeaderWithCloseButton.js b/src/components/HeaderWithCloseButton.js index 45930710da7b..a56dac010ba0 100755 --- a/src/components/HeaderWithCloseButton.js +++ b/src/components/HeaderWithCloseButton.js @@ -7,6 +7,8 @@ import styles from '../styles/styles'; import Header from './Header'; import Icon from './Icon'; import {Close, Download, BackArrow} from './Icon/Expensicons'; +import compose from '../libs/compose'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; const propTypes = { /** Title of the Header */ @@ -29,6 +31,8 @@ const propTypes = { /** Whether we should show a download button */ shouldShowDownloadButton: PropTypes.bool, + + ...withLocalizePropTypes, }; const defaultProps = { @@ -76,6 +80,8 @@ const HeaderWithCloseButton = props => ( @@ -88,4 +94,4 @@ HeaderWithCloseButton.propTypes = propTypes; HeaderWithCloseButton.defaultProps = defaultProps; HeaderWithCloseButton.displayName = 'HeaderWithCloseButton'; -export default HeaderWithCloseButton; +export default compose(withLocalize)(HeaderWithCloseButton); diff --git a/src/components/IOUConfirmationList.js b/src/components/IOUConfirmationList.js index a7ef2f9ac307..0dcecf63a326 100755 --- a/src/components/IOUConfirmationList.js +++ b/src/components/IOUConfirmationList.js @@ -252,6 +252,7 @@ class IOUConfirmationList extends Component { }]} sections={this.getSections()} disableArrowKeysActions + disableRowInteractivity hideAdditionalOptionStates forceTextUnreadStyle canSelectMultipleOptions={this.props.hasMultipleParticipants} diff --git a/src/components/OptionsList.js b/src/components/OptionsList.js index 96c06425fbb9..9f21639d82bd 100644 --- a/src/components/OptionsList.js +++ b/src/components/OptionsList.js @@ -74,6 +74,9 @@ const propTypes = { /** Toggle between compact and default view of the option */ optionMode: PropTypes.oneOf(['compact', 'default']), + + /** Whether to disable the interactivity of the list's option row(s) */ + disableRowInteractivity: PropTypes.bool, }; const defaultProps = { @@ -94,6 +97,7 @@ const defaultProps = { innerRef: null, showTitleTooltip: false, optionMode: undefined, + disableRowInteractivity: false, }; class OptionsList extends Component { @@ -170,6 +174,7 @@ class OptionsList extends Component { showSelectedState={this.props.canSelectMultipleOptions} hideAdditionalOptionStates={this.props.hideAdditionalOptionStates} forceTextUnreadStyle={this.props.forceTextUnreadStyle} + disableRowInteractivity={this.props.disableRowInteractivity} /> ); } diff --git a/src/components/ReportActionItemIOUAction.js b/src/components/ReportActionItemIOUAction.js index d7332d2f0ec1..6921735155af 100644 --- a/src/components/ReportActionItemIOUAction.js +++ b/src/components/ReportActionItemIOUAction.js @@ -1,7 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; -import lodashGet from 'lodash/get'; import ONYXKEYS from '../ONYXKEYS'; import ReportActionItemIOUQuote from './ReportActionItemIOUQuote'; import ReportActionPropTypes from '../pages/home/report/ReportActionPropTypes'; @@ -44,22 +43,22 @@ const defaultProps = { const ReportActionItemIOUAction = ({ action, chatReportID, - chatReport, iouReport, isMostRecentIOUReportAction, }) => { const launchDetailsModal = () => { Navigation.navigate(ROUTES.getIouDetailsRoute(chatReportID, action.originalMessage.IOUReportID)); }; - const hasMultipleParticipants = lodashGet(chatReport, 'participants', []).length >= 2; return ( <> - {isMostRecentIOUReportAction && (iouReport.hasOutstandingIOU) && ( + {isMostRecentIOUReportAction + && iouReport.hasOutstandingIOU + && Boolean(action.originalMessage.IOUReportID) && ( } participantNames + * @param {Set} [participantNames] * @returns {Boolean} */ -function isSearchStringMatch(searchValue, searchText, participantNames) { +function isSearchStringMatch(searchValue, searchText, participantNames = new Set()) { const searchWords = searchValue .replace(/,/g, ' ') .split(' ') diff --git a/src/libs/Permissions.js b/src/libs/Permissions.js index f554fa45bb43..dc9f24f007cc 100644 --- a/src/libs/Permissions.js +++ b/src/libs/Permissions.js @@ -32,7 +32,15 @@ function canUseIOU() { return _.contains(betas, CONST.BETAS.IOU) || canUseAllBetas(); } +/** + * @returns {Boolean} + */ +function canUsePayWithExpensify() { + return _.contains(betas, CONST.BETAS.PAY_WITH_EXPENSIFY) || canUseAllBetas(); +} + export default { canUseChronos, canUseIOU, + canUsePayWithExpensify, }; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 12bda38ca7f0..56a723c5fd56 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -2,9 +2,11 @@ import Onyx from 'react-native-onyx'; import _ from 'underscore'; import CONST from '../../CONST'; import ONYXKEYS from '../../ONYXKEYS'; +import ROUTES from '../../ROUTES'; import * as API from '../API'; import {getSimplifiedIOUReport, fetchChatReportsByIDs, fetchIOUReportByIDAndUpdateChatReport} from './Report'; import openURLInNewTab from '../openURLInNewTab'; +import Navigation from '../Navigation/Navigation'; /** * Retrieve the users preferred currency @@ -73,7 +75,10 @@ function getIOUReportsForNewTransaction(requestParams) { function createIOUTransaction(params) { Onyx.merge(ONYXKEYS.IOU, {loading: true, creatingIOUTransaction: true, error: false}); API.CreateIOUTransaction(params) - .then(data => getIOUReportsForNewTransaction([data])); + .then((data) => { + getIOUReportsForNewTransaction([data]); + Navigation.navigate(ROUTES.getReportRoute(data.chatReportID)); + }); } /** @@ -87,14 +92,18 @@ function createIOUTransaction(params) { function createIOUSplit(params) { Onyx.merge(ONYXKEYS.IOU, {loading: true, creatingIOUTransaction: true, error: false}); + let chatReportID; API.CreateChatReport({ emailList: params.splits.map(participant => participant.email).join(','), }) - .then(data => API.CreateIOUSplit({ - ...params, - splits: JSON.stringify(params.splits), - reportID: data.reportID, - })) + .then((data) => { + chatReportID = data.reportID; + return API.CreateIOUSplit({ + ...params, + splits: JSON.stringify(params.splits), + reportID: data.reportID, + }); + }) .then((data) => { // This data needs to go from this: // {reportIDList: [1, 2], chatReportIDList: [3, 4]} @@ -110,6 +119,7 @@ function createIOUSplit(params) { }); } getIOUReportsForNewTransaction(reportParams); + Navigation.navigate(ROUTES.getReportRoute(chatReportID)); }); } @@ -186,10 +196,10 @@ function payIOUReport({ chatReportID, reportID, paymentMethodType, amount, currency, submitterPhoneNumber, submitterPayPalMeAddress, }) { Onyx.merge(ONYXKEYS.IOU, {loading: true, error: false}); - API.PayIOU({ - reportID, - paymentMethodType, - }) + const payIOUPromise = paymentMethodType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY + ? API.PayWithWallet({reportID}) + : API.PayIOU({reportID, paymentMethodType}); + payIOUPromise .then((response) => { if (response.jsonCode !== 200) { throw new Error(response.message); @@ -204,7 +214,7 @@ function payIOUReport({ fetchIOUReportByIDAndUpdateChatReport(reportID, chatReportID); // Once we have successfully paid the IOU we will transfer the user to their platform of choice if they have - // selected something other than a manual settlement e.g. Venmo or PayPal.me + // selected something other than a manual settlement or Expensify Wallet e.g. Venmo or PayPal.me if (paymentMethodType === CONST.IOU.PAYMENT_TYPE.PAYPAL_ME) { openURLInNewTab(buildPayPalPaymentUrl(amount, submitterPayPalMeAddress, currency)); } else if (paymentMethodType === CONST.IOU.PAYMENT_TYPE.VENMO) { diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js new file mode 100644 index 000000000000..af04011c3c94 --- /dev/null +++ b/src/libs/actions/Policy.js @@ -0,0 +1,36 @@ +import _ from 'underscore'; +import Onyx from 'react-native-onyx'; +import {GetPolicySummaryList} from '../API'; +import ONYXKEYS from '../../ONYXKEYS'; + +/** + * Takes a full policy summary that is returned from the policySummaryList and simplifies it so we are only storing + * the pieces of data that we need to in Onyx + * + * @param {Object} fullPolicy + * @param {String} fullPolicy.name + * @returns {Object} + */ +function getSimplifiedPolicyObject(fullPolicy) { + return { + name: fullPolicy.name, + }; +} + +function getPolicySummaries() { + GetPolicySummaryList() + .then((data) => { + if (data.jsonCode === 200) { + const policyDataToStore = _.reduce(data.policySummaryList, (memo, policy) => ({ + ...memo, + [`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`]: getSimplifiedPolicyObject(policy), + }), {}); + Onyx.mergeCollection(ONYXKEYS.COLLECTION.POLICY, policyDataToStore); + } + }); +} + +export { + // eslint-disable-next-line import/prefer-default-export + getPolicySummaries, +}; diff --git a/src/pages/home/sidebar/OptionRow.js b/src/pages/home/sidebar/OptionRow.js index 5b85d6cce96a..c75b7dd5e2f1 100644 --- a/src/pages/home/sidebar/OptionRow.js +++ b/src/pages/home/sidebar/OptionRow.js @@ -56,9 +56,12 @@ const propTypes = { /** Toggle between compact and default view */ mode: PropTypes.oneOf(['compact', 'default']), - // Whether this option should be disabled + /** Whether this option should be disabled */ isDisabled: PropTypes.bool, + /** Whether to disable the interactivity of this row */ + disableRowInteractivity: PropTypes.bool, + ...withLocalizePropTypes, }; @@ -74,6 +77,7 @@ const defaultProps = { onSelectRow: null, isDisabled: false, optionIsFocused: false, + disableRowInteractivity: false, }; const OptionRow = ({ @@ -89,6 +93,7 @@ const OptionRow = ({ showTitleTooltip, isDisabled, mode, + disableRowInteractivity, toLocalPhone, }) => { const textStyle = optionIsFocused @@ -139,6 +144,7 @@ const OptionRow = ({ {hovered => ( onSelectRow(option)} + disabled={disableRowInteractivity} activeOpacity={0.8} style={[ styles.flexRow, diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 30175cf49d98..f2308ee4702c 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -20,6 +20,8 @@ import KeyboardSpacer from '../../../components/KeyboardSpacer'; import CONST from '../../../CONST'; import {participantPropTypes} from './optionPropTypes'; import themeColors from '../../../styles/themes/default'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; + const propTypes = { /** Toggles the navigation menu open and closed */ @@ -74,6 +76,8 @@ const propTypes = { // Whether we are syncing app data isSyncingData: PropTypes.bool, + + ...withLocalizePropTypes, }; const defaultProps = { @@ -132,16 +136,22 @@ class SidebarLinks extends React.Component { >
diff --git a/src/pages/iou/IOUDetailsModal.js b/src/pages/iou/IOUDetailsModal.js index 9c6c313f07a6..c0e8548fc724 100644 --- a/src/pages/iou/IOUDetailsModal.js +++ b/src/pages/iou/IOUDetailsModal.js @@ -21,6 +21,7 @@ import CONST from '../../CONST'; import CreateMenu from '../../components/CreateMenu'; import isAppInstalled from '../../libs/isAppInstalled'; import Button from '../../components/Button'; +import Permissions from '../../libs/Permissions'; const propTypes = { /** URL Route params */ @@ -102,6 +103,7 @@ class IOUDetailsModal extends Component { this.isComponentMounted = true; fetchIOUReportByID(this.props.route.params.iouReportID, this.props.route.params.chatReportID); this.addVenmoPaymentOptionIfAvailable(); + this.addExpensifyPaymentOptionIfAvailable(); } componentWillUnmount() { @@ -173,10 +175,28 @@ class IOUDetailsModal extends Component { }); } + /** + * Checks to see if we can use Expensify Wallet to pay for this IOU report. + * The IOU report currency must be USD. + */ + addExpensifyPaymentOptionIfAvailable() { + if (lodashGet(this.props, 'iouReport.currency') !== CONST.CURRENCY.USD + || !Permissions.canUsePayWithExpensify()) { + return; + } + + // Make it the first payment option and set it as the default. + this.setState(prevState => ({ + paymentOptions: [CONST.IOU.PAYMENT_TYPE.EXPENSIFY, ...prevState.paymentOptions], + paymentType: CONST.IOU.PAYMENT_TYPE.EXPENSIFY, + })); + } + render() { const sessionEmail = lodashGet(this.props.session, 'email', null); const reportIsLoading = _.isUndefined(this.props.iouReport); const paymentTypeTextOptions = { + [CONST.IOU.PAYMENT_TYPE.EXPENSIFY]: this.props.translate('iou.settleExpensify'), [CONST.IOU.PAYMENT_TYPE.VENMO]: this.props.translate('iou.settleVenmo'), [CONST.IOU.PAYMENT_TYPE.PAYPAL_ME]: this.props.translate('iou.settlePaypalMe'), [CONST.IOU.PAYMENT_TYPE.ELSEWHERE]: this.props.translate('iou.settleElsewhere'), diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js index 0c1e0982f96c..4cf2600c2c77 100755 --- a/src/pages/iou/IOUModal.js +++ b/src/pages/iou/IOUModal.js @@ -294,6 +294,8 @@ class IOUModal extends Component { Navigation.dismissModal()} style={[styles.touchableButtonImage]} + accessibilityRole="button" + accessibilityLabel={this.props.translate('common.close')} >