diff --git a/android/app/build.gradle b/android/app/build.gradle
index 7ca8652a115e..11b699aae021 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 1001019504
- versionName "1.1.95-4"
+ versionCode 1001019600
+ versionName "1.1.96-0"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
if (isNewArchitectureEnabled()) {
diff --git a/contributingGuides/OFFLINE_UX.md b/contributingGuides/OFFLINE_UX.md
index 90d4b45d0204..ac40a4346350 100644
--- a/contributingGuides/OFFLINE_UX.md
+++ b/contributingGuides/OFFLINE_UX.md
@@ -60,7 +60,7 @@ This is the pattern where we queue the request to be sent when the user is onlin
- the user should be given instant feedback and
- the user does not need to know when the change is done on the server in the background
-**How to implement:** Use [`API.write()`](https://github.com/Expensify/App/blob/3493f3ca3a1dc6cdbf9cb8bd342866fcaf45cf1d/src/libs/API.js#L7-L28) to implement this pattern. For this pattern we should only put `optimisticData` in the options. We don't need successData or failureData as we don't care what response comes back at all.
+**How to implement:** Use [`API.write()`](https://github.com/Expensify/App/blob/3493f3ca3a1dc6cdbf9cb8bd342866fcaf45cf1d/src/libs/API.js#L7-L28) to implement this pattern. For this pattern we should only put `optimisticData` in the options. We don't need `successData` or `failureData` as we don't care what response comes back at all.
**Example:** Pinning a chat.
@@ -78,6 +78,10 @@ When the user is offline:
- Use API.write() to implement this pattern
- Optimistic data should include `pendingAction` ([with these possible values](https://github.com/Expensify/App/blob/15f7fa622805ee2971808d6bc67181c4715f0c62/src/CONST.js#L775-L779))
- To ensure the UI is shown as described above, you should enclose the components that contain the data that was added/updated/deleted with the OfflineWithFeedback component
+- Include this data in the action call:
+ - `optimisticData` - always include this object when using the Pattern B
+ - `successData` - include this if the action is `update` or `delete`. You do not have to include this if the action is `add` (same data was already passed using the `optimisticData` object)
+ - `failureData` - always include this object. In case of `add` action, you will want to add some generic error which covers some unexpected failures which were not handled in the backend
**Handling errors:**
- The [OfflineWithFeedback component](https://github.com/Expensify/App/blob/main/src/components/OfflineWithFeedback.js) already handles showing errors too, as long as you pass the error field in the [errors prop](https://github.com/Expensify/App/blob/128ea378f2e1418140325c02f0b894ee60a8e53f/src/components/OfflineWithFeedback.js#L29-L31)
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 28154cd4944d..c7ec264285fb 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.1.95
+ 1.1.96
CFBundleSignature
????
CFBundleURLTypes
@@ -30,7 +30,7 @@
CFBundleVersion
- 1.1.95.4
+ 1.1.96.0
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index f73aed273080..c3afd6409c08 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 1.1.95
+ 1.1.96
CFBundleSignature
????
CFBundleVersion
- 1.1.95.4
+ 1.1.96.0
diff --git a/package-lock.json b/package-lock.json
index edd7f7b52de6..dd20c9c7d657 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.1.95-4",
+ "version": "1.1.96-0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.1.95-4",
+ "version": "1.1.96-0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -60,7 +60,7 @@
"react-native-collapsible": "^1.6.0",
"react-native-config": "^1.4.5",
"react-native-document-picker": "^8.0.0",
- "react-native-gesture-handler": "2.5.0",
+ "react-native-gesture-handler": "2.6.0",
"react-native-google-places-autocomplete": "git+https://github.com/Expensify/react-native-google-places-autocomplete.git#3bbd17d63e6c38d38d857b50f6037c1c0376ff06",
"react-native-haptic-feedback": "^1.13.0",
"react-native-image-pan-zoom": "^2.1.12",
@@ -77,7 +77,7 @@
"react-native-render-html": "6.3.1",
"react-native-safe-area-context": "^3.1.4",
"react-native-screens": "^3.10.1",
- "react-native-svg": "^12.1.0",
+ "react-native-svg": "^13.1.0",
"react-native-webview": "^11.17.2",
"react-pdf": "5.7.2",
"react-plaid-link": "3.3.2",
@@ -31541,9 +31541,9 @@
}
},
"node_modules/react-native-gesture-handler": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.5.0.tgz",
- "integrity": "sha512-djZdcprFf08PZC332D+AeG5wcGeAPhzfCJtB3otUgOgTlvjVXmg/SLFdPJSpzLBqkRAmrC77tM79QgKbuLxkfw==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.6.0.tgz",
+ "integrity": "sha512-IwdYdt5FKjjbRSrSqh8hoNctlYZl5DFnqSJ6buKtrl4A4gyzkrtW6WcmOFl5LnCa6Bcw+znSD77O6UiZ8qda7g==",
"dependencies": {
"@egjs/hammerjs": "^2.0.17",
"hoist-non-react-statics": "^3.3.0",
@@ -31790,16 +31790,16 @@
}
},
"node_modules/react-native-svg": {
- "version": "12.4.4",
- "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-12.4.4.tgz",
- "integrity": "sha512-LpcNlEVCURexqPAvQ9ne8KrPVfYz0wIDygwud8VMRmXLezysXzyQN/DTsjm1BO9lIfYp55WQsr3u3yW/vk6iiA==",
+ "version": "13.1.0",
+ "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.1.0.tgz",
+ "integrity": "sha512-drYa+0piaQ27xFEp1MxRBSu6eHbR37qQITKTHNOmPv1NhPUyZ5tH4ICWe7aTLlB2u6KEhpSHl63HJi3jpZFtvw==",
"dependencies": {
"css-select": "^5.1.0",
"css-tree": "^1.1.3"
},
"peerDependencies": {
"react": "*",
- "react-native": ">=0.50.0"
+ "react-native": "*"
}
},
"node_modules/react-native-svg-transformer": {
@@ -64220,9 +64220,9 @@
"requires": {}
},
"react-native-gesture-handler": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.5.0.tgz",
- "integrity": "sha512-djZdcprFf08PZC332D+AeG5wcGeAPhzfCJtB3otUgOgTlvjVXmg/SLFdPJSpzLBqkRAmrC77tM79QgKbuLxkfw==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.6.0.tgz",
+ "integrity": "sha512-IwdYdt5FKjjbRSrSqh8hoNctlYZl5DFnqSJ6buKtrl4A4gyzkrtW6WcmOFl5LnCa6Bcw+znSD77O6UiZ8qda7g==",
"requires": {
"@egjs/hammerjs": "^2.0.17",
"hoist-non-react-statics": "^3.3.0",
@@ -64391,9 +64391,9 @@
}
},
"react-native-svg": {
- "version": "12.4.4",
- "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-12.4.4.tgz",
- "integrity": "sha512-LpcNlEVCURexqPAvQ9ne8KrPVfYz0wIDygwud8VMRmXLezysXzyQN/DTsjm1BO9lIfYp55WQsr3u3yW/vk6iiA==",
+ "version": "13.1.0",
+ "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.1.0.tgz",
+ "integrity": "sha512-drYa+0piaQ27xFEp1MxRBSu6eHbR37qQITKTHNOmPv1NhPUyZ5tH4ICWe7aTLlB2u6KEhpSHl63HJi3jpZFtvw==",
"requires": {
"css-select": "^5.1.0",
"css-tree": "^1.1.3"
diff --git a/package.json b/package.json
index 158459e78c34..7fe2c037842d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.1.95-4",
+ "version": "1.1.96-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.",
@@ -87,7 +87,7 @@
"react-native-collapsible": "^1.6.0",
"react-native-config": "^1.4.5",
"react-native-document-picker": "^8.0.0",
- "react-native-gesture-handler": "2.5.0",
+ "react-native-gesture-handler": "2.6.0",
"react-native-google-places-autocomplete": "git+https://github.com/Expensify/react-native-google-places-autocomplete.git#3bbd17d63e6c38d38d857b50f6037c1c0376ff06",
"react-native-haptic-feedback": "^1.13.0",
"react-native-image-pan-zoom": "^2.1.12",
@@ -104,7 +104,7 @@
"react-native-render-html": "6.3.1",
"react-native-safe-area-context": "^3.1.4",
"react-native-screens": "^3.10.1",
- "react-native-svg": "^12.1.0",
+ "react-native-svg": "^13.1.0",
"react-native-webview": "^11.17.2",
"react-pdf": "5.7.2",
"react-plaid-link": "3.3.2",
diff --git a/src/components/DotIndicatorMessage.js b/src/components/DotIndicatorMessage.js
new file mode 100644
index 000000000000..2b073627ef51
--- /dev/null
+++ b/src/components/DotIndicatorMessage.js
@@ -0,0 +1,68 @@
+import React from 'react';
+import _ from 'underscore';
+import PropTypes from 'prop-types';
+import {View} from 'react-native';
+import styles from '../styles/styles';
+import Icon from './Icon';
+import * as Expensicons from './Icon/Expensicons';
+import colors from '../styles/colors';
+import variables from '../styles/variables';
+import Text from './Text';
+
+const propTypes = {
+ /**
+ * In most cases this should just be errors from onxyData
+ * if you are not passing that data then this needs to be in a similar shape like
+ * {
+ * timestamp: 'message',
+ * }
+ */
+ messages: PropTypes.objectOf(PropTypes.string),
+
+ // The type of message, 'error' shows a red dot, 'success' shows a green dot
+ type: PropTypes.oneOf(['error', 'success']).isRequired,
+
+ // Additional styles to apply to the container */
+ // eslint-disable-next-line react/forbid-prop-types
+ style: PropTypes.arrayOf(PropTypes.object),
+};
+
+const defaultProps = {
+ messages: {},
+ style: [],
+};
+
+const DotIndicatorMessage = (props) => {
+ if (_.isEmpty(props.messages)) {
+ return null;
+ }
+
+ // To ensure messages are presented in order we are sort of destroying the data we are given
+ // and rebuilding as an array so we can render the messages in order. We don't really care about
+ // the microtime timestamps anyways so isn't the end of the world that we sort of lose them here.
+ // BEWARE: if you decide to refactor this and keep the microtime keys it could cause performance issues
+ const sortedMessages = _.chain(props.messages)
+ .keys()
+ .sortBy()
+ .map(key => props.messages[key])
+ .value();
+
+ return (
+
+
+
+
+
+ {_.map(sortedMessages, (message, i) => (
+ {message}
+ ))}
+
+
+ );
+};
+
+DotIndicatorMessage.propTypes = propTypes;
+DotIndicatorMessage.defaultProps = defaultProps;
+
+export default DotIndicatorMessage;
+
diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js
index bd0eb8a8b6f7..54d6bc0ab9be 100644
--- a/src/components/OfflineWithFeedback.js
+++ b/src/components/OfflineWithFeedback.js
@@ -7,14 +7,12 @@ import withLocalize, {withLocalizePropTypes} from './withLocalize';
import {withNetwork} from './OnyxProvider';
import networkPropTypes from './networkPropTypes';
import stylePropTypes from '../styles/stylePropTypes';
-import Text from './Text';
import styles from '../styles/styles';
import Tooltip from './Tooltip';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import * as StyleUtils from '../styles/StyleUtils';
-import colors from '../styles/colors';
-import variables from '../styles/variables';
+import DotIndicatorMessage from './DotIndicatorMessage';
/**
* This component should be used when we are using the offline pattern B (offline with feedback).
@@ -83,11 +81,6 @@ const OfflineWithFeedback = (props) => {
const needsStrikeThrough = props.network.isOffline && props.pendingAction === 'delete';
const hideChildren = !props.network.isOffline && props.pendingAction === 'delete' && !hasErrors;
let children = props.children;
- const sortedErrors = _.chain(props.errors)
- .keys()
- .sortBy()
- .map(key => props.errors[key])
- .value();
// Apply strikethrough to children if needed, but skip it if we are not going to render them
if (needsStrikeThrough && !hideChildren) {
@@ -102,14 +95,7 @@ const OfflineWithFeedback = (props) => {
)}
{hasErrors && (
-
-
-
-
- {_.map(sortedErrors, (error, i) => (
- {error}
- ))}
-
+
`${count} new message${count > 1 ? 's' : ''}`,
+ newMessages: 'New messages',
reportTypingIndicator: {
isTyping: 'is typing...',
areTyping: 'are typing...',
diff --git a/src/languages/es.js b/src/languages/es.js
index 82369add2179..3810cb69a7e7 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -202,7 +202,7 @@ export default {
beginningOfChatHistoryPolicyExpenseChatPartTwo: ' y ',
beginningOfChatHistoryPolicyExpenseChatPartThree: ' empieza aquÃ! :tada: Este es el lugar donde chatear, pedir dinero y pagar.',
},
- newMessageCount: ({count}) => `${count} mensaje${count > 1 ? 's' : ''} nuevo${count > 1 ? 's' : ''}`,
+ newMessages: 'Mensajes nuevos',
reportTypingIndicator: {
isTyping: 'está escribiendo...',
areTyping: 'están escribiendo...',
diff --git a/src/libs/ErrorUtils.js b/src/libs/ErrorUtils.js
index 115468ff1ea6..bcc6c59cae2b 100644
--- a/src/libs/ErrorUtils.js
+++ b/src/libs/ErrorUtils.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import CONST from '../CONST';
/**
@@ -35,7 +36,23 @@ function getAuthenticateErrorMessage(response) {
}
}
+/**
+ * @param {Object} onyxData
+ * @param {Object} onyxData.errors
+ * @returns {String}
+ */
+function getLatestErrorMessage(onyxData) {
+ return _.chain(onyxData.errors || [])
+ .keys()
+ .sortBy()
+ .reverse()
+ .map(key => onyxData.errors[key])
+ .first()
+ .value();
+}
+
export {
// eslint-disable-next-line import/prefer-default-export
getAuthenticateErrorMessage,
+ getLatestErrorMessage,
};
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js
index cbb6e0af27a3..0d1eceb4d711 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.js
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.js
@@ -8,7 +8,6 @@ import * as StyleUtils from '../../../styles/StyleUtils';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions';
import CONST from '../../../CONST';
import compose from '../../compose';
-import * as Report from '../../actions/Report';
import * as PersonalDetails from '../../actions/PersonalDetails';
import * as Pusher from '../../Pusher/pusher';
import PusherConnectionManager from '../../PusherConnectionManager';
@@ -110,7 +109,6 @@ class AuthScreens extends React.Component {
cluster: CONFIG.PUSHER.CLUSTER,
authEndpoint: `${CONFIG.EXPENSIFY.URL_API_ROOT}api?command=AuthenticatePusher`,
}).then(() => {
- Report.subscribeToUserEvents();
User.subscribeToUserEvents();
Policy.subscribeToPolicyEvents();
});
diff --git a/src/libs/NumberUtils.js b/src/libs/NumberUtils.js
index 5acff89aca09..a63396d0569c 100644
--- a/src/libs/NumberUtils.js
+++ b/src/libs/NumberUtils.js
@@ -4,7 +4,6 @@ import CONST from '../CONST';
* Generates a random positive 64 bit numeric string by randomly generating the left, middle, and right parts and concatenating them. Used to generate client-side ids.
* @returns {String} string representation of a randomly generated 64 bit signed integer
*/
-/* eslint-disable no-unused-vars */
function rand64() {
// Max 64-bit signed:
// 9,223,372,036,854,775,807
diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js
index d17c4bb4d330..2364ae9640bb 100644
--- a/src/libs/OptionsListUtils.js
+++ b/src/libs/OptionsListUtils.js
@@ -222,6 +222,7 @@ function createOption(logins, personalDetails, report, reportActions = {}, {
const personalDetailMap = getPersonalDetailsForLogins(logins, personalDetails);
const personalDetailList = _.values(personalDetailMap);
const isArchivedRoom = ReportUtils.isArchivedRoom(report);
+ const isDefaultRoom = ReportUtils.isDefaultRoom(report);
const hasMultipleParticipants = personalDetailList.length > 1 || isChatRoom || isPolicyExpenseChat;
const personalDetail = personalDetailList[0];
const hasOutstandingIOU = lodashGet(report, 'hasOutstandingIOU', false);
@@ -286,6 +287,7 @@ function createOption(logins, personalDetails, report, reportActions = {}, {
iouReportAmount: lodashGet(iouReport, 'total', 0),
isChatRoom,
isArchivedRoom,
+ isDefaultRoom,
shouldShowSubscript: isPolicyExpenseChat && !report.isOwnPolicyExpenseChat && !isArchivedRoom,
isPolicyExpenseChat,
};
@@ -428,8 +430,9 @@ function getOptions(reports, personalDetails, activeReportID, {
const shouldFilterReportIfRead = hideReadReports && report.unreadActionCount === 0;
const shouldFilterReport = shouldFilterReportIfEmpty || shouldFilterReportIfRead;
+
if (report.reportID !== activeReportID
- && !report.isPinned
+ && (!report.isPinned || isDefaultRoom)
&& !hasDraftComment
&& shouldFilterReport
&& !reportContainsIOUDebt) {
@@ -516,8 +519,10 @@ function getOptions(reports, personalDetails, activeReportID, {
}
// If the report is pinned and we are using the option to display pinned reports on top then we need to
- // collect the pinned reports so we can sort them alphabetically once they are collected
- if (prioritizePinnedReports && reportOption.isPinned) {
+ // collect the pinned reports so we can sort them alphabetically once they are collected. We want to skip
+ // default archived rooms.
+ if (prioritizePinnedReports && reportOption.isPinned
+ && !(reportOption.isArchivedRoom && reportOption.isDefaultRoom)) {
pinnedReportOptions.push(reportOption);
} else if (prioritizeIOUDebts && reportOption.hasOutstandingIOU && !reportOption.isIOUReportOwner) {
iouDebtReportOptions.push(reportOption);
diff --git a/src/libs/Pusher/EventType.js b/src/libs/Pusher/EventType.js
index 97fa8cc9246b..79e7fb0c5137 100644
--- a/src/libs/Pusher/EventType.js
+++ b/src/libs/Pusher/EventType.js
@@ -4,7 +4,6 @@
*/
export default {
REPORT_COMMENT: 'reportComment',
- REPORT_COMMENT_EDIT: 'reportCommentEdit',
PREFERRED_LOCALE: 'preferredLocale',
EXPENSIFY_CARD_UPDATE: 'expensifyCardUpdate',
SCREEN_SHARE_REQUEST: 'screenshareRequest',
diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js
index 5cef373dd1ad..5146eeb8e986 100644
--- a/src/libs/ReportUtils.js
+++ b/src/libs/ReportUtils.js
@@ -99,7 +99,8 @@ function canEditReportAction(reportAction) {
return reportAction.actorEmail === sessionEmail
&& reportAction.reportActionID
&& reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT
- && !isReportMessageAttachment(lodashGet(reportAction, ['message', 0], {}));
+ && !isReportMessageAttachment(lodashGet(reportAction, ['message', 0], {}))
+ && reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE;
}
/**
@@ -113,7 +114,8 @@ function canEditReportAction(reportAction) {
function canDeleteReportAction(reportAction) {
return reportAction.actorEmail === sessionEmail
&& reportAction.reportActionID
- && reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT;
+ && reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT
+ && reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE;
}
/**
diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index 42a98a7e5850..ebc19a252847 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -2,6 +2,7 @@ import _ from 'underscore';
import Onyx from 'react-native-onyx';
import lodashGet from 'lodash/get';
import {PUBLIC_DOMAINS} from 'expensify-common/lib/CONST';
+import Str from 'expensify-common/lib/str';
import * as DeprecatedAPI from '../deprecatedAPI';
import * as API from '../API';
import ONYXKEYS from '../../ONYXKEYS';
@@ -752,14 +753,6 @@ function clearAddMemberError(policyID, memberEmail) {
});
}
-/**
- * @param {String} value
- * @returns {String}
- */
-function capitalizeFirstLetter(value) {
- return value.charAt(0).toUpperCase() + value.slice(1);
-}
-
/**
* Generate a policy name based on an email and policy list.
* @returns {String}
@@ -774,9 +767,9 @@ function generateDefaultWorkspaceName() {
const domain = emailParts[1];
if (_.includes(PUBLIC_DOMAINS, domain.toLowerCase())) {
- defaultWorkspaceName = `${capitalizeFirstLetter(username)}'s Workspace`;
+ defaultWorkspaceName = `${Str.UCFirst(username)}'s Workspace`;
} else {
- defaultWorkspaceName = `${capitalizeFirstLetter(domain.split('.')[0])}'s Workspace`;
+ defaultWorkspaceName = `${Str.UCFirst(domain.split('.')[0])}'s Workspace`;
}
if (`@${domain.toLowerCase()}` === CONST.SMS.DOMAIN) {
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index 541bb2a51a72..4e24a66322c2 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -24,7 +24,6 @@ import * as ReportUtils from '../ReportUtils';
import * as ReportActions from './ReportActions';
import Growl from '../Growl';
import * as Localize from '../Localize';
-import PusherUtils from '../PusherUtils';
import DateUtils from '../DateUtils';
import * as ReportActionsUtils from '../ReportActionsUtils';
import * as NumberUtils from '../NumberUtils';
@@ -416,16 +415,6 @@ function fetchIOUReportByID(iouReportID, chatReportID, shouldRedirectIfEmpty = f
});
}
-/**
- * @param {Number} reportID
- * @param {Number} sequenceNumber
- */
-function setNewMarkerPosition(reportID, sequenceNumber) {
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {
- newMarkerSequenceNumber: sequenceNumber,
- });
-}
-
/**
* Get the private pusher channel name for a Report.
*
@@ -436,48 +425,6 @@ function getReportChannelName(reportID) {
return `${CONST.PUSHER.PRIVATE_REPORT_CHANNEL_PREFIX}${reportID}${CONFIG.PUSHER.SUFFIX}`;
}
-/**
- * Initialize our pusher subscriptions to listen for new report comments and pin toggles
- */
-function subscribeToUserEvents() {
- // If we don't have the user's accountID yet we can't subscribe so return early
- if (!currentUserAccountID) {
- return;
- }
-
- const pusherChannelName = `${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}${currentUserAccountID}${CONFIG.PUSHER.SUFFIX}`;
- if (Pusher.isSubscribed(pusherChannelName) || Pusher.isAlreadySubscribing(pusherChannelName)) {
- return;
- }
-
- // Live-update a report's actions when an 'edit comment' event is received.
- PusherUtils.subscribeToPrivateUserChannelEvent(Pusher.TYPE.REPORT_COMMENT_EDIT,
- currentUserAccountID,
- ({reportID, sequenceNumber, message}) => {
- // We only want the active client to process these events once otherwise multiple tabs would decrement the 'unreadActionCount'
- if (!ActiveClientManager.isClientTheLeader()) {
- return;
- }
-
- const actionsToMerge = {};
- actionsToMerge[sequenceNumber] = {message: [message]};
-
- // If someone besides the current user deleted an action and the sequenceNumber is greater than our last read we will decrement the unread count
- // we skip this for the current user because we should already have decremented the count optimistically when they deleted the comment.
- const isFromCurrentUser = ReportActions.isFromCurrentUser(reportID, sequenceNumber, currentUserAccountID, actionsToMerge);
- if (!message.html && !isFromCurrentUser && sequenceNumber > getLastReadSequenceNumber(reportID)) {
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {
- unreadActionCount: Math.max(getUnreadActionCount(reportID) - 1, 0),
- });
- }
-
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {
- lastMessageText: ReportActions.getLastVisibleMessageText(reportID, actionsToMerge),
- });
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, actionsToMerge);
- });
-}
-
/**
* Setup reportComment push notification callbacks.
*/
@@ -684,7 +631,7 @@ function createOptimisticChatReport(participantList) {
lastActorEmail: '',
lastMessageHtml: '',
lastMessageText: null,
- lastReadSequenceNumber: undefined,
+ lastReadSequenceNumber: 0,
lastMessageTimestamp: 0,
lastVisitedTimestamp: 0,
maxSequenceNumber: 0,
@@ -880,73 +827,6 @@ function addComment(reportID, text) {
addActions(reportID, text);
}
-/**
- * Deletes a comment from the report, basically sets it as empty string
- *
- * @param {Number} reportID
- * @param {Object} reportAction
- */
-function deleteReportComment(reportID, reportAction) {
- // Optimistic Response
- const sequenceNumber = reportAction.sequenceNumber;
- const reportActionsToMerge = {};
- const oldMessage = {...reportAction.message};
- reportActionsToMerge[sequenceNumber] = {
- ...reportAction,
- message: [
- {
- type: CONST.REPORT.MESSAGE.TYPE.COMMENT,
- html: '',
- text: '',
- },
- ],
- };
-
- // If the comment we are deleting is more recent than our last read comment we will update the unread count
- if (sequenceNumber > getLastReadSequenceNumber(reportID)) {
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {
- unreadActionCount: Math.max(getUnreadActionCount(reportID) - 1, 0),
- });
- }
-
- // Optimistically update the report and reportActions
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, reportActionsToMerge);
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {
- lastMessageText: ReportActions.getLastVisibleMessageText(reportID, reportActionsToMerge),
- });
-
- // Try to delete the comment by calling the API
- DeprecatedAPI.Report_EditComment({
- reportID,
- reportActionID: reportAction.reportActionID,
- reportComment: '',
- sequenceNumber,
- })
- .then((response) => {
- if (response.jsonCode === 200) {
- return;
- }
-
- // Reverse Optimistic Response
- reportActionsToMerge[sequenceNumber] = {
- ...reportAction,
- message: oldMessage,
- };
-
- if (sequenceNumber > getLastReadSequenceNumber(reportID)) {
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {
- unreadActionCount: getUnreadActionCount(reportID) + 1,
- });
- }
-
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {
- lastMessageText: ReportActions.getLastVisibleMessageText(reportID, reportActionsToMerge),
- });
-
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, reportActionsToMerge);
- });
-}
-
/**
* Gets the latest page of report actions and updates the last read message
*
@@ -1112,7 +992,6 @@ function markCommentAsUnread(reportID, sequenceNumber) {
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
value: {
- newMarkerSequenceNumber: sequenceNumber,
lastReadSequenceNumber: newLastReadSequenceNumber,
lastVisitedTimestamp: Date.now(),
unreadActionCount: calculateUnreadActionCount(reportID, newLastReadSequenceNumber, maxSequenceNumber),
@@ -1216,6 +1095,88 @@ Onyx.connect({
callback: handleReportChanged,
});
+/**
+ * Deletes a comment from the report, basically sets it as empty string
+ *
+ * @param {Number} reportID
+ * @param {Object} reportAction
+ */
+function deleteReportComment(reportID, reportAction) {
+ const sequenceNumber = reportAction.sequenceNumber;
+ const optimisticReportActions = {
+ [sequenceNumber]: {
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
+ },
+ };
+
+ // If we are deleting the last visible message, let's find the previous visible one
+ // and update the lastMessageText in the chat preview.
+ const optimisticReport = {
+ lastMessageText: ReportActions.getLastVisibleMessageText(reportID, {
+ [sequenceNumber]: {
+ message: [{
+ html: '',
+ text: '',
+ }],
+ },
+ }),
+ };
+
+ // If the API call fails we must show the original message again, so we revert the message content back to how it was
+ // and and remove the pendingAction so the strike-through clears
+ const failureData = [
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
+ value: {
+ [sequenceNumber]: {
+ message: reportAction.message,
+ pendingAction: null,
+ },
+ },
+ },
+ ];
+
+ const successData = [
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
+ value: {
+ [sequenceNumber]: {
+ pendingAction: null,
+ },
+ },
+ },
+ ];
+
+ // If we are deleting an unread message that is greater than our last read we decrease the unreadActionCount
+ // since the message we are deleting is an unread
+ if (sequenceNumber > getLastReadSequenceNumber(reportID)) {
+ const unreadActionCount = getUnreadActionCount(reportID);
+ optimisticReport.unreadActionCount = Math.max(unreadActionCount - 1, 0);
+ }
+
+ const optimisticData = [
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
+ value: optimisticReportActions,
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
+ value: optimisticReport,
+ },
+ ];
+
+ const parameters = {
+ reportID,
+ sequenceNumber,
+ reportActionID: reportAction.reportActionID,
+ };
+ API.write('DeleteComment', parameters, {optimisticData, successData, failureData});
+}
+
/**
* Saves a new message for a comment. Marks the comment as edited, which will be reflected in the UI.
*
@@ -1241,32 +1202,59 @@ function editReportComment(reportID, originalReportAction, textForNewComment) {
return;
}
- // Optimistically update the report action with the new message
+ // Optimistically update the reportAction with the new message
const sequenceNumber = originalReportAction.sequenceNumber;
- const newReportAction = {...originalReportAction};
- const actionToMerge = {};
- newReportAction.message[0].isEdited = true;
- newReportAction.message[0].html = htmlForNewComment;
- newReportAction.message[0].text = parser.htmlToText(htmlForNewComment);
- actionToMerge[sequenceNumber] = newReportAction;
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, actionToMerge);
-
- // Persist the updated report comment
- DeprecatedAPI.Report_EditComment({
+ const optimisticReportActions = {
+ [sequenceNumber]: {
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ message: [{
+ isEdited: true,
+ html: htmlForNewComment,
+ text: textForNewComment,
+ }],
+ },
+ };
+
+ const optimisticData = [
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
+ value: optimisticReportActions,
+ },
+ ];
+
+ const failureData = [
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
+ value: {
+ [sequenceNumber]: {
+ ...originalReportAction,
+ pendingAction: null,
+ },
+ },
+ },
+ ];
+
+ const successData = [
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
+ value: {
+ [sequenceNumber]: {
+ pendingAction: null,
+ },
+ },
+ },
+ ];
+
+ const parameters = {
reportID,
- reportActionID: originalReportAction.reportActionID,
- reportComment: htmlForNewComment,
sequenceNumber,
- })
- .then((response) => {
- if (response.jsonCode === 200) {
- return;
- }
-
- // If it fails, reset Onyx
- actionToMerge[sequenceNumber] = originalReportAction;
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, actionToMerge);
- });
+ reportComment: htmlForNewComment,
+ reportActionID: originalReportAction.reportActionID,
+ };
+ API.write('UpdateComment', parameters, {optimisticData, successData, failureData});
}
/**
@@ -1514,12 +1502,6 @@ function viewNewReportAction(reportID, action) {
return;
}
- // When a new message comes in, if the New marker is not already set (newMarkerSequenceNumber === 0), set the marker above the incoming message.
- const report = lodashGet(allReports, 'reportID', {});
- if (lodashGet(report, 'newMarkerSequenceNumber', 0) === 0 && report.unreadActionCount > 0) {
- setNewMarkerPosition(reportID, report.lastReadSequenceNumber + 1);
- }
-
Log.info('[LOCAL_NOTIFICATION] Creating notification');
LocalNotification.showCommentNotification({
reportAction: action,
@@ -1580,9 +1562,7 @@ export {
addAttachment,
reconnect,
updateNotificationPreference,
- setNewMarkerPosition,
subscribeToReportTypingEvents,
- subscribeToUserEvents,
subscribeToReportCommentPushNotifications,
unsubscribeFromReportChannel,
saveReportComment,
diff --git a/src/libs/actions/ReportActions.js b/src/libs/actions/ReportActions.js
index 4cbb0a4a00c6..504e915ef76b 100644
--- a/src/libs/actions/ReportActions.js
+++ b/src/libs/actions/ReportActions.js
@@ -109,9 +109,22 @@ function deleteOptimisticReportAction(reportID, sequenceNumber) {
});
}
+/**
+ * @param {Number} reportID
+ * @param {String} sequenceNumber
+ */
+function clearReportActionErrors(reportID, sequenceNumber) {
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {
+ [sequenceNumber]: {
+ errors: null,
+ },
+ });
+}
+
export {
getDeletedCommentsCount,
getLastVisibleMessageText,
+ clearReportActionErrors,
isFromCurrentUser,
deleteOptimisticReportAction,
};
diff --git a/src/libs/actions/Session/index.js b/src/libs/actions/Session/index.js
index 8e191c164fe6..6342b3d05ebf 100644
--- a/src/libs/actions/Session/index.js
+++ b/src/libs/actions/Session/index.js
@@ -90,11 +90,33 @@ function signOutAndRedirectToSignIn() {
* @param {String} [login]
*/
function resendValidationLink(login = credentials.login) {
- Onyx.merge(ONYXKEYS.ACCOUNT, {isLoading: true});
- DeprecatedAPI.ResendValidateCode({email: login})
- .finally(() => {
- Onyx.merge(ONYXKEYS.ACCOUNT, {isLoading: false});
- });
+ const optimisticData = [{
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: ONYXKEYS.ACCOUNT,
+ value: {
+ isLoading: true,
+ errors: null,
+ message: null,
+ },
+ }];
+ const successData = [{
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: ONYXKEYS.ACCOUNT,
+ value: {
+ isLoading: false,
+ message: Localize.translateLocal('resendValidationForm.linkHasBeenResent'),
+ },
+ }];
+ const failureData = [{
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: ONYXKEYS.ACCOUNT,
+ value: {
+ isLoading: false,
+ message: null,
+ },
+ }];
+
+ API.write('RequestAccountValidationLink', {email: login}, {optimisticData, successData, failureData});
}
/**
diff --git a/src/libs/deprecatedAPI.js b/src/libs/deprecatedAPI.js
index b056f08a2890..26a25ff2c9e7 100644
--- a/src/libs/deprecatedAPI.js
+++ b/src/libs/deprecatedAPI.js
@@ -260,19 +260,6 @@ function Report_GetHistory(parameters) {
return Network.post(commandName, parameters);
}
-/**
- * @param {Object} parameters
- * @param {Number} parameters.reportID
- * @param {Number} parameters.reportActionID
- * @param {String} parameters.reportComment
- * @returns {Promise}
- */
-function Report_EditComment(parameters) {
- const commandName = 'Report_EditComment';
- requireParameters(['reportID', 'reportActionID', 'reportComment'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
/**
* @param {Object} parameters
* @param {String} parameters.email
@@ -502,18 +489,6 @@ function Policy_Create(parameters) {
return Network.post(commandName, parameters);
}
-/**
- * @param {Object} parameters
- * @param {String} parameters.policyID
- * @param {String} parameters.value
- * @returns {Promise}
- */
-function Policy_CustomUnit_Update(parameters) {
- const commandName = 'Policy_CustomUnit_Update';
- requireParameters(['policyID', 'customUnit'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
/**
* @param {Object} parameters
* @param {String} parameters.policyID
@@ -650,7 +625,6 @@ export {
Policy_Employees_Merge,
RejectTransaction,
Report_GetHistory,
- Report_EditComment,
ResendValidateCode,
SetNameValuePair,
SetPassword,
@@ -667,7 +641,6 @@ export {
TransferWalletBalance,
GetLocalCurrency,
Policy_Create,
- Policy_CustomUnit_Update,
Policy_CustomUnitRate_Update,
Policy_Employees_Remove,
PreferredLocale_Update,
diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js
index d2c114335e53..717963c3dd49 100644
--- a/src/pages/home/ReportScreen.js
+++ b/src/pages/home/ReportScreen.js
@@ -53,9 +53,6 @@ const propTypes = {
/** The largest sequenceNumber on this report */
maxSequenceNumber: PropTypes.number,
- /** The current position of the new marker */
- newMarkerSequenceNumber: PropTypes.number,
-
/** Whether there is an outstanding amount in IOU */
hasOutstandingIOU: PropTypes.bool,
diff --git a/src/pages/home/report/FloatingMessageCounter/index.js b/src/pages/home/report/FloatingMessageCounter/index.js
index fb9dfba7c362..7025d5a67a4f 100644
--- a/src/pages/home/report/FloatingMessageCounter/index.js
+++ b/src/pages/home/report/FloatingMessageCounter/index.js
@@ -11,25 +11,17 @@ import withLocalize, {withLocalizePropTypes} from '../../../../components/withLo
import FloatingMessageCounterContainer from './FloatingMessageCounterContainer';
const propTypes = {
- /** Count of new messages to show in the badge */
- count: PropTypes.number,
+ /** Whether the New Messages indicator is active */
+ isActive: PropTypes.bool,
- /** Whether the marker is active */
- active: PropTypes.bool,
-
- /** Callback to be called when user closes the badge */
- onClose: PropTypes.func,
-
- /** Callback to be called when user clicks the marker */
+ /** Callback to be called when user clicks the New Messages indicator */
onClick: PropTypes.func,
...withLocalizePropTypes,
};
const defaultProps = {
- count: 0,
- active: false,
- onClose: () => {},
+ isActive: false,
onClick: () => {},
};
@@ -45,7 +37,7 @@ class FloatingMessageCounter extends PureComponent {
}
componentDidUpdate() {
- if (this.props.active && this.props.count > 0) {
+ if (this.props.isActive) {
this.show();
} else {
this.hide();
@@ -93,24 +85,10 @@ class FloatingMessageCounter extends PureComponent {
styles.textWhite,
]}
>
- {this.props.translate(
- 'newMessageCount',
- {count: this.props.count},
- )}
+ {this.props.translate('newMessages')}
)}
- shouldRemoveRightBorderRadius
- />
-