diff --git a/android/app/build.gradle b/android/app/build.gradle
index c9d156afc122..d2fdd9f36df3 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -155,8 +155,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001019704
- versionName "1.1.97-4"
+ versionCode 1001019703
+ versionName "1.1.97-3"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
if (isNewArchitectureEnabled()) {
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index fcc229635f36..2e639bb7da1d 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -30,7 +30,7 @@
CFBundleVersion
- 1.1.97.4
+ 1.1.97.3
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index cfc97f8196dc..7dc667bb5cdc 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 1.1.97.4
+ 1.1.97.3
diff --git a/package-lock.json b/package-lock.json
index 0d250578dd52..82c66db4d582 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.1.97-4",
+ "version": "1.1.97-3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.1.97-4",
+ "version": "1.1.97-3",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 3a17c64c5c97..b6747f2b9b6b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.1.97-4",
+ "version": "1.1.97-3",
"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.js b/src/CONST.js
index 32847df3ab37..1c2770a69a81 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -667,6 +667,7 @@ const CONST = {
ADMIN: 'admin',
},
ROOM_PREFIX: '#',
+ CUSTOM_UNIT_RATE_BASE_OFFSET: 100,
},
CUSTOM_UNITS: {
diff --git a/src/components/DotIndicatorMessage.js b/src/components/DotIndicatorMessage.js
index 2b073627ef51..53a2ec337809 100644
--- a/src/components/DotIndicatorMessage.js
+++ b/src/components/DotIndicatorMessage.js
@@ -45,6 +45,10 @@ const DotIndicatorMessage = (props) => {
.keys()
.sortBy()
.map(key => props.messages[key])
+
+ // Using uniq here since some fields are wrapped by the same OfflineWithFeedback component (e.g. WorkspaceReimburseView)
+ // and can potentially pass the same error.
+ .uniq()
.value();
return (
diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index d38846c9aa8a..d921fd41fae3 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -580,14 +580,21 @@ function setWorkspaceErrors(policyID, errors) {
/**
* @param {String} policyID
- * @param {Number} customUnitID
+ * @param {String} customUnitID
+ * @param {String} customUnitRateID
*/
-function removeUnitError(policyID, customUnitID) {
+function clearCustomUnitErrors(policyID, customUnitID, customUnitRateID) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {
customUnits: {
[customUnitID]: {
errors: null,
pendingAction: null,
+ onyxRates: {
+ [customUnitRateID]: {
+ errors: null,
+ pendingAction: null,
+ },
+ },
},
},
});
@@ -603,17 +610,17 @@ function hideWorkspaceAlertMessage(policyID) {
/**
* @param {String} policyID
* @param {Object} currentCustomUnit
- * @param {Object} values The new custom unit values
+ * @param {Object} newCustomUnit
*/
-function updateWorkspaceCustomUnit(policyID, currentCustomUnit, values) {
+function updateWorkspaceCustomUnit(policyID, currentCustomUnit, newCustomUnit) {
const optimisticData = [
{
onyxMethod: 'merge',
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
customUnits: {
- [values.customUnitID]: {
- ...values,
+ [newCustomUnit.customUnitID]: {
+ ...newCustomUnit,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
},
@@ -627,7 +634,7 @@ function updateWorkspaceCustomUnit(policyID, currentCustomUnit, values) {
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
customUnits: {
- [values.customUnitID]: {
+ [newCustomUnit.customUnitID]: {
pendingAction: null,
errors: null,
},
@@ -657,40 +664,81 @@ function updateWorkspaceCustomUnit(policyID, currentCustomUnit, values) {
API.write('UpdateWorkspaceCustomUnit', {
policyID,
- customUnit: JSON.stringify(values),
+ customUnit: JSON.stringify(newCustomUnit),
}, {optimisticData, successData, failureData});
}
/**
* @param {String} policyID
+ * @param {Object} currentCustomUnitRate
* @param {String} customUnitID
- * @param {Object} values
+ * @param {Object} newCustomUnitRate
*/
-function setCustomUnitRate(policyID, customUnitID, values) {
- DeprecatedAPI.Policy_CustomUnitRate_Update({
- policyID: policyID.toString(),
- customUnitID: customUnitID.toString(),
- customUnitRate: JSON.stringify(values),
- lastModified: null,
- })
- .then((response) => {
- if (response.jsonCode !== 200) {
- throw new Error();
- }
+function updateCustomUnitRate(policyID, currentCustomUnitRate, customUnitID, newCustomUnitRate) {
+ const optimisticData = [
+ {
+ onyxMethod: 'merge',
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ customUnits: {
+ [customUnitID]: {
+ onyxRates: {
+ [newCustomUnitRate.customUnitRateID]: {
+ ...newCustomUnitRate,
+ errors: null,
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ },
+ },
+ },
+ ];
+
+ const successData = [
+ {
+ onyxMethod: 'merge',
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ customUnits: {
+ [customUnitID]: {
+ onyxRates: {
+ [newCustomUnitRate.customUnitRateID]: {
+ pendingAction: null,
+ },
+ },
+ },
+ },
+ },
+ },
+ ];
- updateLocalPolicyValues(policyID, {
- customUnit: {
- rate: {
- id: values.customUnitRateID,
- name: values.name,
- value: Number(values.rate),
+ const failureData = [
+ {
+ onyxMethod: 'merge',
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ customUnits: {
+ [customUnitID]: {
+ onyxRates: {
+ [currentCustomUnitRate.customUnitRateID]: {
+ ...currentCustomUnitRate,
+ errors: {
+ [DateUtils.getMicroseconds()]: Localize.translateLocal('workspace.reimburse.updateCustomUnitError'),
+ },
+ },
+ },
},
},
- });
- }).catch(() => {
- // Show the user feedback
- Growl.error(Localize.translateLocal('workspace.editor.genericFailureMessage'), 5000);
- });
+ },
+ },
+ ];
+
+ API.write('UpdateWorkspaceCustomUnitRate', {
+ policyID,
+ customUnitID,
+ customUnitRate: JSON.stringify(newCustomUnitRate),
+ }, {optimisticData, successData, failureData});
}
/**
@@ -815,13 +863,13 @@ export {
create,
update,
setWorkspaceErrors,
- removeUnitError,
+ clearCustomUnitErrors,
hideWorkspaceAlertMessage,
deletePolicy,
createAndNavigate,
createAndGetPolicyList,
updateWorkspaceCustomUnit,
- setCustomUnitRate,
+ updateCustomUnitRate,
updateLastAccessedWorkspace,
subscribeToPolicyEvents,
clearDeleteMemberError,
diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.js b/src/pages/workspace/reimburse/WorkspaceReimburseView.js
index e4899ce9426b..29c04b7b8921 100644
--- a/src/pages/workspace/reimburse/WorkspaceReimburseView.js
+++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.js
@@ -39,7 +39,7 @@ const propTypes = {
attributes: PropTypes.shape({
unit: PropTypes.string,
}),
- rates: PropTypes.arrayOf(
+ onyxRates: PropTypes.objectOf(
PropTypes.shape({
customUnitRateID: PropTypes.string,
name: PropTypes.string,
@@ -62,14 +62,14 @@ class WorkspaceReimburseView extends React.Component {
constructor(props) {
super(props);
const distanceCustomUnit = _.find(lodashGet(props, 'policy.customUnits', {}), unit => unit.name === 'Distance');
+ const customUnitRate = _.find(lodashGet(distanceCustomUnit, 'onyxRates', {}), rate => rate.name === 'Default Rate');
this.state = {
unitID: lodashGet(distanceCustomUnit, 'customUnitID', ''),
unitName: lodashGet(distanceCustomUnit, 'name', ''),
unitValue: lodashGet(distanceCustomUnit, 'attributes.unit', 'mi'),
- rateID: lodashGet(distanceCustomUnit, 'rates[0].customUnitRateID', ''),
- rateName: lodashGet(distanceCustomUnit, 'rates[0].name', ''),
- rateValue: this.getRateDisplayValue(lodashGet(distanceCustomUnit, 'rates[0].rate', 0) / 100),
+ unitRateID: lodashGet(customUnitRate, 'customUnitRateID', ''),
+ unitRateValue: this.getRateDisplayValue(lodashGet(customUnitRate, 'rate', 0) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET),
outputCurrency: lodashGet(props, 'policy.outputCurrency', ''),
};
@@ -98,14 +98,13 @@ class WorkspaceReimburseView extends React.Component {
.values()
.findWhere({name: CONST.CUSTOM_UNITS.NAME_DISTANCE})
.value();
-
+ const customUnitRate = _.find(lodashGet(distanceCustomUnit, 'onyxRates', {}), rate => rate.name === 'Default Rate');
this.setState({
unitID: lodashGet(distanceCustomUnit, 'customUnitID', ''),
unitName: lodashGet(distanceCustomUnit, 'name', ''),
unitValue: lodashGet(distanceCustomUnit, 'attributes.unit', 'mi'),
- rateID: lodashGet(distanceCustomUnit, 'rates[0].customUnitRateID', ''),
- rateName: lodashGet(distanceCustomUnit, 'rates[0].name', ''),
- rateValue: this.getRateDisplayValue(lodashGet(distanceCustomUnit, 'rates[0].rate', 0) / 100),
+ unitRateID: lodashGet(customUnitRate, 'customUnitRateID'),
+ unitRateValue: this.getRateDisplayValue(lodashGet(customUnitRate, 'rate', 0) / 100),
});
}
@@ -130,10 +129,10 @@ class WorkspaceReimburseView extends React.Component {
const isInvalidRateValue = value !== '' && !CONST.REGEX.RATE_VALUE.test(value);
this.setState(prevState => ({
- rateValue: !isInvalidRateValue ? value : prevState.rateValue,
+ unitRateValue: !isInvalidRateValue ? value : prevState.unitRateValue,
}), () => {
// Set the corrected value with a delay and sync to the server
- this.updateRateValueDebounced(this.state.rateValue);
+ this.updateRateValueDebounced(this.state.unitRateValue);
});
}
@@ -156,7 +155,7 @@ class WorkspaceReimburseView extends React.Component {
return;
}
- this.updateRateValueDebounced(this.state.rateValue);
+ this.updateRateValueDebounced(this.state.unitRateValue);
}
updateRateValue(value) {
@@ -167,14 +166,15 @@ class WorkspaceReimburseView extends React.Component {
}
this.setState({
- rateValue: numValue.toFixed(3),
+ unitRateValue: numValue.toFixed(3),
});
- Policy.setCustomUnitRate(this.props.policyID, this.state.unitID, {
- customUnitRateID: this.state.rateID,
- name: this.state.rateName,
- rate: numValue.toFixed(3) * 100,
- }, null);
+ const distanceCustomUnit = _.find(lodashGet(this.props, 'policy.customUnits', {}), unit => unit.name === 'Distance');
+ const currentCustomUnitRate = lodashGet(distanceCustomUnit, ['onyxRates', this.state.unitRateID], {});
+ Policy.updateCustomUnitRate(this.props.policyID, currentCustomUnitRate, this.state.unitID, {
+ ...currentCustomUnitRate,
+ rate: numValue.toFixed(3) * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET,
+ });
}
render() {
@@ -214,9 +214,13 @@ class WorkspaceReimburseView extends React.Component {
{this.props.translate('workspace.reimburse.trackDistanceCopy')}
Policy.removeUnitError(this.props.policyID, this.state.unitID)}
+ errors={{
+ ...lodashGet(this.props, ['policy', 'customUnits', this.state.unitID, 'errors'], {}),
+ ...lodashGet(this.props, ['policy', 'customUnits', this.state.unitID, 'onyxRates', this.state.unitRateID, 'errors'], {}),
+ }}
+ pendingAction={lodashGet(this.props, ['policy', 'customUnits', this.state.unitID, 'pendingAction'])
+ || lodashGet(this.props, ['policy', 'customUnits', this.state.unitID, 'onyxRates', this.state.unitRateID, 'pendingAction'])}
+ onClose={() => Policy.clearCustomUnitErrors(this.props.policyID, this.state.unitID, this.state.unitRateID)}
>
@@ -224,7 +228,7 @@ class WorkspaceReimburseView extends React.Component {
label={this.props.translate('workspace.reimburse.trackDistanceRate')}
placeholder={this.state.outputCurrency}
onChangeText={value => this.setRate(value)}
- value={this.state.rateValue}
+ value={this.state.unitRateValue}
autoCompleteType="off"
autoCorrect={false}
keyboardType={CONST.KEYBOARD_TYPE.DECIMAL_PAD}