diff --git a/.github/ISSUE_TEMPLATE/Accessibility.md b/.github/ISSUE_TEMPLATE/Accessibility.md index 1323e2c17e78..97fc17d28a94 100644 --- a/.github/ISSUE_TEMPLATE/Accessibility.md +++ b/.github/ISSUE_TEMPLATE/Accessibility.md @@ -35,12 +35,12 @@ What can we do to fix the issue? Check off any platforms that are affected by this issue ---> Which of our officially supported platforms is this issue occurring on? Please only tick the box if you have provided a screen-recording in the thread for each platform: -- [ ] Android / native -- [ ] Android / Chrome -- [ ] iOS / native -- [ ] iOS / Safari -- [ ] MacOS / Chrome / Safari -- [ ] MacOS / Desktop +- [ ] Android: Native +- [ ] Android: mWeb Chrome +- [ ] iOS: Native +- [ ] iOS: mWeb Safari +- [ ] MacOS: Chrome / Safari +- [ ] MacOS: Desktop **Version Number:** **Reproducible in staging?:** diff --git a/.github/ISSUE_TEMPLATE/Performance.md b/.github/ISSUE_TEMPLATE/Performance.md index 67b2e6971874..bbb729e8af31 100644 --- a/.github/ISSUE_TEMPLATE/Performance.md +++ b/.github/ISSUE_TEMPLATE/Performance.md @@ -28,12 +28,12 @@ Note: These should be the same as the benchmarks collected before any changes. Check off any platforms that are affected by this issue ---> Which of our officially supported platforms is this issue occurring on? -- [ ] Android / native -- [ ] Android / Chrome -- [ ] iOS / native -- [ ] iOS / Safari -- [ ] MacOS / Chrome / Safari -- [ ] MacOS / Desktop +- [ ] Android: Native +- [ ] Android: mWeb Chrome +- [ ] iOS: Native +- [ ] iOS: mWeb Safari +- [ ] MacOS: Chrome / Safari +- [ ] MacOS: Desktop **Version Number:** **Reproducible in staging?:** diff --git a/.github/ISSUE_TEMPLATE/Standard.md b/.github/ISSUE_TEMPLATE/Standard.md index 39d1c38fa56f..5e0e3633f3bc 100644 --- a/.github/ISSUE_TEMPLATE/Standard.md +++ b/.github/ISSUE_TEMPLATE/Standard.md @@ -7,6 +7,16 @@ labels: Bug, Daily If you haven’t already, check out our [contributing guidelines](https://github.com/Expensify/ReactNativeChat/blob/main/contributingGuides/CONTRIBUTING.md) for onboarding and email contributors@expensify.com to request to join our Slack channel! ___ +**Version Number:** +**Reproducible in staging?:** +**Reproducible in production?:** +**If this was caught during regression testing, add the test name, ID and link from TestRail:** +**Email or phone of affected tester (no customers):** +**Logs:** https://stackoverflow.com/c/expensify/questions/4856 +**Expensify/Expensify Issue URL:** +**Issue reported by:** +**Slack conversation:** + ## Action Performed: Break down in numbered steps @@ -24,22 +34,54 @@ Can the user still use Expensify without this being fixed? Have you informed the Check off any platforms that are affected by this issue ---> Which of our officially supported platforms is this issue occurring on? -- [ ] Android / native -- [ ] Android / Chrome -- [ ] iOS / native -- [ ] iOS / Safari -- [ ] MacOS / Chrome / Safari -- [ ] MacOS / Desktop +- [ ] Android: Native +- [ ] Android: mWeb Chrome +- [ ] iOS: Native +- [ ] iOS: mWeb Safari +- [ ] MacOS: Chrome / Safari +- [ ] MacOS: Desktop -**Version Number:** -**Reproducible in staging?:** -**Reproducible in production?:** -**If this was caught during regression testing, add the test name, ID and link from TestRail:** -**Email or phone of affected tester (no customers):** -**Logs:** https://stackoverflow.com/c/expensify/questions/4856 -**Notes/Photos/Videos:** Any additional supporting documentation -**Expensify/Expensify Issue URL:** -**Issue reported by:** -**Slack conversation:** +## Screenshots/Videos +
+Android: Native + + + +
+ +
+Android: mWeb Chrome + + + +
+ +
+iOS: Native + + + +
+ +
+iOS: mWeb Safari + + + +
+ +
+MacOS: Chrome / Safari + + + +
+ +
+MacOS: Desktop + + + +
[View all open jobs on GitHub](https://github.com/Expensify/App/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 800888580518..0396a7543b50 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -70,12 +70,12 @@ This is a checklist for PR authors. Please make sure to complete all tasks and c - [ ] I tested this PR with a [High Traffic account](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#high-traffic-accounts) against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability). - [ ] I included screenshots or videos for tests on [all platforms](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#make-sure-you-can-test-on-all-platforms) - [ ] I ran the tests on **all platforms** & verified they passed on: - - [ ] Android / native - - [ ] Android / Chrome - - [ ] iOS / native - - [ ] iOS / Safari - - [ ] MacOS / Chrome / Safari - - [ ] MacOS / Desktop + - [ ] Android: Native + - [ ] Android: mWeb Chrome + - [ ] iOS: Native + - [ ] iOS: mWeb Safari + - [ ] MacOS: Chrome / Safari + - [ ] MacOS: Desktop - [ ] I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed) - [ ] I followed proper code patterns (see [Reviewing the code](https://github.com/Expensify/App/blob/main/contributingGuides/PR_REVIEW_GUIDELINES.md#reviewing-the-code)) - [ ] I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. `toggleReport` and not `onIconClick`) @@ -120,42 +120,42 @@ This is a checklist for PR authors. Please make sure to complete all tasks and c ### Screenshots/Videos
-Web +Android: Native
-Mobile Web - Chrome +Android: mWeb Chrome
-Mobile Web - Safari +iOS: Native
-Desktop +iOS: mWeb Safari
-iOS +MacOS: Chrome / Safari
-Android +MacOS: Desktop diff --git a/contributingGuides/REVIEWER_CHECKLIST.md b/contributingGuides/REVIEWER_CHECKLIST.md index 6a5dffc8b073..d52d80a818bb 100644 --- a/contributingGuides/REVIEWER_CHECKLIST.md +++ b/contributingGuides/REVIEWER_CHECKLIST.md @@ -10,12 +10,12 @@ - [ ] I checked that screenshots or videos are included for tests on [all platforms](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#make-sure-you-can-test-on-all-platforms) - [ ] I included screenshots or videos for tests on [all platforms](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#make-sure-you-can-test-on-all-platforms) - [ ] I verified tests pass on **all platforms** & I tested again on: - - [ ] Android / native - - [ ] Android / Chrome - - [ ] iOS / native - - [ ] iOS / Safari - - [ ] MacOS / Chrome / Safari - - [ ] MacOS / Desktop + - [ ] Android: Native + - [ ] Android: mWeb Chrome + - [ ] iOS: Native + - [ ] iOS: mWeb Safari + - [ ] MacOS: Chrome / Safari + - [ ] MacOS: Desktop - [ ] If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack - [ ] I verified proper code patterns were followed (see [Reviewing the code](https://github.com/Expensify/App/blob/main/contributingGuides/PR_REVIEW_GUIDELINES.md#reviewing-the-code)) - [ ] I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. `toggleReport` and not `onIconClick`). diff --git a/src/CONST.ts b/src/CONST.ts index 1ffa64efe28c..652a38200ab1 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -931,6 +931,7 @@ const CONST = { RECEIPTS: 'receipts@expensify.com', STUDENT_AMBASSADOR: 'studentambassadors@expensify.com', SVFG: 'svfg@expensify.com', + EXPENSIFY_EMAIL_DOMAIN: '@expensify.com', }, ACCOUNT_ID: { @@ -2726,6 +2727,8 @@ const CONST = { SPEND: 'spend', WORKSPACES: 'workspaces', }, + + MISSING_TRANSLATION: 'MISSING TRANSLATION', } as const; export default CONST; diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index b71047e3ca36..a427ba0b0718 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -141,6 +141,9 @@ const propTypes = { /** Whether the money request is a distance request */ isDistanceRequest: PropTypes.bool, + /** A flag for verifying that the current report is a sub-report of a workspace chat */ + isPolicyExpenseChat: PropTypes.bool, + /* Onyx Props */ /** Collection of categories attached to a policy */ policyCategories: PropTypes.objectOf(categoryPropTypes), @@ -176,6 +179,7 @@ const defaultProps = { transaction: {}, mileageRate: {unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate: 0, currency: 'USD'}, isDistanceRequest: false, + isPolicyExpenseChat: false, }; function MoneyRequestConfirmationList(props) { @@ -193,11 +197,8 @@ function MoneyRequestConfirmationList(props) { const distance = lodashGet(transaction, 'routes.route0.distance', 0); const shouldCalculateDistanceAmount = props.isDistanceRequest && props.iouAmount === 0; - // A flag for verifying that the current report is a sub-report of a workspace chat - const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(ReportUtils.getReport(props.reportID))), [props.reportID]); - // A flag for showing the categories field - const shouldShowCategories = isPolicyExpenseChat && Permissions.canUseCategories(props.betas) && OptionsListUtils.hasEnabledOptions(_.values(props.policyCategories)); + const shouldShowCategories = props.isPolicyExpenseChat && Permissions.canUseCategories(props.betas) && OptionsListUtils.hasEnabledOptions(_.values(props.policyCategories)); // Fetches the first tag list of the policy const policyTag = PolicyUtils.getTag(props.policyTags); @@ -205,7 +206,7 @@ function MoneyRequestConfirmationList(props) { const policyTagListName = lodashGet(policyTag, 'name', translate('common.tag')); const canUseTags = Permissions.canUseTags(props.betas); // A flag for showing the tags field - const shouldShowTags = isPolicyExpenseChat && canUseTags && OptionsListUtils.hasEnabledOptions(_.values(policyTagList)); + const shouldShowTags = props.isPolicyExpenseChat && canUseTags && OptionsListUtils.hasEnabledOptions(_.values(policyTagList)); // A flag for showing the billable field const shouldShowBillable = canUseTags && !lodashGet(props.policy, 'disabledFields.defaultBillable', true); diff --git a/src/components/OptionRow.js b/src/components/OptionRow.js index 70415ab03a13..47ab4fe45db1 100644 --- a/src/components/OptionRow.js +++ b/src/components/OptionRow.js @@ -282,6 +282,7 @@ class OptionRow extends Component { ) : ( this.props.onSelectedStatePressed(this.props.option)} + disabled={this.state.isDisabled} accessibilityRole={CONST.ACCESSIBILITY_ROLE.CHECKBOX} accessibilityLabel={CONST.ACCESSIBILITY_ROLE.CHECKBOX} > diff --git a/src/libs/Localize/index.js b/src/libs/Localize/index.js index db371301f43f..a26c7d4ebc10 100644 --- a/src/libs/Localize/index.js +++ b/src/libs/Localize/index.js @@ -2,12 +2,27 @@ import _ from 'underscore'; import lodashGet from 'lodash/get'; import Str from 'expensify-common/lib/str'; import * as RNLocalize from 'react-native-localize'; +import Onyx from 'react-native-onyx'; import Log from '../Log'; import Config from '../../CONFIG'; import translations from '../../languages/translations'; import CONST from '../../CONST'; import LocaleListener from './LocaleListener'; import BaseLocaleListener from './LocaleListener/BaseLocaleListener'; +import ONYXKEYS from '../../ONYXKEYS'; + +// Current user mail is needed for handling missing translations +let userEmail = ''; +Onyx.connect({ + key: ONYXKEYS.SESSION, + waitForCollectionCallback: true, + callback: (val) => { + if (!val) { + return; + } + userEmail = val.email; + }, +}); // Listener when an update in Onyx happens so we use the updated locale when translating/localizing items. LocaleListener.connect(); @@ -70,11 +85,14 @@ function translate(desiredLanguage = CONST.LOCALES.DEFAULT, phraseKey, phrasePar return Str.result(translatedPhrase, phraseParameters); } - // Phrase is not found in default language, on production log an alert to server + // Phrase is not found in default language, on production and staging log an alert to server // on development throw an error - if (Config.IS_IN_PRODUCTION) { + if (Config.IS_IN_PRODUCTION || Config.IS_IN_STAGING) { const phraseString = _.isArray(phraseKey) ? phraseKey.join('.') : phraseKey; Log.alert(`${phraseString} was not found in the en locale`); + if (userEmail.includes(CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN)) { + return CONST.MISSING_TRANSLATION; + } return phraseString; } throw new Error(`${phraseKey} was not found in the default language`); diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 44eba0cef5a5..c99c8d17175a 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -22,6 +22,7 @@ import * as Report from './Report'; import * as NumberUtils from '../NumberUtils'; import ReceiptGeneric from '../../../assets/images/receipt-generic.png'; import * as LocalePhoneNumber from '../LocalePhoneNumber'; +import * as Policy from './Policy'; let allReports; Onyx.connect({ @@ -44,13 +45,6 @@ Onyx.connect({ }, }); -let allRecentlyUsedCategories = {}; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES, - waitForCollectionCallback: true, - callback: (val) => (allRecentlyUsedCategories = val), -}); - let allRecentlyUsedTags = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS, @@ -137,7 +131,7 @@ function buildOnyxDataForMoneyRequest( iouAction, optimisticPersonalDetailListAction, reportPreviewAction, - optimisticRecentlyUsedCategories, + optimisticPolicyRecentlyUsedCategories, optimisticPolicyRecentlyUsedTags, isNewChatReport, isNewIOUReport, @@ -189,11 +183,11 @@ function buildOnyxDataForMoneyRequest( }, ]; - if (!_.isEmpty(optimisticRecentlyUsedCategories)) { + if (!_.isEmpty(optimisticPolicyRecentlyUsedCategories)) { optimisticData.push({ onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${iouReport.policyID}`, - value: optimisticRecentlyUsedCategories, + value: optimisticPolicyRecentlyUsedCategories, }); } @@ -478,13 +472,7 @@ function getMoneyRequestInformation( billable, ); - const uniquePolicyRecentlyUsedCategories = allRecentlyUsedCategories - ? _.filter( - allRecentlyUsedCategories[`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${iouReport.policyID}`], - (recentlyUsedPolicyCategory) => recentlyUsedPolicyCategory !== category, - ) - : []; - const optimisticPolicyRecentlyUsedCategories = [category, ...uniquePolicyRecentlyUsedCategories]; + const optimisticPolicyRecentlyUsedCategories = Policy.buildOptimisticPolicyRecentlyUsedCategories(iouReport.policyID, category); const optimisticPolicyRecentlyUsedTags = {}; const policyTags = allPolicyTags[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${iouReport.policyID}`]; @@ -882,11 +870,12 @@ function requestMoney( * @param {Number} amount - always in the smallest unit of the currency * @param {String} comment * @param {String} currency + * @param {String} category * @param {String} existingSplitChatReportID - the report ID where the split bill happens, could be a group chat or a workspace chat * * @return {Object} */ -function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, existingSplitChatReportID = '') { +function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, category, existingSplitChatReportID = '') { const currentUserEmailForIOUSplit = OptionsListUtils.addSMSDomainIfPhoneNumber(currentUserLogin); const participantAccountIDs = _.map(participants, (participant) => Number(participant.accountID)); const existingSplitChatReport = @@ -910,6 +899,10 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco '', '', `${Localize.translateLocal('iou.splitBill')} ${Localize.translateLocal('common.with')} ${formattedParticipants} [${DateUtils.getDBTime().slice(0, 10)}]`, + undefined, + undefined, + undefined, + category, ); // Note: The created action must be optimistically generated before the IOU action so there's no chance that the created action appears after the IOU action in the chat @@ -1036,9 +1029,12 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco const hasMultipleParticipants = participants.length > 1; _.each(participants, (participant) => { - // In case the participant is a worskapce, email & accountID should remain undefined and won't be used in the rest of this code - const email = isOwnPolicyExpenseChat ? '' : OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login).toLowerCase(); - const accountID = isOwnPolicyExpenseChat ? 0 : Number(participant.accountID); + // In a case when a participant is a workspace, even when a current user is not an owner of the workspace + const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(participant); + + // In case the participant is a workspace, email & accountID should remain undefined and won't be used in the rest of this code + const email = isOwnPolicyExpenseChat || isPolicyExpenseChat ? '' : OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login).toLowerCase(); + const accountID = isOwnPolicyExpenseChat || isPolicyExpenseChat ? 0 : Number(participant.accountID); if (email === currentUserEmailForIOUSplit) { return; } @@ -1090,6 +1086,11 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco '', CONST.IOU.MONEY_REQUEST_TYPE.SPLIT, splitTransaction.transactionID, + undefined, + undefined, + undefined, + undefined, + category, ); // STEP 4: Build optimistic reportActions. We need: @@ -1131,6 +1132,12 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco oneOnOneReportPreviewAction = ReportUtils.buildOptimisticReportPreview(oneOnOneChatReport, oneOnOneIOUReport); } + // Add category to optimistic policy recently used categories when a participant is a workspace + let optimisticPolicyRecentlyUsedCategories = []; + if (isPolicyExpenseChat) { + optimisticPolicyRecentlyUsedCategories = Policy.buildOptimisticPolicyRecentlyUsedCategories(participant.policyID, category); + } + // STEP 5: Build Onyx Data const [oneOnOneOptimisticData, oneOnOneSuccessData, oneOnOneFailureData] = buildOnyxDataForMoneyRequest( oneOnOneChatReport, @@ -1141,7 +1148,7 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco oneOnOneIOUAction, oneOnOnePersonalDetailListAction, oneOnOneReportPreviewAction, - [], + optimisticPolicyRecentlyUsedCategories, {}, isNewOneOnOneChatReport, shouldCreateNewOneOnOneIOUReport, @@ -1191,11 +1198,11 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco * @param {Number} amount - always in smallest currency unit * @param {String} comment * @param {String} currency + * @param {String} category * @param {String} existingSplitChatReportID - Either a group DM or a workspace chat */ -function splitBill(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, existingSplitChatReportID = '') { - const {splitData, splits, onyxData} = createSplitsAndOnyxData(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, existingSplitChatReportID); - +function splitBill(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, category, existingSplitChatReportID = '') { + const {splitData, splits, onyxData} = createSplitsAndOnyxData(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, category, existingSplitChatReportID); API.write( 'SplitBill', { @@ -1204,6 +1211,7 @@ function splitBill(participants, currentUserLogin, currentUserAccountID, amount, splits: JSON.stringify(splits), currency, comment, + category, transactionID: splitData.transactionID, reportActionID: splitData.reportActionID, createdReportActionID: splitData.createdReportActionID, @@ -1224,9 +1232,10 @@ function splitBill(participants, currentUserLogin, currentUserAccountID, amount, * @param {Number} amount - always in smallest currency unit * @param {String} comment * @param {String} currency + * @param {String} category */ -function splitBillAndOpenReport(participants, currentUserLogin, currentUserAccountID, amount, comment, currency) { - const {splitData, splits, onyxData} = createSplitsAndOnyxData(participants, currentUserLogin, currentUserAccountID, amount, comment, currency); +function splitBillAndOpenReport(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, category) { + const {splitData, splits, onyxData} = createSplitsAndOnyxData(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, category); API.write( 'SplitBillAndOpenReport', @@ -1236,6 +1245,7 @@ function splitBillAndOpenReport(participants, currentUserLogin, currentUserAccou splits: JSON.stringify(splits), currency, comment, + category, transactionID: splitData.transactionID, reportActionID: splitData.reportActionID, createdReportActionID: splitData.createdReportActionID, @@ -2376,6 +2386,21 @@ function navigateToNextPage(iou, iouType, report, path = '') { Navigation.navigate(ROUTES.MONEY_REQUEST_PARTICIPANTS.getRoute(iouType)); } +/** + * When the money request or split bill creation flow is initialized via FAB, the reportID is not passed as a navigation + * parameter. + * Gets a report id from the first participant of the IOU object stored in Onyx. + * @param {Object} iou + * @param {Array} iou.participants + * @param {Object} route + * @param {Object} route.params + * @param {String} [route.params.reportID] + * @returns {String} + */ +function getIOUReportID(iou, route) { + return lodashGet(route, 'params.reportID') || lodashGet(iou, 'participants.0.reportID', ''); +} + export { createDistanceRequest, editMoneyRequest, @@ -2407,4 +2432,5 @@ export { navigateToNextPage, updateDistanceRequest, replaceReceipt, + getIOUReportID, }; diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index fcce909c5582..3e5e91a74488 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -1,6 +1,7 @@ import _ from 'underscore'; import Onyx from 'react-native-onyx'; import lodashGet from 'lodash/get'; +import lodashUnion from 'lodash/union'; import {PUBLIC_DOMAINS} from 'expensify-common/lib/CONST'; import Str from 'expensify-common/lib/str'; import {escapeRegExp} from 'lodash'; @@ -65,6 +66,13 @@ Onyx.connect({ callback: (val) => (allPersonalDetails = val), }); +let allRecentlyUsedCategories = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES, + waitForCollectionCallback: true, + callback: (val) => (allRecentlyUsedCategories = val), +}); + /** * Stores in Onyx the policy ID of the last workspace that was accessed by the user * @param {String|null} policyID @@ -1168,6 +1176,21 @@ function clearErrors(policyID) { hideWorkspaceAlertMessage(policyID); } +/** + * @param {String} policyID + * @param {String} category + * @returns {Object} + */ +function buildOptimisticPolicyRecentlyUsedCategories(policyID, category) { + if (!policyID || !category) { + return []; + } + + const policyRecentlyUsedCategories = lodashGet(allRecentlyUsedCategories, `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${policyID}`, []); + + return lodashUnion([category], policyRecentlyUsedCategories); +} + export { removeMembers, addMembersToWorkspace, @@ -1197,4 +1220,5 @@ export { setWorkspaceInviteMembersDraft, clearErrors, openDraftWorkspaceRequest, + buildOptimisticPolicyRecentlyUsedCategories, }; diff --git a/src/pages/iou/MoneyRequestCategoryPage.js b/src/pages/iou/MoneyRequestCategoryPage.js index 5284b785304f..5055c9f90f8a 100644 --- a/src/pages/iou/MoneyRequestCategoryPage.js +++ b/src/pages/iou/MoneyRequestCategoryPage.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; +import compose from '../../libs/compose'; import ROUTES from '../../ROUTES'; import Navigation from '../../libs/Navigation/Navigation'; import useLocalize from '../../hooks/useLocalize'; @@ -83,11 +84,20 @@ MoneyRequestCategoryPage.displayName = 'MoneyRequestCategoryPage'; MoneyRequestCategoryPage.propTypes = propTypes; MoneyRequestCategoryPage.defaultProps = defaultProps; -export default withOnyx({ - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID', '')}`, - }, - iou: { - key: ONYXKEYS.IOU, - }, -})(MoneyRequestCategoryPage); +export default compose( + withOnyx({ + iou: { + key: ONYXKEYS.IOU, + }, + }), + // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file + withOnyx({ + report: { + key: ({route, iou}) => { + const reportID = IOU.getIOUReportID(iou, route); + + return `${ONYXKEYS.COLLECTION.REPORT}${reportID}`; + }, + }, + }), +)(MoneyRequestCategoryPage); diff --git a/src/pages/iou/MoneyRequestTagPage.js b/src/pages/iou/MoneyRequestTagPage.js index 43c0cd9d1480..2fc37d2c4271 100644 --- a/src/pages/iou/MoneyRequestTagPage.js +++ b/src/pages/iou/MoneyRequestTagPage.js @@ -98,14 +98,21 @@ MoneyRequestTagPage.defaultProps = defaultProps; export default compose( withOnyx({ - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID')}`, - }, iou: { key: ONYXKEYS.IOU, }, }), // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file + withOnyx({ + report: { + key: ({route, iou}) => { + const reportID = IOU.getIOUReportID(iou, route); + + return `${ONYXKEYS.COLLECTION.REPORT}${reportID}`; + }, + }, + }), + // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ policyTags: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 3881221d5c52..907869c0e3a4 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -75,8 +75,14 @@ function MoneyRequestConfirmPage(props) { }), [props.iou.participants, props.personalDetails], ); + const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(props.report)), [props.report]); const isManualRequestDM = props.selectedTab === CONST.TAB.MANUAL && iouType.current === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST; + useEffect(() => { + IOU.resetMoneyRequestCategory(); + IOU.resetMoneyRequestTag(); + }, []); + useEffect(() => { const policyExpenseChat = _.find(participants, (participant) => participant.isPolicyExpenseChat); if (policyExpenseChat) { @@ -198,6 +204,7 @@ function MoneyRequestConfirmPage(props) { props.iou.amount, trimmedComment, props.iou.currency, + props.iou.category, reportID.current, ); return; @@ -212,6 +219,7 @@ function MoneyRequestConfirmPage(props) { props.iou.amount, trimmedComment, props.iou.currency, + props.iou.category, ); return; } @@ -238,6 +246,7 @@ function MoneyRequestConfirmPage(props) { props.currentUserPersonalDetails.login, props.currentUserPersonalDetails.accountID, props.iou.currency, + props.iou.category, props.iou.receiptPath, props.iou.receiptSource, isDistanceRequest, @@ -338,6 +347,7 @@ function MoneyRequestConfirmPage(props) { receiptSource={props.iou.receiptSource} iouType={iouType.current} reportID={reportID.current} + isPolicyExpenseChat={isPolicyExpenseChat} // The participants can only be modified when the action is initiated from directly within a group chat and not the floating-action-button. // This is because when there is a group of people, say they are on a trip, and you have some shared expenses with some of the people, // but not all of them (maybe someone skipped out on dinner). Then it's nice to be able to select/deselect people from the group chat bill @@ -375,12 +385,8 @@ export default compose( withOnyx({ report: { key: ({route, iou}) => { - let reportID = lodashGet(route, 'params.reportID', ''); - if (!reportID) { - // When the money request creation flow is initialized on Global Create, the reportID is not passed as a navigation parameter. - // Get the report id from the participants list on the IOU object stored in Onyx. - reportID = lodashGet(iou, 'participants.0.reportID', ''); - } + const reportID = IOU.getIOUReportID(iou, route); + return `${ONYXKEYS.COLLECTION.REPORT}${reportID}`; }, }, diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js index 8d745903eb40..89c18efc4e76 100644 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js @@ -45,7 +45,6 @@ const defaultProps = { function MoneyRequestParticipantsPage({iou, selectedTab, route}) { const {translate} = useLocalize(); const prevMoneyRequestId = useRef(iou.id); - const isNewReportIDSelectedLocally = useRef(false); const optionsSelectorRef = useRef(); const iouType = useRef(lodashGet(route, 'params.iouType', '')); const reportID = useRef(lodashGet(route, 'params.reportID', '')); @@ -63,19 +62,7 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route}) { setHeaderTitle(_.isEmpty(iou.participants) ? translate('tabSelector.manual') : translate('iou.split')); }, [iou.participants, isDistanceRequest, translate]); - const navigateToRequestStep = (moneyRequestType, option) => { - if (option.reportID) { - isNewReportIDSelectedLocally.current = true; - IOU.setMoneyRequestId(`${moneyRequestType}${option.reportID}`); - Navigation.navigate(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(moneyRequestType, option.reportID)); - return; - } - - IOU.setMoneyRequestId(moneyRequestType); - Navigation.navigate(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(moneyRequestType, reportID.current)); - }; - - const navigateToSplitStep = (moneyRequestType) => { + const navigateToConfirmationStep = (moneyRequestType) => { IOU.setMoneyRequestId(moneyRequestType); Navigation.navigate(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(moneyRequestType, reportID.current)); }; @@ -88,7 +75,7 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route}) { // ID in Onyx could change by initiating a new request in a separate browser tab or completing a request if (prevMoneyRequestId.current !== iou.id) { // The ID is cleared on completing a request. In that case, we will do nothing - if (iou.id && !isDistanceRequest && !isSplitRequest && !isNewReportIDSelectedLocally.current) { + if (iou.id && !isDistanceRequest && !isSplitRequest) { navigateBack(true); } return; @@ -96,7 +83,7 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route}) { // Reset the money request Onyx if the ID in Onyx does not match the ID from params const moneyRequestId = `${iouType.current}${reportID.current}`; - const shouldReset = iou.id !== moneyRequestId && !isNewReportIDSelectedLocally.current; + const shouldReset = iou.id !== moneyRequestId; if (shouldReset) { IOU.resetMoneyRequestInfo(moneyRequestId); } @@ -126,8 +113,8 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route}) { ref={(el) => (optionsSelectorRef.current = el)} participants={iou.participants} onAddParticipants={IOU.setMoneyRequestParticipants} - navigateToRequest={(option) => navigateToRequestStep(CONST.IOU.MONEY_REQUEST_TYPE.REQUEST, option)} - navigateToSplit={() => navigateToSplitStep(CONST.IOU.MONEY_REQUEST_TYPE.SPLIT)} + navigateToRequest={() => navigateToConfirmationStep(CONST.IOU.MONEY_REQUEST_TYPE.REQUEST)} + navigateToSplit={() => navigateToConfirmationStep(CONST.IOU.MONEY_REQUEST_TYPE.SPLIT)} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} iouType={iouType.current} isDistanceRequest={isDistanceRequest} diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 05b206ce4147..ac30bcf55787 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -164,7 +164,7 @@ function MoneyRequestParticipantsSelector({ onAddParticipants([ {accountID: option.accountID, login: option.login, isPolicyExpenseChat: option.isPolicyExpenseChat, reportID: option.reportID, selected: true, searchText: option.searchText}, ]); - navigateToRequest(option); + navigateToRequest(); }; /**