diff --git a/android/app/build.gradle b/android/app/build.gradle index f367beb199ba..25acb6f86df5 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,8 +98,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001042901 - versionName "1.4.29-1" + versionCode 1001043000 + versionName "1.4.30-0" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index d4078ea25068..124077702be2 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.29 + 1.4.30 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.29.1 + 1.4.30.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 2d52c398bbf5..2554f5dce3f2 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.29 + 1.4.30 CFBundleSignature ???? CFBundleVersion - 1.4.29.1 + 1.4.30.0 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index f1b09af842f4..1069a5847a02 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -3,9 +3,9 @@ CFBundleShortVersionString - 1.4.29 + 1.4.30 CFBundleVersion - 1.4.29.1 + 1.4.30.0 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 9979a58f53fa..3e5ae6e8d58c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.29-1", + "version": "1.4.30-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.29-1", + "version": "1.4.30-0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index bd01bd26a106..f1c077102930 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.29-1", + "version": "1.4.30-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", diff --git a/src/CONST.ts b/src/CONST.ts index accce6e92555..813ef3e6b0cc 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3133,4 +3133,8 @@ const CONST = { MINI_CONTEXT_MENU_MAX_ITEMS: 4, } as const; +type Country = keyof typeof CONST.ALL_COUNTRIES; + +export type {Country}; + export default CONST; diff --git a/src/components/CountrySelector.js b/src/components/CountrySelector.tsx similarity index 62% rename from src/components/CountrySelector.js rename to src/components/CountrySelector.tsx index 68a6486bce48..589530cd7879 100644 --- a/src/components/CountrySelector.js +++ b/src/components/CountrySelector.tsx @@ -1,39 +1,30 @@ -import PropTypes from 'prop-types'; -import React, {useEffect} from 'react'; +import React, {forwardRef, useEffect} from 'react'; +import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; +import type {Country} from '@src/CONST'; import ROUTES from '@src/ROUTES'; import FormHelpMessage from './FormHelpMessage'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; -import refPropTypes from './refPropTypes'; -const propTypes = { +type CountrySelectorProps = { /** Form error text. e.g when no country is selected */ - errorText: PropTypes.string, + errorText?: string; /** Callback called when the country changes. */ - onInputChange: PropTypes.func.isRequired, + onInputChange: (value?: string) => void; /** Current selected country */ - value: PropTypes.string, + value?: Country; /** inputID used by the Form component */ // eslint-disable-next-line react/no-unused-prop-types - inputID: PropTypes.string.isRequired, - - /** React ref being forwarded to the MenuItemWithTopDescription */ - forwardedRef: refPropTypes, -}; - -const defaultProps = { - errorText: '', - value: undefined, - forwardedRef: () => {}, + inputID: string; }; -function CountrySelector({errorText, value: countryCode, onInputChange, forwardedRef}) { +function CountrySelector({errorText = '', value: countryCode, onInputChange}: CountrySelectorProps, ref: ForwardedRef) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -51,12 +42,12 @@ function CountrySelector({errorText, value: countryCode, onInputChange, forwarde { const activeRoute = Navigation.getActiveRouteWithoutParams(); - Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS_COUNTRY.getRoute(countryCode, activeRoute)); + Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS_COUNTRY.getRoute(countryCode ?? '', activeRoute)); }} /> @@ -66,18 +57,6 @@ function CountrySelector({errorText, value: countryCode, onInputChange, forwarde ); } -CountrySelector.propTypes = propTypes; -CountrySelector.defaultProps = defaultProps; CountrySelector.displayName = 'CountrySelector'; -const CountrySelectorWithRef = React.forwardRef((props, ref) => ( - -)); - -CountrySelectorWithRef.displayName = 'CountrySelectorWithRef'; - -export default CountrySelectorWithRef; +export default forwardRef(CountrySelector); diff --git a/src/languages/en.ts b/src/languages/en.ts index b6da38df21a0..0a754a883d07 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1,6 +1,7 @@ import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; import Str from 'expensify-common/lib/str'; import CONST from '@src/CONST'; +import type {Country} from '@src/CONST'; import type { AddressLineParams, AlreadySignedInParams, @@ -106,7 +107,7 @@ type StateValue = { type States = Record; -type AllCountries = Record; +type AllCountries = Record; /* eslint-disable max-len */ export default { diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 2b49fb8b7dce..d258b5419103 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1183,6 +1183,21 @@ function updateMoneyRequestTag(transactionID, transactionThreadReportID, tag) { API.write('UpdateMoneyRequestTag', params, onyxData); } +/** + * Updates the category of a money request + * + * @param {String} transactionID + * @param {Number} transactionThreadReportID + * @param {String} category + */ +function updateMoneyRequestCategory(transactionID, transactionThreadReportID, category) { + const transactionChanges = { + category, + }; + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true); + API.write('UpdateMoneyRequestCategory', params, onyxData); +} + /** * Updates the description of a money request * @@ -1957,6 +1972,32 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co }); }); + _.each(participants, (participant) => { + const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(participant); + if (!isPolicyExpenseChat) { + return; + } + + const optimisticPolicyRecentlyUsedCategories = Policy.buildOptimisticPolicyRecentlyUsedCategories(participant.policyID, category); + const optimisticPolicyRecentlyUsedTags = Policy.buildOptimisticPolicyRecentlyUsedTags(participant.policyID, tag); + + if (!_.isEmpty(optimisticPolicyRecentlyUsedCategories)) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${participant.policyID}`, + value: optimisticPolicyRecentlyUsedCategories, + }); + } + + if (!_.isEmpty(optimisticPolicyRecentlyUsedTags)) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${participant.policyID}`, + value: optimisticPolicyRecentlyUsedTags, + }); + } + }); + // Save the new splits array into the transaction's comment in case the user calls CompleteSplitBill while offline optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -3693,6 +3734,7 @@ export { updateMoneyRequestBillable, updateMoneyRequestMerchant, updateMoneyRequestTag, + updateMoneyRequestCategory, updateMoneyRequestAmountAndCurrency, updateMoneyRequestDescription, replaceReceipt, diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 606d3da1ddb9..3eb9d88f1120 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -110,12 +110,6 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep }); }, [parentReportAction, fieldToEdit]); - // Update the transaction object and close the modal - function editMoneyRequest(transactionChanges) { - IOU.editMoneyRequest(transaction, report.reportID, transactionChanges); - Navigation.dismissModal(report.reportID); - } - const saveAmountAndCurrency = useCallback( ({amount, currency: newCurrency}) => { const newAmount = CurrencyUtils.convertToBackendAmount(Number.parseFloat(amount)); @@ -176,6 +170,16 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep [transactionTag, transaction.transactionID, report.reportID], ); + const saveCategory = useCallback( + ({category: newCategory}) => { + // In case the same category has been selected, reset the category. + const updatedCategory = newCategory === transactionCategory ? '' : newCategory; + IOU.updateMoneyRequestCategory(transaction.transactionID, report.reportID, updatedCategory); + Navigation.dismissModal(); + }, + [transactionCategory, transaction.transactionID, report.reportID], + ); + const saveComment = useCallback( ({comment: newComment}) => { // Only update comment if it has changed @@ -235,14 +239,7 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep { - let updatedCategory = transactionChanges.category; - // In case the same category has been selected, do reset of the category. - if (transactionCategory === updatedCategory) { - updatedCategory = ''; - } - editMoneyRequest({category: updatedCategory}); - }} + onSubmit={saveCategory} /> ); }