From 5ae65ead66ad2f87b2dcd4b1ab55c87a4b3a17ce Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 26 Jul 2023 16:19:52 -0700 Subject: [PATCH 001/254] Component for indicator messages with close button --- .../DotIndicatorMessageWithClose.js | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/components/DotIndicatorMessageWithClose.js diff --git a/src/components/DotIndicatorMessageWithClose.js b/src/components/DotIndicatorMessageWithClose.js new file mode 100644 index 000000000000..1a0770508eb3 --- /dev/null +++ b/src/components/DotIndicatorMessageWithClose.js @@ -0,0 +1,74 @@ +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 DotIndicatorMessage from './DotIndicatorMessage'; +import Tooltip from './Tooltip'; +import CONST from '../CONST'; +import * as StyleUtils from '../styles/StyleUtils'; +import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; +import stylePropTypes from '../styles/stylePropTypes'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; + +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.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))])), + + // The type of message, 'error' shows a red dot, 'success' shows a green dot + type: PropTypes.oneOf(['error', 'success']).isRequired, + + /** A function to run when the X button next to the message is clicked */ + onClose: PropTypes.func, + + /** Additional style object for the container*/ + containerStyles: stylePropTypes, + + ...withLocalizePropTypes, +}; + +const defaultProps = { + messages: {}, + onClose: () => {}, + containerStyles: [], +}; + +function DotIndicatorMessageWithClose(props) { + if (_.isEmpty(props.messages)) { + return null; + } + + return ( + + + + + + + + + ); +} + +DotIndicatorMessageWithClose.propTypes = propTypes; +DotIndicatorMessageWithClose.defaultProps = defaultProps; +DotIndicatorMessageWithClose.displayName = 'DotIndicatorMessageWithClose'; + +export default withLocalize(DotIndicatorMessageWithClose); From 1039385b4d1c94b7f7586ea3aff57e716e171391 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 26 Jul 2023 16:21:29 -0700 Subject: [PATCH 002/254] Show message when members added with primary login --- src/languages/en.js | 1 + src/pages/workspace/WorkspaceMembersPage.js | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/languages/en.js b/src/languages/en.js index b7a130addf18..a0f73bc41788 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -1150,6 +1150,7 @@ export default { cannotRemove: 'You cannot remove yourself or the workspace owner.', genericRemove: 'There was a problem removing that workspace member.', }, + addedWithPrimary: 'Some users were added with their primary logins.', }, card: { header: 'Unlock free Expensify Cards', diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index e758d738d964..028420d60093 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -38,6 +38,7 @@ import PressableWithFeedback from '../../components/Pressable/PressableWithFeedb import usePrevious from '../../hooks/usePrevious'; import Log from '../../libs/Log'; import * as PersonalDetailsUtils from '../../libs/PersonalDetailsUtils'; +import DotIndicatorMessageWithClose from '../../components/DotIndicatorMessageWithClose'; const propTypes = { /** All personal details asssociated with user */ @@ -397,6 +398,7 @@ function WorkspaceMembersPage(props) { }); const policyID = lodashGet(props.route, 'params.policyID'); const policyName = lodashGet(props.policy, 'name'); + const primaryLoginsInvited = props.policy.primaryLoginsInvited || {}; return ( + {data.length > 0 ? ( From fa3adb21e53d8cdb0460c7fe739ba253bbbb6847 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 26 Jul 2023 16:21:57 -0700 Subject: [PATCH 003/254] Style fix --- src/components/DotIndicatorMessageWithClose.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/DotIndicatorMessageWithClose.js b/src/components/DotIndicatorMessageWithClose.js index 1a0770508eb3..fd535475583f 100644 --- a/src/components/DotIndicatorMessageWithClose.js +++ b/src/components/DotIndicatorMessageWithClose.js @@ -29,7 +29,7 @@ const propTypes = { /** A function to run when the X button next to the message is clicked */ onClose: PropTypes.func, - /** Additional style object for the container*/ + /** Additional style object for the container */ containerStyles: stylePropTypes, ...withLocalizePropTypes, @@ -47,7 +47,7 @@ function DotIndicatorMessageWithClose(props) { } return ( - + Date: Wed, 26 Jul 2023 16:29:40 -0700 Subject: [PATCH 004/254] Allow dismissing added with primary message --- src/libs/actions/Policy.js | 5 +++++ src/pages/workspace/WorkspaceMembersPage.js | 1 + 2 files changed, 6 insertions(+) diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index ebf9f100bc90..b1364dd9c373 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -1144,6 +1144,10 @@ function clearErrors(policyID) { hideWorkspaceAlertMessage(policyID); } +function dismissAddedWithPrimaryMessages(policyID) { + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {primaryLoginsInvited: null}); +} + export { removeMembers, addMembersToWorkspace, @@ -1173,4 +1177,5 @@ export { setWorkspaceInviteMembersDraft, isPolicyOwner, clearErrors, + dismissAddedWithPrimaryMessages, }; diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 028420d60093..7c396aa0f65d 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -460,6 +460,7 @@ function WorkspaceMembersPage(props) { type="success" messages={_.isEmpty(primaryLoginsInvited) ? null : {0: props.translate('workspace.people.addedWithPrimary')}} containerStyles={[styles.pt3]} + onClose={() => Policy.dismissAddedWithPrimaryMessages(props.route.params.policyID)} /> {data.length > 0 ? ( From 8a449b1a8b70fdc1fb4a82038a5d94f0b65b1a2a Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 26 Jul 2023 18:02:22 -0700 Subject: [PATCH 005/254] Show added by secondary login messages --- src/languages/en.js | 1 + src/pages/workspace/WorkspaceMembersPage.js | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index a0f73bc41788..b35bf44f6159 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -1151,6 +1151,7 @@ export default { genericRemove: 'There was a problem removing that workspace member.', }, addedWithPrimary: 'Some users were added with their primary logins.', + invitedBySecondaryLogin: ({secondaryLogin}) => `Added by secondary login ${secondaryLogin}.`, }, card: { header: 'Unlock free Expensify Cards', diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 7c396aa0f65d..3dadf76b2431 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -342,6 +342,7 @@ function WorkspaceMembersPage(props) { }} onSelectRow={() => toggleUser(item.accountID, item.pendingAction)} /> + {Boolean(item.invitedSecondaryLogin) && {props.translate('workspace.people.invitedBySecondaryLogin', {secondaryLogin: item.invitedSecondaryLogin})}} {(props.session.accountID === item.accountID || item.role === 'admin') && ( @@ -362,6 +363,7 @@ function WorkspaceMembersPage(props) { [selectedEmployees, errors, props.session.accountID, dismissError, toggleUser], ); + const invitedSecondaryToPrimaryLogins = _.invert(props.policy.primaryLoginsInvited); const policyOwner = lodashGet(props.policy, 'owner'); const currentUserLogin = lodashGet(props.currentUserPersonalDetails, 'login'); const removableMembers = {}; @@ -378,6 +380,7 @@ function WorkspaceMembersPage(props) { data.push({ ...policyMember, ...details, + invitedSecondaryLogin: invitedSecondaryToPrimaryLogins[details.login] || '', }); }); data = _.sortBy(data, (value) => value.displayName.toLowerCase()); @@ -398,7 +401,6 @@ function WorkspaceMembersPage(props) { }); const policyID = lodashGet(props.route, 'params.policyID'); const policyName = lodashGet(props.policy, 'name'); - const primaryLoginsInvited = props.policy.primaryLoginsInvited || {}; return ( Policy.dismissAddedWithPrimaryMessages(props.route.params.policyID)} /> From 3afc37b3a183f25584a56acc7759128f0d0d1896 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 3 Aug 2023 18:23:29 -0700 Subject: [PATCH 006/254] Reuse component where it was extracted from --- src/components/OfflineWithFeedback.js | 29 +++++++-------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js index 820cce252205..87963ed26fd9 100644 --- a/src/components/OfflineWithFeedback.js +++ b/src/components/OfflineWithFeedback.js @@ -9,13 +9,9 @@ import CONST from '../CONST'; import networkPropTypes from './networkPropTypes'; import stylePropTypes from '../styles/stylePropTypes'; 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 DotIndicatorMessage from './DotIndicatorMessage'; import shouldRenderOffscreen from '../libs/shouldRenderOffscreen'; -import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; +import DotIndicatorMessageWithClose from './DotIndicatorMessageWithClose'; /** * This component should be used when we are using the offline pattern B (offline with feedback). @@ -116,23 +112,12 @@ function OfflineWithFeedback(props) { )} {props.shouldShowErrorMessages && hasErrorMessages && ( - - - - - - - - + )} ); From d4c2c737245b230484be65a2f31faf40ddf8c5f6 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 3 Aug 2023 18:23:37 -0700 Subject: [PATCH 007/254] style --- src/pages/workspace/WorkspaceMembersPage.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 3dadf76b2431..647c35b4f6b3 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -342,7 +342,11 @@ function WorkspaceMembersPage(props) { }} onSelectRow={() => toggleUser(item.accountID, item.pendingAction)} /> - {Boolean(item.invitedSecondaryLogin) && {props.translate('workspace.people.invitedBySecondaryLogin', {secondaryLogin: item.invitedSecondaryLogin})}} + {Boolean(item.invitedSecondaryLogin) && ( + + {props.translate('workspace.people.invitedBySecondaryLogin', {secondaryLogin: item.invitedSecondaryLogin})} + + )} {(props.session.accountID === item.accountID || item.role === 'admin') && ( From 9157c287179821b57de11d482fc64134ce615a2b Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 3 Aug 2023 18:36:09 -0700 Subject: [PATCH 008/254] Add informative comments, better variable name --- src/components/DotIndicatorMessage.js | 8 +------- src/libs/actions/Policy.js | 5 +++++ src/pages/workspace/WorkspaceMembersPage.js | 8 +++++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/components/DotIndicatorMessage.js b/src/components/DotIndicatorMessage.js index ac550f34de3f..1073f8c9aedc 100644 --- a/src/components/DotIndicatorMessage.js +++ b/src/components/DotIndicatorMessage.js @@ -10,13 +10,7 @@ import Text from './Text'; import * as Localize from '../libs/Localize'; 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', - * } - */ + // The error messages to display messages: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))])), // The type of message, 'error' shows a red dot, 'success' shows a green dot diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index b1364dd9c373..28ebb3d29e09 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -1144,6 +1144,11 @@ function clearErrors(policyID) { hideWorkspaceAlertMessage(policyID); } +/** + * Dismiss the informative messages about which policy members were added with primary logins when invited with their secondary login. + * + * @param {String} policyID + */ function dismissAddedWithPrimaryMessages(policyID) { Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {primaryLoginsInvited: null}); } diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 647c35b4f6b3..018ffb717908 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -367,7 +367,7 @@ function WorkspaceMembersPage(props) { [selectedEmployees, errors, props.session.accountID, dismissError, toggleUser], ); - const invitedSecondaryToPrimaryLogins = _.invert(props.policy.primaryLoginsInvited); + const invitedPrimaryToSecondaryLogins = _.invert(props.policy.primaryLoginsInvited); const policyOwner = lodashGet(props.policy, 'owner'); const currentUserLogin = lodashGet(props.currentUserPersonalDetails, 'login'); const removableMembers = {}; @@ -384,7 +384,9 @@ function WorkspaceMembersPage(props) { data.push({ ...policyMember, ...details, - invitedSecondaryLogin: invitedSecondaryToPrimaryLogins[details.login] || '', + + // Note which secondary login was used to invite this primary login + invitedSecondaryLogin: invitedPrimaryToSecondaryLogins[details.login] || '', }); }); data = _.sortBy(data, (value) => value.displayName.toLowerCase()); @@ -464,7 +466,7 @@ function WorkspaceMembersPage(props) { /> Policy.dismissAddedWithPrimaryMessages(props.route.params.policyID)} /> From 0a28b29e21703cdee9083c317c54c182b7fd82a3 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 3 Aug 2023 19:22:53 -0700 Subject: [PATCH 009/254] Add spanish translations --- src/languages/es.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/languages/es.js b/src/languages/es.js index 4006f559eb1f..f2837c60b5e7 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -1159,6 +1159,8 @@ export default { cannotRemove: 'No puedes eliminarte ni a ti mismo ni al dueño del espacio de trabajo.', genericRemove: 'Ha ocurrido un problema al eliminar al miembro del espacio de trabajo.', }, + addedWithPrimary: 'Se agregaron algunos usuarios con sus inicios de sesión principales.', + invitedBySecondaryLogin: ({secondaryLogin}) => `Agregado por inicio de sesión secundario ${secondaryLogin}.`, }, card: { header: 'Desbloquea Tarjetas Expensify gratis', From f88a4560795434cfe55384c37f30e9d43b9b4fb6 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 3 Aug 2023 19:26:25 -0700 Subject: [PATCH 010/254] Change comment in proper file --- src/components/DotIndicatorMessage.js | 8 +++++++- src/components/DotIndicatorMessageWithClose.js | 8 +------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/DotIndicatorMessage.js b/src/components/DotIndicatorMessage.js index 1073f8c9aedc..ac550f34de3f 100644 --- a/src/components/DotIndicatorMessage.js +++ b/src/components/DotIndicatorMessage.js @@ -10,7 +10,13 @@ import Text from './Text'; import * as Localize from '../libs/Localize'; const propTypes = { - // The error messages to display + /** + * 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.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))])), // The type of message, 'error' shows a red dot, 'success' shows a green dot diff --git a/src/components/DotIndicatorMessageWithClose.js b/src/components/DotIndicatorMessageWithClose.js index fd535475583f..7626a956489f 100644 --- a/src/components/DotIndicatorMessageWithClose.js +++ b/src/components/DotIndicatorMessageWithClose.js @@ -14,13 +14,7 @@ import stylePropTypes from '../styles/stylePropTypes'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; 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', - * } - */ + // The error messages to display messages: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))])), // The type of message, 'error' shows a red dot, 'success' shows a green dot From 1a9d3284a1eaf165b1119d5bbc50231747a494e3 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 7 Aug 2023 17:10:30 -0700 Subject: [PATCH 011/254] Update with native speaker translations --- src/languages/es.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/es.js b/src/languages/es.js index 3e2d5b820f9e..8592f55d998c 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -1184,8 +1184,8 @@ export default { cannotRemove: 'No puedes eliminarte ni a ti mismo ni al dueño del espacio de trabajo.', genericRemove: 'Ha ocurrido un problema al eliminar al miembro del espacio de trabajo.', }, - addedWithPrimary: 'Se agregaron algunos usuarios con sus inicios de sesión principales.', - invitedBySecondaryLogin: ({secondaryLogin}) => `Agregado por inicio de sesión secundario ${secondaryLogin}.`, + addedWithPrimary: 'Se agregaron algunos usuarios con sus nombres de usuario principales.', + invitedBySecondaryLogin: ({secondaryLogin}) => `Agregado por nombre de usuario secundario ${secondaryLogin}.`, }, card: { header: 'Desbloquea Tarjetas Expensify gratis', From cff45ee5e15adcd382908a4276a490395d1db5df Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 13 Sep 2023 13:20:40 +0800 Subject: [PATCH 012/254] fix iou CREATED and transaction action created time is the same --- src/libs/ReportUtils.js | 9 ++++++--- src/libs/actions/IOU.js | 5 ++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 3c4944ef1a5e..8239634b2a8b 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2067,6 +2067,7 @@ function getIOUReportActionMessage(iouReportID, type, total, comment, currency, * @param {Boolean} [isSendMoneyFlow] - Whether this is send money flow * @param {Object} [receipt] * @param {Boolean} [isOwnPolicyExpenseChat] - Whether this is an expense report create from the current user's policy expense chat + * @param {String} [created] - Action created time * @returns {Object} */ function buildOptimisticIOUReportAction( @@ -2082,6 +2083,7 @@ function buildOptimisticIOUReportAction( isSendMoneyFlow = false, receipt = {}, isOwnPolicyExpenseChat = false, + created = DateUtils.getDBTime(), ) { const IOUReportID = iouReportID || generateReportID(); @@ -2139,7 +2141,7 @@ function buildOptimisticIOUReportAction( ], reportActionID: NumberUtils.rand64(), shouldShow: true, - created: DateUtils.getDBTime(), + created, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, receipt, whisperedToAccountIDs: !_.isEmpty(receipt) ? [currentUserAccountID] : [], @@ -2400,9 +2402,10 @@ function buildOptimisticChatReport( /** * Returns the necessary reportAction onyx data to indicate that the chat has been created optimistically * @param {String} emailCreatingAction + * @param {String} [created] - Action created time * @returns {Object} */ -function buildOptimisticCreatedReportAction(emailCreatingAction) { +function buildOptimisticCreatedReportAction(emailCreatingAction, created = DateUtils.getDBTime()) { return { reportActionID: NumberUtils.rand64(), actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, @@ -2429,7 +2432,7 @@ function buildOptimisticCreatedReportAction(emailCreatingAction) { ], automatic: false, avatar: lodashGet(allPersonalDetails, [currentUserAccountID, 'avatar'], UserUtils.getDefaultAvatar(currentUserAccountID)), - created: DateUtils.getDBTime(), + created, shouldShow: true, }; } diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 5b79fb6ad4bb..7ea1fbd1dff6 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -459,8 +459,9 @@ function getMoneyRequestInformation( // 3. IOU action for the iouReport // 4. REPORTPREVIEW action for the chatReport // Note: The CREATED action for the IOU report must be optimistically generated before the IOU action so there's no chance that it appears after the IOU action in the chat + const currentTime = DateUtils.getDBTime(); const optimisticCreatedActionForChat = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail); - const optimisticCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail); + const optimisticCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail, DateUtils.subtractMillisecondsFromDateTime(currentTime, 1)); const iouAction = ReportUtils.buildOptimisticIOUReportAction( CONST.IOU.REPORT_ACTION_TYPE.CREATE, amount, @@ -473,6 +474,8 @@ function getMoneyRequestInformation( false, false, receiptObject, + false, + currentTime, ); let reportPreviewAction = isNewIOUReport ? null : ReportActionsUtils.getReportPreviewAction(chatReport.reportID, iouReport.reportID); From d7a37cd79e82e263baf58e1053cdd3aec79fee5f Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 13 Sep 2023 13:32:46 +0800 Subject: [PATCH 013/254] fix iou CREATED and transaction action created time is the same --- src/libs/actions/IOU.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 7ea1fbd1dff6..a0787cfaca84 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -875,8 +875,9 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco // 3. IOU action for the iouReport // 4. REPORTPREVIEW action for the chatReport // Note: The CREATED action for the IOU report must be optimistically generated before the IOU action so there's no chance that it appears after the IOU action in the chat + const currentTime = DateUtils.getDBTime(); const oneOnOneCreatedActionForChat = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit); - const oneOnOneCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit); + const oneOnOneCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit, DateUtils.subtractMillisecondsFromDateTime(currentTime, 1)); const oneOnOneIOUAction = ReportUtils.buildOptimisticIOUReportAction( CONST.IOU.REPORT_ACTION_TYPE.CREATE, splitAmount, @@ -886,6 +887,11 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco oneOnOneTransaction.transactionID, '', oneOnOneIOUReport.reportID, + undefined, + undefined, + undefined, + undefined, + currentTime, ); // Add optimistic personal details for new participants From 702c960fb6160656aa20eeb563a1392942660461 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 13 Sep 2023 14:20:29 +0800 Subject: [PATCH 014/254] update test --- tests/actions/IOUTest.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/actions/IOUTest.js b/tests/actions/IOUTest.js index afb06cdb6fb3..902b669b287a 100644 --- a/tests/actions/IOUTest.js +++ b/tests/actions/IOUTest.js @@ -92,7 +92,7 @@ describe('actions/IOU', () => { iouAction = iouActions[0]; // The CREATED action should not be created after the IOU action - expect(Date.parse(createdAction.created)).toBeLessThanOrEqual(Date.parse(iouAction.created)); + expect(Date.parse(createdAction.created)).toBeLessThan(Date.parse(iouAction.created)); // The IOUReportID should be correct expect(iouAction.originalMessage.IOUReportID).toBe(iouReportID); @@ -199,6 +199,7 @@ describe('actions/IOU', () => { }; let iouReportID; let iouAction; + let iouCreatedAction; let transactionID; fetch.pause(); return Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, chatReport) @@ -247,10 +248,11 @@ describe('actions/IOU', () => { // The chat report should have a CREATED and an IOU action expect(_.size(allIOUReportActions)).toBe(2); + iouCreatedAction = _.find(allIOUReportActions, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED); iouAction = _.find(allIOUReportActions, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); // The CREATED action should not be created after the IOU action - expect(Date.parse(createdAction.created)).toBeLessThanOrEqual(Date.parse(iouAction.created)); + expect(Date.parse(iouCreatedAction.created)).toBeLessThan(Date.parse(iouAction.created)); // The IOUReportID should be correct expect(iouAction.originalMessage.IOUReportID).toBe(iouReportID); @@ -582,7 +584,7 @@ describe('actions/IOU', () => { iouAction = iouActions[0]; // The CREATED action should not be created after the IOU action - expect(Date.parse(createdAction.created)).toBeLessThanOrEqual(Date.parse(iouAction.created)); + expect(Date.parse(createdAction.created)).toBeLessThan(Date.parse(iouAction.created)); // The IOUReportID should be correct expect(iouAction.originalMessage.IOUReportID).toBe(iouReportID); @@ -994,17 +996,19 @@ describe('actions/IOU', () => { // Carlos DM should have two reportActions – the existing CREATED action and a pending IOU action expect(_.size(carlosReportActions)).toBe(2); + carlosIOUCreatedAction = _.find(carlosReportActions, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED); carlosIOUAction = _.find(carlosReportActions, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); expect(carlosIOUAction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); expect(carlosIOUAction.originalMessage.IOUReportID).toBe(carlosIOUReport.reportID); expect(carlosIOUAction.originalMessage.amount).toBe(amount / 4); expect(carlosIOUAction.originalMessage.comment).toBe(comment); expect(carlosIOUAction.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE); - expect(Date.parse(carlosCreatedAction.created)).toBeLessThanOrEqual(Date.parse(carlosIOUAction.created)); + expect(Date.parse(carlosIOUCreatedAction.created)).toBeLessThan(Date.parse(carlosIOUAction.created)); // Jules DM should have three reportActions, the existing CREATED action, the existing IOU action, and a new pending IOU action expect(_.size(julesReportActions)).toBe(3); expect(julesReportActions[julesCreatedAction.reportActionID]).toStrictEqual(julesCreatedAction); + julesIOUCreatedAction = _.find(julesReportActions, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED); julesIOUAction = _.find( julesReportActions, (reportAction) => @@ -1015,7 +1019,7 @@ describe('actions/IOU', () => { expect(julesIOUAction.originalMessage.amount).toBe(amount / 4); expect(julesIOUAction.originalMessage.comment).toBe(comment); expect(julesIOUAction.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE); - expect(Date.parse(julesCreatedAction.created)).toBeLessThanOrEqual(Date.parse(julesIOUAction.created)); + expect(Date.parse(julesIOUCreatedAction.created)).toBeLessThan(Date.parse(julesIOUAction.created)); // Vit DM should have two reportActions – a pending CREATED action and a pending IOU action expect(_.size(vitReportActions)).toBe(2); @@ -1027,7 +1031,7 @@ describe('actions/IOU', () => { expect(vitIOUAction.originalMessage.amount).toBe(amount / 4); expect(vitIOUAction.originalMessage.comment).toBe(comment); expect(vitIOUAction.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE); - expect(Date.parse(vitCreatedAction.created)).toBeLessThanOrEqual(Date.parse(vitIOUAction.created)); + expect(Date.parse(vitCreatedAction.created)).toBeLessThan(Date.parse(vitIOUAction.created)); // Group chat should have two reportActions – a pending CREATED action and a pending IOU action w/ type SPLIT expect(_.size(groupReportActions)).toBe(2); From 9548e2a531605a72cc14ed6f39569032525c37bc Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 13 Sep 2023 14:26:41 +0800 Subject: [PATCH 015/254] fix var is not defined --- tests/actions/IOUTest.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/actions/IOUTest.js b/tests/actions/IOUTest.js index 902b669b287a..683865a4058a 100644 --- a/tests/actions/IOUTest.js +++ b/tests/actions/IOUTest.js @@ -851,9 +851,11 @@ describe('actions/IOU', () => { let carlosIOUReport; let carlosIOUAction; + let carlosIOUCreatedAction; let carlosTransaction; let julesIOUAction; + let julesIOUCreatedAction; let julesTransaction; let vitChatReport; From e608638391f557facb96497e942266de89461daf Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Wed, 20 Sep 2023 23:32:53 +0200 Subject: [PATCH 016/254] Migrate policy utils lib --- src/libs/{PolicyUtils.js => PolicyUtils.ts} | 116 +++++++------------- src/types/onyx/Policy.ts | 1 + 2 files changed, 41 insertions(+), 76 deletions(-) rename src/libs/{PolicyUtils.js => PolicyUtils.ts} (50%) diff --git a/src/libs/PolicyUtils.js b/src/libs/PolicyUtils.ts similarity index 50% rename from src/libs/PolicyUtils.js rename to src/libs/PolicyUtils.ts index 164f284a4ef5..1f2abfa2b7a8 100644 --- a/src/libs/PolicyUtils.js +++ b/src/libs/PolicyUtils.ts @@ -1,72 +1,56 @@ -import _ from 'underscore'; -import lodashGet from 'lodash/get'; import Str from 'expensify-common/lib/str'; import CONST from '../CONST'; import ONYXKEYS from '../ONYXKEYS'; +import * as OnyxTypes from '../types/onyx'; + +type PolicyMemberList = Record; +type PolicyMembersCollection = Record; +type MemberEmailsToAccountIDs = Record; +type PersonalDetailsList = Record; /** * Filter out the active policies, which will exclude policies with pending deletion - * @param {Object} policies - * @returns {Array} */ -function getActivePolicies(policies) { - return _.filter(policies, (policy) => policy && policy.isPolicyExpenseChatEnabled && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); +function getActivePolicies(policies: OnyxTypes.Policy[]): OnyxTypes.Policy[] { + return policies.filter((policy) => policy?.isPolicyExpenseChatEnabled && policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); } /** * Checks if we have any errors stored within the POLICY_MEMBERS. Determines whether we should show a red brick road error or not. * Data structure: {accountID: {role:'user', errors: []}, accountID2: {role:'admin', errors: [{1231312313: 'Unable to do X'}]}, ...} - * - * @param {Object} policyMembers - * @returns {Boolean} */ -function hasPolicyMemberError(policyMembers) { - return _.some(policyMembers, (member) => !_.isEmpty(member.errors)); +function hasPolicyMemberError(policyMembers: PolicyMemberList): boolean { + return Object.values(policyMembers).some((member) => Object.keys(member?.errors ?? {}).length > 0); } /** * Check if the policy has any error fields. - * - * @param {Object} policy - * @param {Object} policy.errorFields - * @return {Boolean} */ -function hasPolicyErrorFields(policy) { - return _.some(lodashGet(policy, 'errorFields', {}), (fieldErrors) => !_.isEmpty(fieldErrors)); +function hasPolicyErrorFields(policy: OnyxTypes.Policy): boolean { + return Object.keys(policy?.errorFields ?? {}).some((fieldErrors) => Object.keys(fieldErrors).length > 0); } /** * Check if the policy has any errors, and if it doesn't, then check if it has any error fields. - * - * @param {Object} policy - * @param {Object} policy.errors - * @param {Object} policy.errorFields - * @return {Boolean} */ -function hasPolicyError(policy) { - return !_.isEmpty(lodashGet(policy, 'errors', {})) ? true : hasPolicyErrorFields(policy); +function hasPolicyError(policy: OnyxTypes.Policy): boolean { + return Object.keys(policy?.errors ?? {}).length > 0 ? true : hasPolicyErrorFields(policy); } /** * Checks if we have any errors stored within the policy custom units. - * - * @param {Object} policy - * @returns {Boolean} */ -function hasCustomUnitsError(policy) { - return !_.isEmpty(_.pick(lodashGet(policy, 'customUnits', {}), 'errors')); +function hasCustomUnitsError(policy: OnyxTypes.Policy): boolean { + return Object.keys(policy?.customUnits?.error ?? {}).length > 0; } /** * Get the brick road indicator status for a policy. The policy has an error status if there is a policy member error, a custom unit error or a field error. - * - * @param {Object} policy - * @param {String} policy.id - * @param {Object} policyMembersCollection - * @returns {String} */ -function getPolicyBrickRoadIndicatorStatus(policy, policyMembersCollection) { - const policyMembers = lodashGet(policyMembersCollection, `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policy.id}`, {}); +function getPolicyBrickRoadIndicatorStatus(policy: OnyxTypes.Policy, policyMembersCollection: PolicyMembersCollection): string { + if (!(`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policy?.id}` in policyMembersCollection)) return ''; + + const policyMembers = policyMembersCollection[`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policy?.id}`]; if (hasPolicyMemberError(policyMembers) || hasCustomUnitsError(policy) || hasPolicyErrorFields(policy)) { return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; } @@ -79,84 +63,64 @@ function getPolicyBrickRoadIndicatorStatus(policy, policyMembersCollection) { * If online, show the policy pending deletion only if there is an error. * Note: Using a local ONYXKEYS.NETWORK subscription will cause a delay in * updating the screen. Passing the offline status from the component. - * @param {Object} policy - * @param {Boolean} isOffline - * @returns {Boolean} */ -function shouldShowPolicy(policy, isOffline) { +function shouldShowPolicy(policy: OnyxTypes.Policy, isOffline: boolean): boolean { return ( policy && - policy.isPolicyExpenseChatEnabled && - policy.role === CONST.POLICY.ROLE.ADMIN && - (isOffline || policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !_.isEmpty(policy.errors)) + policy?.isPolicyExpenseChatEnabled && + policy?.role === CONST.POLICY.ROLE.ADMIN && + (isOffline || policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policy?.errors).length > 0) ); } -/** - * @param {string} email - * @returns {boolean} - */ -function isExpensifyTeam(email) { +function isExpensifyTeam(email: string): boolean { const emailDomain = Str.extractEmailDomain(email); return emailDomain === CONST.EXPENSIFY_PARTNER_NAME || emailDomain === CONST.EMAIL.GUIDES_DOMAIN; } -/** - * @param {string} email - * @returns {boolean} - */ -function isExpensifyGuideTeam(email) { +function isExpensifyGuideTeam(email: string): boolean { const emailDomain = Str.extractEmailDomain(email); return emailDomain === CONST.EMAIL.GUIDES_DOMAIN; } /** * Checks if the current user is an admin of the policy. - * - * @param {Object} policy - * @returns {Boolean} */ -const isPolicyAdmin = (policy) => lodashGet(policy, 'role') === CONST.POLICY.ROLE.ADMIN; +const isPolicyAdmin = (policy: OnyxTypes.Policy): boolean => policy?.role === CONST.POLICY.ROLE.ADMIN; /** - * @param {Object} policyMembers - * @param {Object} personalDetails - * @returns {Object} - * * Create an object mapping member emails to their accountIDs. Filter for members without errors, and get the login email from the personalDetail object using the accountID. * * We only return members without errors. Otherwise, the members with errors would immediately be removed before the user has a chance to read the error. */ -function getMemberAccountIDsForWorkspace(policyMembers, personalDetails) { - const memberEmailsToAccountIDs = {}; - _.each(policyMembers, (member, accountID) => { - if (!_.isEmpty(member.errors)) { +function getMemberAccountIDsForWorkspace(policyMembers: PolicyMemberList, personalDetails: PersonalDetailsList): MemberEmailsToAccountIDs { + const memberEmailsToAccountIDs: MemberEmailsToAccountIDs = {}; + Object.keys(policyMembers).forEach((accountID) => { + const member = policyMembers[accountID]; + if (Object.keys(member?.errors ?? {}).length > 0) { return; } const personalDetail = personalDetails[accountID]; - if (!personalDetail || !personalDetail.login) { + if (!personalDetail?.login) { return; } - memberEmailsToAccountIDs[personalDetail.login] = accountID; + memberEmailsToAccountIDs[personalDetail?.login] = accountID; }); return memberEmailsToAccountIDs; } /** * Get login list that we should not show in the workspace invite options - * - * @param {Object} policyMembers - * @param {Object} personalDetails - * @returns {Array} */ -function getIneligibleInvitees(policyMembers, personalDetails) { - const memberEmailsToExclude = [...CONST.EXPENSIFY_EMAILS]; - _.each(policyMembers, (policyMember, accountID) => { +function getIneligibleInvitees(policyMembers: PolicyMemberList, personalDetails: PersonalDetailsList): string[] { + const memberEmailsToExclude: string[] = [...CONST.EXPENSIFY_EMAILS]; + Object.keys(policyMembers).forEach((accountID) => { + const policyMember = policyMembers[accountID]; // Policy members that are pending delete or have errors are not valid and we should show them in the invite options (don't exclude them). - if (policyMember.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !_.isEmpty(policyMember.errors)) { + if (policyMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policyMember?.errors ?? {}).length > 0) { return; } - const memberEmail = lodashGet(personalDetails, `[${accountID}].login`); + const memberEmail = personalDetails[accountID]?.login; if (!memberEmail) { return; } diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index cacbb5d15199..10ee569c93e6 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -32,6 +32,7 @@ type Policy = { isFromFullPolicy?: boolean; lastModified?: string; customUnits?: Record; + isPolicyExpenseChatEnabled: boolean; }; export default Policy; From 45b90544c39d8bdaa5c0fdaea999d3a3d95f31e1 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Thu, 21 Sep 2023 13:07:56 +0800 Subject: [PATCH 017/254] boilerplate for changes --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/actions/Report.js | 36 +++++++++++++++++++ .../report/ContextMenu/ContextMenuActions.js | 28 +++++++++++++++ 4 files changed, 66 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index 1882b7e97afb..30f6b0c68f8e 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -396,6 +396,7 @@ export default { deleteConfirmation: ({action}: DeleteConfirmationParams) => `Are you sure you want to delete this ${ReportActionsUtils.isMoneyRequestAction(action) ? 'request' : 'comment'}?`, onlyVisible: 'Only visible to', replyInThread: 'Reply in thread', + subscribeToThread: 'Subscribe to thread', flagAsOffensive: 'Flag as offensive', }, emojiReactions: { diff --git a/src/languages/es.ts b/src/languages/es.ts index 87c7a19fed8a..f2786057e107 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -387,6 +387,7 @@ export default { deleteConfirmation: ({action}: DeleteConfirmationParams) => `¿Estás seguro de que quieres eliminar este ${ReportActionsUtils.isMoneyRequestAction(action) ? 'pedido' : 'comentario'}`, onlyVisible: 'Visible sólo para', replyInThread: 'Responder en el hilo', + subscribeToThread: 'NEED TO TRANSLATE', flagAsOffensive: 'Marcar como ofensivo', }, emojiReactions: { diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 55b03110e925..61d6df634dca 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -630,6 +630,42 @@ function navigateToAndOpenChildReport(childReportID = '0', parentReportAction = } } +/** + * This will subscribe to an existing thread, or create a new one and then subsribe to it if necessary + * + * @param {String} childReportID The reportID we are trying to open + * @param {Object} parentReportAction the parent comment of a thread + * @param {String} parentReportID The reportID of the parent + * + */ +function subscribeToChildReport(childReportID = '0', parentReportAction = {}, parentReportID = '0') { + if (childReportID !== '0') { + openReport(childReportID); + Navigation.navigate(ROUTES.getReportRoute(childReportID)); + } else { + const participantAccountIDs = _.uniq([currentUserAccountID, Number(parentReportAction.actorAccountID)]); + const parentReport = allReports[parentReportID]; + const newChat = ReportUtils.buildOptimisticChatReport( + participantAccountIDs, + lodashGet(parentReportAction, ['message', 0, 'text']), + lodashGet(parentReport, 'chatType', ''), + lodashGet(parentReport, 'policyID', CONST.POLICY.OWNER_EMAIL_FAKE), + CONST.POLICY.OWNER_ACCOUNT_ID_FAKE, + false, + '', + undefined, + undefined, + CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + parentReportAction.reportActionID, + parentReportID, + ); + + const participantLogins = PersonalDetailsUtils.getLoginsByAccountIDs(newChat.participantAccountIDs); + openReport(newChat.reportID, participantLogins, newChat, parentReportAction.reportActionID); + Navigation.navigate(ROUTES.getReportRoute(newChat.reportID)); + } +} + /** * Get the latest report history without marking the report as read. * diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 173bda0e5221..6e7256162be6 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -146,6 +146,34 @@ export default [ }, getDescription: () => {}, }, + { + isAnonymousAction: false, + textTranslateKey: 'reportActionContextMenu.subscribeToThread', + icon: Expensicons.ChatBubble, + successTextTranslateKey: '', + successIcon: null, + shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID) => { + if (type !== CONTEXT_MENU_TYPES.REPORT_ACTION) { + return false; + } + const isCommentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && !ReportUtils.isThreadFirstChat(reportAction, reportID); + const isReportPreviewAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW; + const isIOUAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && !ReportActionsUtils.isSplitBillAction(reportAction); + return isCommentAction || isReportPreviewAction || isIOUAction; + }, + onPress: (closePopover, {reportAction, reportID}) => { + if (closePopover) { + hideContextMenu(false, () => { + ReportActionComposeFocusManager.focus(); + Report.navigateToAndOpenChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID); + }); + return; + } + + Report.navigateToAndOpenChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID); + }, + getDescription: () => {}, + }, { isAnonymousAction: false, textTranslateKey: 'reportActionContextMenu.copyURLToClipboard', From 58f417f8039a4d6faab3d0ee6254582beada2fd3 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Fri, 22 Sep 2023 16:19:47 +0800 Subject: [PATCH 018/254] intermediate changes --- src/libs/actions/Report.js | 33 +++++++++++++++++-- .../report/ContextMenu/ContextMenuActions.js | 22 +++++++------ 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 61d6df634dca..c2dd3daf49b1 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -638,10 +638,11 @@ function navigateToAndOpenChildReport(childReportID = '0', parentReportAction = * @param {String} parentReportID The reportID of the parent * */ -function subscribeToChildReport(childReportID = '0', parentReportAction = {}, parentReportID = '0') { +function toggleSubscribeToChildReport(childReportID = '0', parentReportAction = {}, parentReportID = '0', prevNotificationPreference) { if (childReportID !== '0') { openReport(childReportID); - Navigation.navigate(ROUTES.getReportRoute(childReportID)); + if (prevNotificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE) + updateNotificationPreference(childReportID, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, ) } else { const participantAccountIDs = _.uniq([currentUserAccountID, Number(parentReportAction.actorAccountID)]); const parentReport = allReports[parentReportID]; @@ -1241,6 +1242,32 @@ function saveReportActionDraftNumberOfLines(reportID, reportActionID, numberOfLi Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT_NUMBER_OF_LINES}${reportID}_${reportActionID}`, numberOfLines); } +/** + * @param {String} reportID + * @param {String} previousValue + * @param {String} newValue + */ +function updateNotificationPreference(reportID, previousValue, newValue) { + if (previousValue === newValue) { + return; + } + const optimisticData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: {notificationPreference: newValue}, + }, + ]; + const failureData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: {notificationPreference: previousValue}, + }, + ]; + API.write('UpdateReportNotificationPreference', {reportID, notificationPreference: newValue}, {optimisticData, failureData}); +} + /** * @param {String} reportID * @param {String} previousValue @@ -2105,6 +2132,7 @@ export { reconnect, updateWelcomeMessage, updateWriteCapabilityAndNavigate, + updateNotificationPreference, updateNotificationPreferenceAndNavigate, subscribeToReportTypingEvents, unsubscribeFromReportChannel, @@ -2132,6 +2160,7 @@ export { navigateToAndOpenReport, navigateToAndOpenReportWithAccountIDs, navigateToAndOpenChildReport, + toggleSubscribeToChildReport, updatePolicyRoomNameAndNavigate, clearPolicyRoomNameErrors, clearIOUError, diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 6e7256162be6..61bf5abe4674 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -25,6 +25,7 @@ import * as Task from '../../../../libs/actions/Task'; import * as Localize from '../../../../libs/Localize'; import * as TransactionUtils from '../../../../libs/TransactionUtils'; import * as CurrencyUtils from '../../../../libs/CurrencyUtils'; +import Log from '../../../../libs/Log'; /** * Gets the HTML version of the message in an action. @@ -149,7 +150,7 @@ export default [ { isAnonymousAction: false, textTranslateKey: 'reportActionContextMenu.subscribeToThread', - icon: Expensicons.ChatBubble, + icon: Expensicons.Chair, successTextTranslateKey: '', successIcon: null, shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID) => { @@ -162,15 +163,16 @@ export default [ return isCommentAction || isReportPreviewAction || isIOUAction; }, onPress: (closePopover, {reportAction, reportID}) => { - if (closePopover) { - hideContextMenu(false, () => { - ReportActionComposeFocusManager.focus(); - Report.navigateToAndOpenChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID); - }); - return; - } - - Report.navigateToAndOpenChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID); + Log.info("sparsisparsi start"); + Log.info(JSON.stringify(reportAction)); + Log.info("sparsisparsi done"); + // if (closePopover) { + // hideContextMenu(false, () => { + // ReportActionComposeFocusManager.focus(); + // Report.subscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID); + // }); + // return; + // } }, getDescription: () => {}, }, From 478559309312a717b3cb7e14f85187123f2f2608 Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Wed, 27 Sep 2023 11:35:25 +0800 Subject: [PATCH 019/254] Remove the categories beta --- src/CONST.ts | 1 - src/components/MoneyRequestConfirmationList.js | 2 +- src/components/ReportActionItem/MoneyRequestView.js | 2 +- src/libs/Permissions.ts | 5 ----- 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 8aee1b9f1af4..67b62be473b5 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -238,7 +238,6 @@ const CONST = { TASKS: 'tasks', THREADS: 'threads', CUSTOM_STATUS: 'customStatus', - NEW_DOT_CATEGORIES: 'newDotCategories', NEW_DOT_TAGS: 'newDotTags', }, BUTTON_STATES: { diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 0d554ff0eca4..2a488e9810c2 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -194,7 +194,7 @@ function MoneyRequestConfirmationList(props) { 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 = isPolicyExpenseChat && OptionsListUtils.hasEnabledOptions(_.values(props.policyCategories)); // Fetches the first tag list of the policy const policyTag = PolicyUtils.getTag(props.policyTags); diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index ec91fb292257..3bd6fdc5673d 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -106,7 +106,7 @@ function MoneyRequestView({report, betas, parentReport, policyCategories, should const policyTagsList = lodashGet(policyTag, 'tags', {}); // Flags for showing categories and tags - const shouldShowCategory = isPolicyExpenseChat && Permissions.canUseCategories(betas) && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); + const shouldShowCategory = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); const shouldShowTag = isPolicyExpenseChat && Permissions.canUseTags(betas) && (transactionTag || OptionsListUtils.hasEnabledOptions(lodashValues(policyTagsList))); const shouldShowBillable = isPolicyExpenseChat && Permissions.canUseTags(betas) && (transactionBillable || !lodashGet(policy, 'disabledFields.defaultBillable', true)); diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 05322472a407..ff96717adf62 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -49,10 +49,6 @@ function canUseCustomStatus(betas: Beta[]): boolean { return betas?.includes(CONST.BETAS.CUSTOM_STATUS) || canUseAllBetas(betas); } -function canUseCategories(betas: Beta[]): boolean { - return betas?.includes(CONST.BETAS.NEW_DOT_CATEGORIES) || canUseAllBetas(betas); -} - function canUseTags(betas: Beta[]): boolean { return betas?.includes(CONST.BETAS.NEW_DOT_TAGS) || canUseAllBetas(betas); } @@ -74,7 +70,6 @@ export default { canUsePolicyRooms, canUseTasks, canUseCustomStatus, - canUseCategories, canUseTags, canUseLinkPreviews, }; From 7e4e58f3465a9fbc17ac4e752a829d17d147140b Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Wed, 27 Sep 2023 16:03:50 +0800 Subject: [PATCH 020/254] Fix JS error --- src/components/OptionRow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/OptionRow.js b/src/components/OptionRow.js index 8bc016faa6b5..66a716698f2f 100644 --- a/src/components/OptionRow.js +++ b/src/components/OptionRow.js @@ -212,7 +212,7 @@ class OptionRow extends Component { accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} hoverDimmingValue={1} hoverStyle={this.props.hoverStyle} - needsOffscreenAlphaCompositing={this.props.option.icons.length >= 2} + needsOffscreenAlphaCompositing={this.props.option.icons && this.props.option.icons.length >= 2} > From f38e0e5b64aebafcc94c0f4c06b2f3c46dbe5ee4 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 27 Sep 2023 11:09:34 +0200 Subject: [PATCH 021/254] Initial work --- src/ONYXKEYS.ts | 2 +- src/libs/ReportActionsUtils.js | 2 +- src/libs/{SidebarUtils.js => SidebarUtils.ts} | 63 ++++++++----------- 3 files changed, 29 insertions(+), 38 deletions(-) rename src/libs/{SidebarUtils.js => SidebarUtils.ts} (91%) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 6649a33fe15e..51c5fb202f00 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -380,7 +380,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: Record; [ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report; - [ONYXKEYS.COLLECTION.REPORT_ACTIONS]: OnyxTypes.ReportAction; + [ONYXKEYS.COLLECTION.REPORT_ACTIONS]: Record; [ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS]: string; [ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS]: OnyxTypes.ReportActionReactions; [ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT]: string; diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 67c44784eeb2..142f072728b2 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -334,7 +334,7 @@ function isReportActionDeprecated(reportAction, key) { * and supported type, it's not deleted and also not closed. * * @param {Object} reportAction - * @param {String} key + * @param {String | Number} key * @returns {Boolean} */ function shouldReportActionBeVisible(reportAction, key) { diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.ts similarity index 91% rename from src/libs/SidebarUtils.js rename to src/libs/SidebarUtils.ts index df676f23ebc7..e1251f9f50bc 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.ts @@ -13,9 +13,11 @@ import * as CollectionUtils from './CollectionUtils'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as UserUtils from './UserUtils'; import * as PersonalDetailsUtils from './PersonalDetailsUtils'; +import ReportAction from '../types/onyx/ReportAction'; + +const visibleReportActionItems: Record = {}; +const lastReportActions: Record = {}; -const visibleReportActionItems = {}; -const lastReportActions = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, callback: (actions, key) => { @@ -24,38 +26,37 @@ Onyx.connect({ } const reportID = CollectionUtils.extractCollectionItemID(key); - const actionsArray = ReportActionsUtils.getSortedReportActions(_.toArray(actions)); - lastReportActions[reportID] = _.last(actionsArray); + const actionsArray: ReportAction[] = ReportActionsUtils.getSortedReportActions(Object.values(actions)); + lastReportActions[reportID] = actionsArray[actionsArray.length - 1]; // The report is only visible if it is the last action not deleted that // does not match a closed or created state. - const reportActionsForDisplay = _.filter( - actionsArray, + const reportActionsForDisplay = actionsArray.filter( (reportAction, actionKey) => ReportActionsUtils.shouldReportActionBeVisible(reportAction, actionKey) && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED && reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, ); - visibleReportActionItems[reportID] = _.last(reportActionsForDisplay); + visibleReportActionItems[reportID] = reportActionsForDisplay[reportActionsForDisplay.length - 1]; }, }); // Session can remain stale because the only way for the current user to change is to // sign out and sign in, which would clear out all the Onyx // data anyway and cause SidebarLinks to rerender. -let currentUserAccountID; +let currentUserAccountID: number | undefined; Onyx.connect({ key: ONYXKEYS.SESSION, - callback: (val) => { - if (!val) { + callback: (session) => { + if (!session) { return; } - currentUserAccountID = val.accountID; + currentUserAccountID = session.accountID; }, }); -let resolveSidebarIsReadyPromise; +let resolveSidebarIsReadyPromise: (args?: unknown[]) => void; let sidebarIsReadyPromise = new Promise((resolve) => { resolveSidebarIsReadyPromise = resolve; @@ -71,11 +72,11 @@ function isSidebarLoadedReady() { return sidebarIsReadyPromise; } -function compareStringDates(stringA, stringB) { - if (stringA < stringB) { +function compareStringDates(a: string, b: string) { + if (a < b) { return -1; } - if (stringA > stringB) { + if (a > b) { return 1; } return 0; @@ -89,7 +90,7 @@ function setIsSidebarLoadedReady() { const reportIDsCache = new Map(); // Function to set a key-value pair while maintaining the maximum key limit -function setWithLimit(map, key, value) { +function setWithLimit(map: Map, key: TKey, value: TValue) { if (map.size >= 5) { // If the map has reached its limit, remove the first (oldest) key-value pair const firstKey = map.keys().next().value; @@ -102,18 +103,11 @@ function setWithLimit(map, key, value) { let hasInitialReportActions = false; /** - * @param {String} currentReportId - * @param {Object} allReportsDict - * @param {Object} betas - * @param {String[]} policies - * @param {String} priorityMode - * @param {Object} allReportActions - * @returns {String[]} An array of reportIDs sorted in the proper order + * @returns An array of reportIDs sorted in the proper order */ function getOrderedReportIDs(currentReportId, allReportsDict, betas, policies, priorityMode, allReportActions) { // Generate a unique cache key based on the function arguments const cachedReportsKey = JSON.stringify( - // eslint-disable-next-line es/no-optional-chaining [currentReportId, allReportsDict, betas, policies, priorityMode, allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.length || 1], (key, value) => { /** @@ -216,13 +210,13 @@ function getOrderedReportIDs(currentReportId, allReportsDict, betas, policies, p /** * Gets all the data necessary for rendering an OptionRowLHN component * - * @param {Object} report - * @param {Object} reportActions - * @param {Object} personalDetails - * @param {String} preferredLocale - * @param {Object} [policy] - * @param {Object} parentReportAction - * @returns {Object} + * @param report + * @param reportActions + * @param personalDetails + * @param preferredLocale + * @param [policy] + * @param parentReportAction + * @returns */ function getOptionData(report, reportActions, personalDetails, preferredLocale, policy, parentReportAction) { // When a user signs out, Onyx is cleared. Due to the lazy rendering with a virtual list, it's possible for @@ -331,14 +325,11 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, } : null; } - let lastMessageText = - hasMultipleParticipants && lastActorDetails && lastActorDetails.accountID && Number(lastActorDetails.accountID) !== currentUserAccountID ? `${lastActorDetails.displayName}: ` : ''; + let lastMessageText = hasMultipleParticipants && lastActorDetails?.accountID && Number(lastActorDetails.accountID) !== currentUserAccountID ? `${lastActorDetails.displayName}: ` : ''; lastMessageText += report ? lastMessageTextFromReport : ''; if (result.isArchivedRoom) { - const archiveReason = - (lastReportActions[report.reportID] && lastReportActions[report.reportID].originalMessage && lastReportActions[report.reportID].originalMessage.reason) || - CONST.REPORT.ARCHIVE_REASON.DEFAULT; + const archiveReason = lastReportActions[report.reportID]?.originalMessage?.reason || CONST.REPORT.ARCHIVE_REASON.DEFAULT; lastMessageText = Localize.translate(preferredLocale, `reportArchiveReasons.${archiveReason}`, { displayName: archiveReason.displayName || PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails, 'displayName'), policyName: ReportUtils.getPolicyName(report, false, policy), From 0b7a83e8edd8e44971226f86f55c81140b20fc76 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Thu, 28 Sep 2023 18:22:10 +0800 Subject: [PATCH 022/254] icons and frontend wip --- assets/images/bell.svg | 3 ++ assets/images/bellSlash.svg | 3 ++ ios/NewExpensify.xcodeproj/project.pbxproj | 18 +++++++- ios/tmp.xcconfig | 10 ++++- src/components/Icon/Expensicons.js | 4 ++ src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/actions/Report.js | 1 + .../report/ContextMenu/ContextMenuActions.js | 43 +++++++++++++++++-- 9 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 assets/images/bell.svg create mode 100644 assets/images/bellSlash.svg diff --git a/assets/images/bell.svg b/assets/images/bell.svg new file mode 100644 index 000000000000..a53c9508cbd6 --- /dev/null +++ b/assets/images/bell.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/bellSlash.svg b/assets/images/bellSlash.svg new file mode 100644 index 000000000000..2cacb07f4268 --- /dev/null +++ b/assets/images/bellSlash.svg @@ -0,0 +1,3 @@ + + + diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 64ed3fda8b02..d1cd2d066833 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -29,7 +29,7 @@ 70CF6E82262E297300711ADC /* BootSplash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 70CF6E81262E297300711ADC /* BootSplash.storyboard */; }; BDB853621F354EBB84E619C2 /* ExpensifyNewKansas-MediumItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = D2AFB39EC1D44BF9B91D3227 /* ExpensifyNewKansas-MediumItalic.otf */; }; DD79042B2792E76D004484B4 /* RCTBootSplash.m in Sources */ = {isa = PBXBuildFile; fileRef = DD79042A2792E76D004484B4 /* RCTBootSplash.m */; }; - E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; + E51DC681C7DEE40AEBDDFBFE /* (null) in Frameworks */ = {isa = PBXBuildFile; }; E9DF872D2525201700607FDC /* AirshipConfig.plist in Resources */ = {isa = PBXBuildFile; fileRef = E9DF872C2525201700607FDC /* AirshipConfig.plist */; }; ED222ED90E074A5481A854FA /* ExpensifyNeue-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 8B28D84EF339436DBD42A203 /* ExpensifyNeue-BoldItalic.otf */; }; F0C450EA2705020500FD2970 /* colors.json in Resources */ = {isa = PBXBuildFile; fileRef = F0C450E92705020500FD2970 /* colors.json */; }; @@ -124,7 +124,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */, + E51DC681C7DEE40AEBDDFBFE /* (null) in Frameworks */, 5A464BC8112CDB1DE1E38F1C /* libPods-NewExpensify.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -722,9 +722,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = NewExpensify/Chat.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 368M544MTT; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 368M544MTT; ENABLE_BITCODE = NO; INFOPLIST_FILE = "$(SRCROOT)/NewExpensify/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -738,6 +740,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev; PRODUCT_NAME = "New Expensify Dev"; PROVISIONING_PROFILE_SPECIFIER = expensify_chat_dev; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = expensify_chat_dev; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -754,9 +757,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = NewExpensify/Chat.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 368M544MTT; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 368M544MTT; INFOPLIST_FILE = NewExpensify/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -769,6 +774,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev; PRODUCT_NAME = "New Expensify Dev"; PROVISIONING_PROFILE_SPECIFIER = expensify_chat_dev; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = expensify_chat_dev; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -976,6 +982,7 @@ CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 368M544MTT; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 368M544MTT; ENABLE_BITCODE = NO; INFOPLIST_FILE = "$(SRCROOT)/NewExpensify/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -989,6 +996,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.chat.expensify.chat; PRODUCT_NAME = "New Expensify"; PROVISIONING_PROFILE_SPECIFIER = chat_expensify_appstore; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = chat_expensify_appstore; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1098,6 +1106,7 @@ CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 368M544MTT; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 368M544MTT; ENABLE_BITCODE = NO; INFOPLIST_FILE = "$(SRCROOT)/NewExpensify/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -1111,6 +1120,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.adhoc; PRODUCT_NAME = "New Expensify AdHoc"; PROVISIONING_PROFILE_SPECIFIER = chat_expensify_appstore; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = expensify_chat_adhoc; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1214,6 +1224,7 @@ CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 368M544MTT; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 368M544MTT; INFOPLIST_FILE = NewExpensify/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1226,6 +1237,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.chat.expensify.chat; PRODUCT_NAME = "New Expensify"; PROVISIONING_PROFILE_SPECIFIER = chat_expensify_appstore; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = chat_expensify_appstore; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -1326,6 +1338,7 @@ CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 368M544MTT; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 368M544MTT; INFOPLIST_FILE = NewExpensify/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1338,6 +1351,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.adhoc; PRODUCT_NAME = "New Expensify AdHoc"; PROVISIONING_PROFILE_SPECIFIER = chat_expensify_appstore; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = expensify_chat_adhoc; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; diff --git a/ios/tmp.xcconfig b/ios/tmp.xcconfig index 8b137891791f..2f2502669450 100644 --- a/ios/tmp.xcconfig +++ b/ios/tmp.xcconfig @@ -1 +1,9 @@ - +NEW_EXPENSIFY_URL=https:/$()/new.expensify.com/ +SECURE_EXPENSIFY_URL=https:/$()/secure.expensify.com/ +EXPENSIFY_URL=https:/$()/www.expensify.com/ +EXPENSIFY_PARTNER_NAME=chat-expensify-com +EXPENSIFY_PARTNER_PASSWORD=e21965746fd75f82bb66 +PUSHER_APP_KEY=268df511a204fbb60884 +USE_WEB_PROXY=false +ENVIRONMENT=production +SEND_CRASH_REPORTS=true diff --git a/src/components/Icon/Expensicons.js b/src/components/Icon/Expensicons.js index a0c8b72d755a..0eea0ca7398c 100644 --- a/src/components/Icon/Expensicons.js +++ b/src/components/Icon/Expensicons.js @@ -9,6 +9,8 @@ import ArrowRightLong from '../../../assets/images/arrow-right-long.svg'; import ArrowsUpDown from '../../../assets/images/arrows-updown.svg'; import BackArrow from '../../../assets/images/back-left.svg'; import Bank from '../../../assets/images/bank.svg'; +import Bell from '../../../assets/images/bell.svg'; +import BellSlash from '../../../assets/images/bellSlash.svg'; import Bill from '../../../assets/images/bill.svg'; import Bolt from '../../../assets/images/bolt.svg'; import Briefcase from '../../../assets/images/briefcase.svg'; @@ -138,6 +140,8 @@ export { BackArrow, Bank, Bill, + Bell, + BellSlash, Bolt, Briefcase, Bug, diff --git a/src/languages/en.ts b/src/languages/en.ts index 53b9bab98ced..e038acf7a862 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -400,6 +400,7 @@ export default { onlyVisible: 'Only visible to', replyInThread: 'Reply in thread', subscribeToThread: 'Subscribe to thread', + unsubscribeFromThread: 'Unsubscribe from thread', flagAsOffensive: 'Flag as offensive', }, emojiReactions: { diff --git a/src/languages/es.ts b/src/languages/es.ts index 4eead6ddc0f3..c577e8f4b7ce 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -391,6 +391,7 @@ export default { onlyVisible: 'Visible sólo para', replyInThread: 'Responder en el hilo', subscribeToThread: 'NEED TO TRANSLATE', + unsubscribeFromThread: 'NEED TO TRANSLATE', flagAsOffensive: 'Marcar como ofensivo', }, emojiReactions: { diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index ecb98548d5e1..4d9b74a5a69f 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -706,6 +706,7 @@ function navigateToAndOpenChildReport(childReportID = '0', parentReportAction = * @param {String} childReportID The reportID we are trying to open * @param {Object} parentReportAction the parent comment of a thread * @param {String} parentReportID The reportID of the parent + * @param {String} prevNotificationPreference The previous notification preference for the child report * */ function toggleSubscribeToChildReport(childReportID = '0', parentReportAction = {}, parentReportID = '0', prevNotificationPreference) { diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 1e603e40a99e..3fd39ddf25c0 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -138,7 +138,7 @@ export default [ if (closePopover) { hideContextMenu(false, () => { ReportActionComposeFocusManager.focus(); - Report.navigateToAndOpenChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID); + Report.navigateToAndOpenChildReport(lodashGet(reportAction, 'childReportID', ''), reportAction, reportID); }); return; } @@ -150,22 +150,57 @@ export default [ { isAnonymousAction: false, textTranslateKey: 'reportActionContextMenu.subscribeToThread', - icon: Expensicons.Chair, + // textTranslateKey: lodashGet(reportAction, 'childReportNotificationPreference', '0'), + icon: Expensicons.Bell, + successTextTranslateKey: '', + successIcon: null,g + shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID) => { + const subscribed = lodashGet(reportAction, 'childReportNotificationPreference', '') !== "hidden"; + if (type !== CONTEXT_MENU_TYPES.REPORT_ACTION) { + return false; + } + const isCommentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && !ReportUtils.isThreadFirstChat(reportAction, reportID); + const isReportPreviewAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW; + const isIOUAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && !ReportActionsUtils.isSplitBillAction(reportAction); + return !subscribed && (isCommentAction || isReportPreviewAction || isIOUAction); + }, + onPress: (closePopover, {reportAction, reportID}) => { + Log.info("sparsisparsi start"); + Log.info(lodashGet(reportAction, 'childReportNotificationPreference', '0')); + Log.info("sparsisparsi done"); + debugger; + // if (closePopover) { + // hideContextMenu(false, () => { + // ReportActionComposeFocusManager.focus(); + // Report.subscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID); + // }); + // return; + // } + }, + getDescription: () => {}, + }, + { + isAnonymousAction: false, + textTranslateKey: 'reportActionContextMenu.unsubscribeFromThread', + // textTranslateKey: lodashGet(reportAction, 'childReportNotificationPreference', '0'), + icon: Expensicons.BellSlash, successTextTranslateKey: '', successIcon: null, shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID) => { + const subscribed = lodashGet(reportAction, 'childReportNotificationPreference', '0') !== "hidden"; if (type !== CONTEXT_MENU_TYPES.REPORT_ACTION) { return false; } const isCommentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && !ReportUtils.isThreadFirstChat(reportAction, reportID); const isReportPreviewAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW; const isIOUAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && !ReportActionsUtils.isSplitBillAction(reportAction); - return isCommentAction || isReportPreviewAction || isIOUAction; + return subscribed && (isCommentAction || isReportPreviewAction || isIOUAction); }, onPress: (closePopover, {reportAction, reportID}) => { Log.info("sparsisparsi start"); - Log.info(JSON.stringify(reportAction)); + Log.info(lodashGet(reportAction, 'childReportNotificationPreference', '0')); Log.info("sparsisparsi done"); + debugger; // if (closePopover) { // hideContextMenu(false, () => { // ReportActionComposeFocusManager.focus(); From 8113ebf244d214dc2e9d9d02ebb8d93fb88377a1 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Fri, 29 Sep 2023 12:24:07 +0200 Subject: [PATCH 023/254] Type fixes --- src/libs/PolicyUtils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 1f2abfa2b7a8..5b4ed6fcd208 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -96,11 +96,11 @@ const isPolicyAdmin = (policy: OnyxTypes.Policy): boolean => policy?.role === CO function getMemberAccountIDsForWorkspace(policyMembers: PolicyMemberList, personalDetails: PersonalDetailsList): MemberEmailsToAccountIDs { const memberEmailsToAccountIDs: MemberEmailsToAccountIDs = {}; Object.keys(policyMembers).forEach((accountID) => { - const member = policyMembers[accountID]; + const member = policyMembers?.[accountID]; if (Object.keys(member?.errors ?? {}).length > 0) { return; } - const personalDetail = personalDetails[accountID]; + const personalDetail = personalDetails?.[accountID]; if (!personalDetail?.login) { return; } @@ -115,12 +115,12 @@ function getMemberAccountIDsForWorkspace(policyMembers: PolicyMemberList, person function getIneligibleInvitees(policyMembers: PolicyMemberList, personalDetails: PersonalDetailsList): string[] { const memberEmailsToExclude: string[] = [...CONST.EXPENSIFY_EMAILS]; Object.keys(policyMembers).forEach((accountID) => { - const policyMember = policyMembers[accountID]; + const policyMember = policyMembers?.[accountID]; // Policy members that are pending delete or have errors are not valid and we should show them in the invite options (don't exclude them). if (policyMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policyMember?.errors ?? {}).length > 0) { return; } - const memberEmail = personalDetails[accountID]?.login; + const memberEmail = personalDetails?.[accountID]?.login; if (!memberEmail) { return; } From a27b365ec21320343fd4a9921e17ebcf543f9d29 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Mon, 2 Oct 2023 09:52:48 +0700 Subject: [PATCH 024/254] fix clicking back button bring back the workspace --- src/libs/actions/App.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index b8be35aa1919..a493dd9b74d9 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -325,9 +325,11 @@ function createWorkspaceAndNavigateToIt(policyOwnerEmail = '', makeMeAdmin = fal } if (shouldNavigateToAdminChat) { - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(adminsChatReportID)); + Navigation.dismissModal(adminsChatReportID); } - + }) + .then(() => Navigation.isNavigationReady()) + .then(() => { Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); }) .then(endSignOnTransition); From 837bd88068113e148404d989f4331e8bfef0d7ec Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Mon, 2 Oct 2023 10:58:11 +0200 Subject: [PATCH 025/254] Migrate PolicyUtils to TS --- src/libs/PolicyUtils.ts | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index a901384ef668..9b45318e67eb 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -14,7 +14,9 @@ type UnitRate = {rate: number}; * These are policies that we can use to create reports with in NewDot. */ function getActivePolicies(policies: OnyxTypes.Policy[]): OnyxTypes.Policy[] { - return policies.filter((policy) => policy && (policy.isPolicyExpenseChatEnabled || policy.areChatRoomsEnabled) && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); + return (policies ?? []).filter( + (policy) => policy && (policy.isPolicyExpenseChatEnabled || policy.areChatRoomsEnabled) && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + ); } /** @@ -22,14 +24,14 @@ function getActivePolicies(policies: OnyxTypes.Policy[]): OnyxTypes.Policy[] { * Data structure: {accountID: {role:'user', errors: []}, accountID2: {role:'admin', errors: [{1231312313: 'Unable to do X'}]}, ...} */ function hasPolicyMemberError(policyMembers: PolicyMemberList): boolean { - return Object.values(policyMembers).some((member) => Object.keys(member?.errors ?? {}).length > 0); + return Object.values(policyMembers ?? {}).some((member) => Object.keys(member?.errors ?? {}).length > 0); } /** * Check if the policy has any error fields. */ function hasPolicyErrorFields(policy: OnyxTypes.Policy): boolean { - return Object.keys(policy?.errorFields ?? {}).some((fieldErrors) => Object.keys(fieldErrors).length > 0); + return Object.keys(policy?.errorFields ?? {}).some((fieldErrors) => Object.keys(fieldErrors ?? {}).length > 0); } /** @@ -87,19 +89,19 @@ function getPolicyBrickRoadIndicatorStatus(policy: OnyxTypes.Policy, policyMembe function shouldShowPolicy(policy: OnyxTypes.Policy, isOffline: boolean): boolean { return ( policy && - policy.isPolicyExpenseChatEnabled && - policy.role === CONST.POLICY.ROLE.ADMIN && - (isOffline || policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policy.errors).length > 0) + policy?.isPolicyExpenseChatEnabled && + policy?.role === CONST.POLICY.ROLE.ADMIN && + (isOffline || policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policy.errors ?? {}).length > 0) ); } function isExpensifyTeam(email: string): boolean { - const emailDomain = Str.extractEmailDomain(email); + const emailDomain = Str.extractEmailDomain(email ?? ''); return emailDomain === CONST.EXPENSIFY_PARTNER_NAME || emailDomain === CONST.EMAIL.GUIDES_DOMAIN; } function isExpensifyGuideTeam(email: string): boolean { - const emailDomain = Str.extractEmailDomain(email); + const emailDomain = Str.extractEmailDomain(email ?? ''); return emailDomain === CONST.EMAIL.GUIDES_DOMAIN; } @@ -115,9 +117,9 @@ const isPolicyAdmin = (policy: OnyxTypes.Policy): boolean => policy?.role === CO */ function getMemberAccountIDsForWorkspace(policyMembers: PolicyMemberList, personalDetails: PersonalDetailsList): MemberEmailsToAccountIDs { const memberEmailsToAccountIDs: Record = {}; - Object.keys(policyMembers).forEach((accountID) => { + Object.keys(policyMembers ?? {}).forEach((accountID) => { const member = policyMembers?.[accountID]; - if (Object.keys(member?.errors ?? {}).length > 0) { + if (Object.keys(member?.errors ?? {})?.length > 0) { return; } const personalDetail = personalDetails[accountID]; @@ -134,7 +136,7 @@ function getMemberAccountIDsForWorkspace(policyMembers: PolicyMemberList, person */ function getIneligibleInvitees(policyMembers: PolicyMemberList, personalDetails: PersonalDetailsList): string[] { const memberEmailsToExclude: string[] = [...CONST.EXPENSIFY_EMAILS]; - Object.keys(policyMembers).forEach((accountID) => { + Object.keys(policyMembers ?? {}).forEach((accountID) => { const policyMember = policyMembers?.[accountID]; // Policy members that are pending delete or have errors are not valid and we should show them in the invite options (don't exclude them). if (policyMember.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policyMember?.errors ?? {}).length > 0) { @@ -154,11 +156,11 @@ function getIneligibleInvitees(policyMembers: PolicyMemberList, personalDetails: * Gets the tag from policy tags, defaults to the first if no key is provided. */ function getTag(policyTags: Record, tagKey?: keyof typeof policyTags) { - if (Object.keys(policyTags).length === 0) { + if (Object.keys(policyTags ?? {})?.length === 0) { return {}; } - const policyTagKey = tagKey ?? Object.keys(policyTags)[0]; + const policyTagKey = tagKey ?? Object.keys(policyTags ?? {})[0]; return policyTags?.[policyTagKey] ?? {}; } @@ -167,11 +169,11 @@ function getTag(policyTags: Record, tagKey?: keyof * Gets the first tag name from policy tags. */ function getTagListName(policyTags: Record) { - if (Object.keys(policyTags).length === 0) { + if (Object.keys(policyTags ?? {})?.length === 0) { return ''; } - const policyTagKeys = Object.keys(policyTags)[0] ?? []; + const policyTagKeys = Object.keys(policyTags ?? {})[0] ?? []; return policyTags?.[policyTagKeys]?.name ?? ''; } @@ -180,17 +182,17 @@ function getTagListName(policyTags: Record) { * Gets the tags of a policy for a specific key. Defaults to the first tag if no key is provided. */ function getTagList(policyTags: Record>, tagKey: string) { - if (Object.keys(policyTags).length === 0) { + if (Object.keys(policyTags ?? {})?.length === 0) { return {}; } - const policyTagKey = tagKey ?? Object.keys(policyTags)[0]; + const policyTagKey = tagKey ?? Object.keys(policyTags ?? {})[0]; return policyTags?.[policyTagKey]?.tags ?? {}; } function isPendingDeletePolicy(policy: OnyxTypes.Policy): boolean { - return policy.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; + return policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; } export { From f42364d124e12225486364263fec5eef0b7c31b2 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 2 Oct 2023 12:34:47 +0200 Subject: [PATCH 026/254] [TS migration] Migrate 'SidebarUtils.js' lib to TypeScript --- src/libs/PersonalDetailsUtils.js | 4 +- src/libs/ReportUtils.js | 16 +- src/libs/SidebarUtils.ts | 238 +++++++++++++++++++++--------- src/types/onyx/PersonalDetails.ts | 2 + src/types/onyx/Report.ts | 7 +- 5 files changed, 186 insertions(+), 81 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.js b/src/libs/PersonalDetailsUtils.js index a401dea4b911..98bf171812fc 100644 --- a/src/libs/PersonalDetailsUtils.js +++ b/src/libs/PersonalDetailsUtils.js @@ -17,8 +17,8 @@ Onyx.connect({ }); /** - * @param {Object} passedPersonalDetails - * @param {Array} pathToDisplayName + * @param {Object | Null} passedPersonalDetails + * @param {Array | String} pathToDisplayName * @param {String} [defaultValue] optional default display name value * @returns {String} */ diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 475c1a8bcb8a..da2ef0f04ebf 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -108,9 +108,9 @@ function getPolicyType(report, policies) { /** * Get the policy name from a given report * @param {Object} report - * @param {String} report.policyID - * @param {String} report.oldPolicyName - * @param {String} report.policyName + * @param {String} [report.policyID] + * @param {String} [report.oldPolicyName] + * @param {String} [report.policyName] * @param {Boolean} [returnEmptyIfNotFound] * @param {Object} [policy] * @returns {String} @@ -363,7 +363,7 @@ function isUserCreatedPolicyRoom(report) { /** * Whether the provided report is a Policy Expense chat. * @param {Object} report - * @param {String} report.chatType + * @param {String} [report.chatType] * @returns {Boolean} */ function isPolicyExpenseChat(report) { @@ -389,7 +389,7 @@ function isControlPolicyExpenseReport(report) { /** * Whether the provided report is a chat room * @param {Object} report - * @param {String} report.chatType + * @param {String} [report.chatType] * @returns {Boolean} */ function isChatRoom(report) { @@ -578,8 +578,8 @@ function findLastAccessedReport(reports, ignoreDomainRooms, policies, isFirstTim /** * Whether the provided report is an archived room * @param {Object} report - * @param {Number} report.stateNum - * @param {Number} report.statusNum + * @param {Number} [report.stateNum] + * @param {Number} [report.statusNum] * @returns {Boolean} */ function isArchivedRoom(report) { @@ -2912,7 +2912,7 @@ function shouldHideReport(report, currentReportId) { * filter out the majority of reports before filtering out very specific minority of reports. * * @param {Object} report - * @param {String} currentReportId + * @param {String | Null} currentReportId * @param {Boolean} isInGSDMode * @param {String[]} betas * @param {Object} policies diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index e1251f9f50bc..938c0f7f0a26 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1,8 +1,7 @@ /* eslint-disable rulesdir/prefer-underscore-method */ import Onyx from 'react-native-onyx'; -import _ from 'underscore'; -import lodashGet from 'lodash/get'; import Str from 'expensify-common/lib/str'; +import {ValueOf} from 'type-fest'; import ONYXKEYS from '../ONYXKEYS'; import * as ReportUtils from './ReportUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; @@ -14,6 +13,11 @@ import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as UserUtils from './UserUtils'; import * as PersonalDetailsUtils from './PersonalDetailsUtils'; import ReportAction from '../types/onyx/ReportAction'; +import Beta from '../types/onyx/Beta'; +import Policy from '../types/onyx/Policy'; +import Report from '../types/onyx/Report'; +import {PersonalDetails} from '../types/onyx'; +import * as OnyxCommon from '../types/onyx/OnyxCommon'; const visibleReportActionItems: Record = {}; const lastReportActions: Record = {}; @@ -68,11 +72,11 @@ function resetIsSidebarLoadedReadyPromise() { }); } -function isSidebarLoadedReady() { +function isSidebarLoadedReady(): Promise { return sidebarIsReadyPromise; } -function compareStringDates(a: string, b: string) { +function compareStringDates(a: string, b: string): 0 | 1 | -1 { if (a < b) { return -1; } @@ -87,7 +91,7 @@ function setIsSidebarLoadedReady() { } // Define a cache object to store the memoized results -const reportIDsCache = new Map(); +const reportIDsCache = new Map(); // Function to set a key-value pair while maintaining the maximum key limit function setWithLimit(map: Map, key: TKey, value: TValue) { @@ -105,11 +109,18 @@ let hasInitialReportActions = false; /** * @returns An array of reportIDs sorted in the proper order */ -function getOrderedReportIDs(currentReportId, allReportsDict, betas, policies, priorityMode, allReportActions) { +function getOrderedReportIDs( + currentReportId: string | null, + allReports: Record, + betas: Beta[], + policies: Record, + priorityMode: ValueOf, + allReportActions: Record, +): string[] { // Generate a unique cache key based on the function arguments const cachedReportsKey = JSON.stringify( - [currentReportId, allReportsDict, betas, policies, priorityMode, allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.length || 1], - (key, value) => { + [currentReportId, allReports, betas, policies, priorityMode, allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.length || 1], + (key, value: unknown) => { /** * Exclude 'participantAccountIDs', 'participants' and 'lastMessageText' not to overwhelm a cached key value with huge data, * which we don't need to store in a cacheKey @@ -117,13 +128,15 @@ function getOrderedReportIDs(currentReportId, allReportsDict, betas, policies, p if (key === 'participantAccountIDs' || key === 'participants' || key === 'lastMessageText') { return undefined; } + return value; }, ); // Check if the result is already in the cache - if (reportIDsCache.has(cachedReportsKey) && hasInitialReportActions) { - return reportIDsCache.get(cachedReportsKey); + const cachedIDs = reportIDsCache.get(cachedReportsKey); + if (cachedIDs && hasInitialReportActions) { + return cachedIDs; } // This is needed to prevent caching when Onyx is empty for a second render @@ -131,7 +144,7 @@ function getOrderedReportIDs(currentReportId, allReportsDict, betas, policies, p const isInGSDMode = priorityMode === CONST.PRIORITY_MODE.GSD; const isInDefaultMode = !isInGSDMode; - const allReportsDictValues = Object.values(allReportsDict); + const allReportsDictValues = Object.values(allReports); // Filter out all the reports that shouldn't be displayed const reportsToDisplay = allReportsDictValues.filter((report) => ReportUtils.shouldReportBeInOptionList(report, currentReportId, isInGSDMode, betas, policies, allReportActions, true)); @@ -152,7 +165,7 @@ function getOrderedReportIDs(currentReportId, allReportsDict, betas, policies, p report.displayName = ReportUtils.getReportName(report); // eslint-disable-next-line no-param-reassign - report.iouReportAmount = ReportUtils.getMoneyRequestTotal(report, allReportsDict); + report.iouReportAmount = ReportUtils.getMoneyRequestTotal(report, allReports); }); // The LHN is split into five distinct groups, and each group is sorted a little differently. The groups will ALWAYS be in this order: @@ -165,11 +178,11 @@ function getOrderedReportIDs(currentReportId, allReportsDict, betas, policies, p // 5. Archived reports // - Sorted by lastVisibleActionCreated in default (most recent) view mode // - Sorted by reportDisplayName in GSD (focus) view mode - const pinnedReports = []; - const outstandingIOUReports = []; - const draftReports = []; - const nonArchivedReports = []; - const archivedReports = []; + const pinnedReports: Report[] = []; + const outstandingIOUReports: Report[] = []; + const draftReports: Report[] = []; + const nonArchivedReports: Report[] = []; + const archivedReports: Report[] = []; reportsToDisplay.forEach((report) => { if (report.isPinned) { pinnedReports.push(report); @@ -185,47 +198,121 @@ function getOrderedReportIDs(currentReportId, allReportsDict, betas, policies, p }); // Sort each group of reports accordingly - pinnedReports.sort((a, b) => a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase())); - outstandingIOUReports.sort((a, b) => b.iouReportAmount - a.iouReportAmount || a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase())); - draftReports.sort((a, b) => a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase())); + pinnedReports.sort((a, b) => (a?.displayName && b?.displayName ? a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()) : 0)); + outstandingIOUReports.sort((a, b) => { + const compareAmounts = a?.iouReportAmount && b?.iouReportAmount ? b.iouReportAmount - a.iouReportAmount : 0; + const compareDisplayNames = a?.displayName && b?.displayName ? a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()) : 0; + return compareAmounts || compareDisplayNames; + }); + draftReports.sort((a, b) => (a?.displayName && b?.displayName ? a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()) : 0)); if (isInDefaultMode) { - nonArchivedReports.sort( - (a, b) => compareStringDates(b.lastVisibleActionCreated, a.lastVisibleActionCreated) || a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()), - ); + nonArchivedReports.sort((a, b) => { + const compareDates = a?.lastVisibleActionCreated && b?.lastVisibleActionCreated ? compareStringDates(b.lastVisibleActionCreated, a.lastVisibleActionCreated) : 0; + const compareDisplayNames = a?.displayName && b?.displayName ? a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()) : 0; + return compareDates || compareDisplayNames; + }); // For archived reports ensure that most recent reports are at the top by reversing the order - archivedReports.sort((a, b) => compareStringDates(b.lastVisibleActionCreated, a.lastVisibleActionCreated)); + archivedReports.sort((a, b) => (a?.lastVisibleActionCreated && b?.lastVisibleActionCreated ? compareStringDates(b.lastVisibleActionCreated, a.lastVisibleActionCreated) : 0)); } else { - nonArchivedReports.sort((a, b) => a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase())); - archivedReports.sort((a, b) => a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase())); + nonArchivedReports.sort((a, b) => (a?.displayName && b?.displayName ? a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()) : 0)); + archivedReports.sort((a, b) => (a?.displayName && b?.displayName ? a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()) : 0)); } // Now that we have all the reports grouped and sorted, they must be flattened into an array and only return the reportID. // The order the arrays are concatenated in matters and will determine the order that the groups are displayed in the sidebar. - const LHNReports = [].concat(pinnedReports, outstandingIOUReports, draftReports, nonArchivedReports, archivedReports).map((report) => report.reportID); + const LHNReports = [...pinnedReports, ...outstandingIOUReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report.reportID); setWithLimit(reportIDsCache, cachedReportsKey, LHNReports); return LHNReports; } +type OptionData = { + text?: string | null; + alternateText?: string | null; + pendingAction?: OnyxCommon.PendingAction | null; + allReportErrors?: OnyxCommon.Errors | null; + brickRoadIndicator?: typeof CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR | '' | null; + icons?: Icon[] | null; + tooltipText?: string | null; + ownerAccountID?: number | null; + subtitle?: string | null; + participantsList?: PersonalDetails[] | null; + login?: string | null; + accountID?: number | null; + managerID?: number | null; + reportID?: string | null; + policyID?: string | null; + status?: string | null; + type?: string | null; + stateNum?: ValueOf | null; + statusNum?: ValueOf | null; + phoneNumber?: string | null; + isUnread?: boolean | null; + isUnreadWithMention?: boolean | null; + hasDraftComment?: boolean | null; + keyForList?: string | null; + searchText?: string | null; + isPinned?: boolean | null; + hasOutstandingIOU?: boolean | null; + iouReportID?: string | null; + isIOUReportOwner?: boolean | null; + iouReportAmount?: number | null; + isChatRoom?: boolean | null; + isArchivedRoom?: boolean | null; + shouldShowSubscript?: boolean | null; + isPolicyExpenseChat?: boolean | null; + isMoneyRequestReport?: boolean | null; + isExpenseRequest?: boolean | null; + isWaitingOnBankAccount?: boolean | null; + isLastMessageDeletedParentAction?: boolean | null; + isAllowedToComment?: boolean | null; + isThread?: boolean | null; + isTaskReport?: boolean | null; + isWaitingForTaskCompleteFromAssignee?: boolean | null; + parentReportID?: string | null; + notificationPreference?: string | number | null; + displayNamesWithTooltips?: DisplayNamesWithTooltip[] | null; +}; + +type DisplayNamesWithTooltip = { + displayName?: string; + avatar?: string; + login?: string; + accountID?: number; + pronouns?: string; +}; + +type ActorDetails = { + displayName?: string; + accountID?: number; +}; + +type Icon = { + source?: string; + id?: number; + type?: string; + name?: string; +}; + /** * Gets all the data necessary for rendering an OptionRowLHN component - * - * @param report - * @param reportActions - * @param personalDetails - * @param preferredLocale - * @param [policy] - * @param parentReportAction - * @returns */ -function getOptionData(report, reportActions, personalDetails, preferredLocale, policy, parentReportAction) { +function getOptionData( + report: Report, + reportActions: Record, + personalDetails: Record, + preferredLocale: ValueOf, + policy: Policy, + parentReportAction: ReportAction, +): OptionData | undefined { // When a user signs out, Onyx is cleared. Due to the lazy rendering with a virtual list, it's possible for // this method to be called after the Onyx data has been cleared out. In that case, it's fine to do // a null check here and return early. if (!report || !personalDetails) { return; } - const result = { + + const result: OptionData = { text: null, alternateText: null, pendingAction: null, @@ -263,10 +350,14 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, isWaitingOnBankAccount: false, isLastMessageDeletedParentAction: false, isAllowedToComment: true, + isThread: null, + isTaskReport: null, + isWaitingForTaskCompleteFromAssignee: null, + parentReportID: null, + notificationPreference: null, }; - - const participantPersonalDetailList = _.values(OptionsListUtils.getPersonalDetailsForAccountIDs(report.participantAccountIDs, personalDetails)); - const personalDetail = participantPersonalDetailList[0] || {}; + const participantPersonalDetailList: PersonalDetails[] = Object.values(OptionsListUtils.getPersonalDetailsForAccountIDs(report.participantAccountIDs ?? [], personalDetails)); + const personalDetail = participantPersonalDetailList[0] ?? {}; result.isThread = ReportUtils.isChatThread(report); result.isChatRoom = ReportUtils.isChatRoom(report); @@ -280,8 +371,8 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, result.isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); result.shouldShowSubscript = ReportUtils.shouldReportShowSubscript(report); result.pendingAction = report.pendingFields ? report.pendingFields.addWorkspaceRoom || report.pendingFields.createChat : null; - result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions); - result.brickRoadIndicator = !_.isEmpty(result.allReportErrors) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; + result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) as OnyxCommon.Errors; + result.brickRoadIndicator = Object.keys(result.allReportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; result.ownerAccountID = report.ownerAccountID; result.managerID = report.managerID; result.reportID = report.reportID; @@ -294,30 +385,30 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, result.isPinned = report.isPinned; result.iouReportID = report.iouReportID; result.keyForList = String(report.reportID); - result.tooltipText = ReportUtils.getReportParticipantsTitle(report.participantAccountIDs || []); + result.tooltipText = ReportUtils.getReportParticipantsTitle(report.participantAccountIDs ?? []); result.hasOutstandingIOU = report.hasOutstandingIOU; - result.parentReportID = report.parentReportID || null; + result.parentReportID = report.parentReportID; result.isWaitingOnBankAccount = report.isWaitingOnBankAccount; - result.notificationPreference = report.notificationPreference || null; + result.notificationPreference = report.notificationPreference; result.isAllowedToComment = !ReportUtils.shouldDisableWriteActions(report); const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat; const subtitle = ReportUtils.getChatRoomSubtitle(report); - const login = Str.removeSMSDomain(lodashGet(personalDetail, 'login', '')); - const status = lodashGet(personalDetail, 'status', ''); + const login = Str.removeSMSDomain(personalDetail?.login ?? ''); + const status = personalDetail?.status ?? ''; const formattedLogin = Str.isSMSLogin(login) ? LocalePhoneNumber.formatPhoneNumber(login) : login; // We only create tooltips for the first 10 users or so since some reports have hundreds of users, causing performance to degrade. - const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips((participantPersonalDetailList || []).slice(0, 10), hasMultipleParticipants); + const displayNamesWithTooltips: DisplayNamesWithTooltip[] = ReportUtils.getDisplayNamesWithTooltips((participantPersonalDetailList || []).slice(0, 10), hasMultipleParticipants); const lastMessageTextFromReport = OptionsListUtils.getLastMessageTextForReport(report); // If the last actor's details are not currently saved in Onyx Collection, // then try to get that from the last report action if that action is valid // to get data from. - let lastActorDetails = personalDetails[report.lastActorAccountID] || null; + let lastActorDetails: ActorDetails | null = report.lastActorAccountID ? personalDetails[report.lastActorAccountID] : null; if (!lastActorDetails && visibleReportActionItems[report.reportID]) { - const lastActorDisplayName = lodashGet(visibleReportActionItems[report.reportID], 'person[0].text'); + const lastActorDisplayName = visibleReportActionItems[report.reportID]?.person?.[0]?.text; lastActorDetails = lastActorDisplayName ? { displayName: lastActorDisplayName, @@ -328,22 +419,25 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, let lastMessageText = hasMultipleParticipants && lastActorDetails?.accountID && Number(lastActorDetails.accountID) !== currentUserAccountID ? `${lastActorDetails.displayName}: ` : ''; lastMessageText += report ? lastMessageTextFromReport : ''; - if (result.isArchivedRoom) { - const archiveReason = lastReportActions[report.reportID]?.originalMessage?.reason || CONST.REPORT.ARCHIVE_REASON.DEFAULT; + const reportAction = lastReportActions[report.reportID]; + if (result.isArchivedRoom && reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED) { + const archiveReason = reportAction?.originalMessage?.reason ?? CONST.REPORT.ARCHIVE_REASON.DEFAULT; + lastMessageText = Localize.translate(preferredLocale, `reportArchiveReasons.${archiveReason}`, { - displayName: archiveReason.displayName || PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails, 'displayName'), + displayName: PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails, 'displayName'), policyName: ReportUtils.getPolicyName(report, false, policy), }); } if ((result.isChatRoom || result.isPolicyExpenseChat || result.isThread || result.isTaskReport) && !result.isArchivedRoom) { const lastAction = visibleReportActionItems[report.reportID]; - if (lodashGet(lastAction, 'actionName', '') === CONST.REPORT.ACTIONS.TYPE.RENAMED) { - const newName = lodashGet(lastAction, 'originalMessage.newName', ''); + + if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.RENAMED) { + const newName = lastAction?.originalMessage?.newName ?? ''; result.alternateText = Localize.translate(preferredLocale, 'newRoomPage.roomRenamedTo', {newName}); - } else if (lodashGet(lastAction, 'actionName', '') === CONST.REPORT.ACTIONS.TYPE.TASKREOPENED) { + } else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.TASKREOPENED) { result.alternateText = `${Localize.translate(preferredLocale, 'task.messages.reopened')}: ${report.reportName}`; - } else if (lodashGet(lastAction, 'actionName', '') === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED) { + } else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED) { result.alternateText = `${Localize.translate(preferredLocale, 'task.messages.completed')}: ${report.reportName}`; } else { result.alternateText = lastMessageTextFromReport.length > 0 ? lastMessageText : Localize.translate(preferredLocale, 'report.noActivityYet'); @@ -354,19 +448,23 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, // We also add a fullstop after the final name, the word "and" before the final name and commas between all previous names. lastMessageText = Localize.translate(preferredLocale, 'reportActionsView.beginningOfChatHistory') + - _.map(displayNamesWithTooltips, ({displayName, pronouns}, index) => { - const formattedText = _.isEmpty(pronouns) ? displayName : `${displayName} (${pronouns})`; - - if (index === displayNamesWithTooltips.length - 1) { - return `${formattedText}.`; - } - if (index === displayNamesWithTooltips.length - 2) { - return `${formattedText} ${Localize.translate(preferredLocale, 'common.and')}`; - } - if (index < displayNamesWithTooltips.length - 2) { - return `${formattedText},`; - } - }).join(' '); + displayNamesWithTooltips + .map(({displayName, pronouns}, index) => { + const formattedText = !pronouns ? displayName : `${displayName} (${pronouns})`; + + if (index === displayNamesWithTooltips.length - 1) { + return `${formattedText}.`; + } + if (index === displayNamesWithTooltips.length - 2) { + return `${formattedText} ${Localize.translate(preferredLocale, 'common.and')}`; + } + if (index < displayNamesWithTooltips.length - 2) { + return `${formattedText},`; + } + + return ''; + }) + .join(' '); } result.alternateText = lastMessageText || formattedLogin; diff --git a/src/types/onyx/PersonalDetails.ts b/src/types/onyx/PersonalDetails.ts index 64911dbfecb1..e1c944a95c40 100644 --- a/src/types/onyx/PersonalDetails.ts +++ b/src/types/onyx/PersonalDetails.ts @@ -40,6 +40,8 @@ type PersonalDetails = { /** Whether timezone is automatically set */ automatic?: boolean; }; + + status?: string; }; export default PersonalDetails; diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 88caa683305d..b029e3996963 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -55,7 +55,7 @@ type Report = { reportName?: string; /** ID of the report */ - reportID?: string; + reportID: string; /** The state that the report is currently in */ stateNum?: ValueOf; @@ -83,6 +83,11 @@ type Report = { participantAccountIDs?: number[]; total?: number; currency?: string; + iouReportAmount?: number; + isWaitingOnBankAccount?: boolean; + isLastMessageDeletedParentAction?: boolean; + iouReportID?: string; + pendingFields?: Record; }; export default Report; From a803a71b9ecd22bb274020655a72bde5a76012d2 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Mon, 2 Oct 2023 18:55:58 +0700 Subject: [PATCH 027/254] fix refactor logic then --- src/libs/actions/App.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index a493dd9b74d9..5c9a0c2a8628 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -327,8 +327,8 @@ function createWorkspaceAndNavigateToIt(policyOwnerEmail = '', makeMeAdmin = fal if (shouldNavigateToAdminChat) { Navigation.dismissModal(adminsChatReportID); } + return Navigation.isNavigationReady(); }) - .then(() => Navigation.isNavigationReady()) .then(() => { Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); }) From 4fe4ff020c80b21bcca6b00131fd2e553fb55075 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Tue, 3 Oct 2023 00:17:04 +0700 Subject: [PATCH 028/254] fix handle case create workspace without openning modal --- src/libs/actions/App.js | 17 +++++++++++++++-- .../FloatingActionButtonAndPopover.js | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 5c9a0c2a8628..fbea58b17efe 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -313,8 +313,17 @@ function endSignOnTransition() { * @param {String} [policyName] Optional, custom policy name we will use for created workspace * @param {Boolean} [transitionFromOldDot] Optional, if the user is transitioning from old dot * @param {Boolean} [shouldNavigateToAdminChat] Optional, navigate to the #admin room after creation + * @param {Boolean} [isThereModalToDismiss] Optional, if there is a modal to dismiss + */ -function createWorkspaceAndNavigateToIt(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', transitionFromOldDot = false, shouldNavigateToAdminChat = true) { +function createWorkspaceAndNavigateToIt( + policyOwnerEmail = '', + makeMeAdmin = false, + policyName = '', + transitionFromOldDot = false, + shouldNavigateToAdminChat = true, + isThereModalToDismiss = true, +) { const policyID = Policy.generatePolicyID(); const adminsChatReportID = Policy.createWorkspace(policyOwnerEmail, makeMeAdmin, policyName, policyID); Navigation.isNavigationReady() @@ -325,7 +334,11 @@ function createWorkspaceAndNavigateToIt(policyOwnerEmail = '', makeMeAdmin = fal } if (shouldNavigateToAdminChat) { - Navigation.dismissModal(adminsChatReportID); + if (isThereModalToDismiss) { + Navigation.dismissModal(adminsChatReportID); + } else { + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(adminsChatReportID)); + } } return Navigation.isNavigationReady(); }) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index e9ede2c9a89a..bf703715848f 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -216,7 +216,7 @@ function FloatingActionButtonAndPopover(props) { iconHeight: 40, text: props.translate('workspace.new.newWorkspace'), description: props.translate('workspace.new.getTheExpensifyCardAndMore'), - onSelected: () => interceptAnonymousUser(() => App.createWorkspaceAndNavigateToIt('', false, '', false, !props.isSmallScreenWidth)), + onSelected: () => interceptAnonymousUser(() => App.createWorkspaceAndNavigateToIt('', false, '', false, !props.isSmallScreenWidth, false)), }, ] : []), From 08da8c014b6c53e8042bbae41731fdd891619c62 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 3 Oct 2023 10:01:07 +0200 Subject: [PATCH 029/254] Self review the code --- src/libs/SidebarUtils.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 938c0f7f0a26..d3bb9f6491d3 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -350,11 +350,6 @@ function getOptionData( isWaitingOnBankAccount: false, isLastMessageDeletedParentAction: false, isAllowedToComment: true, - isThread: null, - isTaskReport: null, - isWaitingForTaskCompleteFromAssignee: null, - parentReportID: null, - notificationPreference: null, }; const participantPersonalDetailList: PersonalDetails[] = Object.values(OptionsListUtils.getPersonalDetailsForAccountIDs(report.participantAccountIDs ?? [], personalDetails)); const personalDetail = participantPersonalDetailList[0] ?? {}; @@ -387,9 +382,9 @@ function getOptionData( result.keyForList = String(report.reportID); result.tooltipText = ReportUtils.getReportParticipantsTitle(report.participantAccountIDs ?? []); result.hasOutstandingIOU = report.hasOutstandingIOU; - result.parentReportID = report.parentReportID; + result.parentReportID = report.parentReportID ?? null; result.isWaitingOnBankAccount = report.isWaitingOnBankAccount; - result.notificationPreference = report.notificationPreference; + result.notificationPreference = report.notificationPreference ?? null; result.isAllowedToComment = !ReportUtils.shouldDisableWriteActions(report); const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat; From bbcdbd146d234e201c05f74eaf40842b5a8c3811 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 3 Oct 2023 19:17:51 +0200 Subject: [PATCH 030/254] Changes after review of PolicyUtils --- src/ONYXKEYS.ts | 2 +- src/libs/PolicyUtils.ts | 50 ++++++++++++++++++---------------- src/types/onyx/PolicyMember.ts | 3 ++ src/types/onyx/PolicyTag.ts | 3 ++ src/types/onyx/index.ts | 6 ++-- 5 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index a1afc4fef2c1..b01ffbc37141 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -373,7 +373,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTag; [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories; - [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMember; + [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMembers; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: Record; [ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report; [ONYXKEYS.COLLECTION.REPORT_METADATA]: OnyxTypes.ReportMetadata; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 9b45318e67eb..c0bb7e539bec 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -1,21 +1,24 @@ +import {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Str from 'expensify-common/lib/str'; import CONST from '../CONST'; import ONYXKEYS from '../ONYXKEYS'; -import * as OnyxTypes from '../types/onyx'; +import {PersonalDetails, Policy, PolicyMembers, PolicyTags} from '../types/onyx'; -type PolicyMemberList = Record; -type PolicyMembersCollection = Record; type MemberEmailsToAccountIDs = Record; -type PersonalDetailsList = Record; +type PersonalDetailsList = Record; type UnitRate = {rate: number}; /** * Filter out the active policies, which will exclude policies with pending deletion * These are policies that we can use to create reports with in NewDot. */ -function getActivePolicies(policies: OnyxTypes.Policy[]): OnyxTypes.Policy[] { - return (policies ?? []).filter( - (policy) => policy && (policy.isPolicyExpenseChatEnabled || policy.areChatRoomsEnabled) && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, +function getActivePolicies(policies: OnyxCollection): Policy[] | undefined { + if (!policies) { + return; + } + return (Object.values(policies) ?? []).filter( + (policy): policy is Policy => + policy !== null && policy && (policy.isPolicyExpenseChatEnabled || policy.areChatRoomsEnabled) && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, ); } @@ -23,28 +26,28 @@ function getActivePolicies(policies: OnyxTypes.Policy[]): OnyxTypes.Policy[] { * Checks if we have any errors stored within the POLICY_MEMBERS. Determines whether we should show a red brick road error or not. * Data structure: {accountID: {role:'user', errors: []}, accountID2: {role:'admin', errors: [{1231312313: 'Unable to do X'}]}, ...} */ -function hasPolicyMemberError(policyMembers: PolicyMemberList): boolean { +function hasPolicyMemberError(policyMembers: OnyxEntry): boolean { return Object.values(policyMembers ?? {}).some((member) => Object.keys(member?.errors ?? {}).length > 0); } /** * Check if the policy has any error fields. */ -function hasPolicyErrorFields(policy: OnyxTypes.Policy): boolean { +function hasPolicyErrorFields(policy: OnyxEntry): boolean { return Object.keys(policy?.errorFields ?? {}).some((fieldErrors) => Object.keys(fieldErrors ?? {}).length > 0); } /** * Check if the policy has any errors, and if it doesn't, then check if it has any error fields. */ -function hasPolicyError(policy: OnyxTypes.Policy): boolean { +function hasPolicyError(policy: OnyxEntry): boolean { return Object.keys(policy?.errors ?? {}).length > 0 ? true : hasPolicyErrorFields(policy); } /** * Checks if we have any errors stored within the policy custom units. */ -function hasCustomUnitsError(policy: OnyxTypes.Policy): boolean { +function hasCustomUnitsError(policy: OnyxEntry): boolean { return Object.keys(policy?.customUnits?.errors ?? {}).length > 0; } @@ -71,8 +74,8 @@ function getUnitRateValue(customUnitRate: UnitRate, toLocaleDigit: (arg: string) /** * Get the brick road indicator status for a policy. The policy has an error status if there is a policy member error, a custom unit error or a field error. */ -function getPolicyBrickRoadIndicatorStatus(policy: OnyxTypes.Policy, policyMembersCollection: PolicyMembersCollection): string { - const policyMembers = policyMembersCollection?.[`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policy.id}`] ?? {}; +function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry, policyMembersCollection: OnyxCollection): string { + const policyMembers = policyMembersCollection?.[`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policy?.id}`] ?? {}; if (hasPolicyMemberError(policyMembers) || hasCustomUnitsError(policy) || hasPolicyErrorFields(policy)) { return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; } @@ -86,8 +89,9 @@ function getPolicyBrickRoadIndicatorStatus(policy: OnyxTypes.Policy, policyMembe * Note: Using a local ONYXKEYS.NETWORK subscription will cause a delay in * updating the screen. Passing the offline status from the component. */ -function shouldShowPolicy(policy: OnyxTypes.Policy, isOffline: boolean): boolean { +function shouldShowPolicy(policy: OnyxEntry, isOffline: boolean): boolean { return ( + policy !== null && policy && policy?.isPolicyExpenseChatEnabled && policy?.role === CONST.POLICY.ROLE.ADMIN && @@ -108,21 +112,21 @@ function isExpensifyGuideTeam(email: string): boolean { /** * Checks if the current user is an admin of the policy. */ -const isPolicyAdmin = (policy: OnyxTypes.Policy): boolean => policy?.role === CONST.POLICY.ROLE.ADMIN; +const isPolicyAdmin = (policy: OnyxEntry): boolean => policy?.role === CONST.POLICY.ROLE.ADMIN; /** * Create an object mapping member emails to their accountIDs. Filter for members without errors, and get the login email from the personalDetail object using the accountID. * * We only return members without errors. Otherwise, the members with errors would immediately be removed before the user has a chance to read the error. */ -function getMemberAccountIDsForWorkspace(policyMembers: PolicyMemberList, personalDetails: PersonalDetailsList): MemberEmailsToAccountIDs { +function getMemberAccountIDsForWorkspace(policyMembers: OnyxEntry, personalDetails: OnyxEntry): MemberEmailsToAccountIDs { const memberEmailsToAccountIDs: Record = {}; Object.keys(policyMembers ?? {}).forEach((accountID) => { const member = policyMembers?.[accountID]; if (Object.keys(member?.errors ?? {})?.length > 0) { return; } - const personalDetail = personalDetails[accountID]; + const personalDetail = personalDetails?.[accountID]; if (!personalDetail?.login) { return; } @@ -134,12 +138,12 @@ function getMemberAccountIDsForWorkspace(policyMembers: PolicyMemberList, person /** * Get login list that we should not show in the workspace invite options */ -function getIneligibleInvitees(policyMembers: PolicyMemberList, personalDetails: PersonalDetailsList): string[] { +function getIneligibleInvitees(policyMembers: OnyxEntry, personalDetails: OnyxEntry): string[] { const memberEmailsToExclude: string[] = [...CONST.EXPENSIFY_EMAILS]; Object.keys(policyMembers ?? {}).forEach((accountID) => { const policyMember = policyMembers?.[accountID]; // Policy members that are pending delete or have errors are not valid and we should show them in the invite options (don't exclude them). - if (policyMember.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policyMember?.errors ?? {}).length > 0) { + if (policyMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policyMember?.errors ?? {}).length > 0) { return; } const memberEmail = personalDetails?.[accountID]?.login; @@ -155,7 +159,7 @@ function getIneligibleInvitees(policyMembers: PolicyMemberList, personalDetails: /** * Gets the tag from policy tags, defaults to the first if no key is provided. */ -function getTag(policyTags: Record, tagKey?: keyof typeof policyTags) { +function getTag(policyTags: OnyxEntry, tagKey?: keyof typeof policyTags) { if (Object.keys(policyTags ?? {})?.length === 0) { return {}; } @@ -168,7 +172,7 @@ function getTag(policyTags: Record, tagKey?: keyof /** * Gets the first tag name from policy tags. */ -function getTagListName(policyTags: Record) { +function getTagListName(policyTags: OnyxEntry) { if (Object.keys(policyTags ?? {})?.length === 0) { return ''; } @@ -181,7 +185,7 @@ function getTagListName(policyTags: Record) { /** * Gets the tags of a policy for a specific key. Defaults to the first tag if no key is provided. */ -function getTagList(policyTags: Record>, tagKey: string) { +function getTagList(policyTags: OnyxCollection, tagKey: string) { if (Object.keys(policyTags ?? {})?.length === 0) { return {}; } @@ -191,7 +195,7 @@ function getTagList(policyTags: Record): boolean { return policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; } diff --git a/src/types/onyx/PolicyMember.ts b/src/types/onyx/PolicyMember.ts index 055465020c36..60836b276ea0 100644 --- a/src/types/onyx/PolicyMember.ts +++ b/src/types/onyx/PolicyMember.ts @@ -14,4 +14,7 @@ type PolicyMember = { pendingAction?: OnyxCommon.PendingAction; }; +type PolicyMembers = Record; + export default PolicyMember; +export type {PolicyMembers}; diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index fe6bee3a1f31..7807dcc00433 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -10,4 +10,7 @@ type PolicyTag = { 'GL Code': string; }; +type PolicyTags = Record; + export default PolicyTag; +export type {PolicyTags}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index e50925e7adf2..9067229a17d9 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -32,7 +32,7 @@ import WalletTransfer from './WalletTransfer'; import MapboxAccessToken from './MapboxAccessToken'; import {OnyxUpdatesFromServer, OnyxUpdateEvent} from './OnyxUpdatesFromServer'; import Download from './Download'; -import PolicyMember from './PolicyMember'; +import PolicyMember, {PolicyMembers} from './PolicyMember'; import Policy from './Policy'; import PolicyCategory from './PolicyCategory'; import Report from './Report'; @@ -45,7 +45,7 @@ import Form, {AddDebitCardForm} from './Form'; import RecentWaypoint from './RecentWaypoint'; import RecentlyUsedCategories from './RecentlyUsedCategories'; import RecentlyUsedTags from './RecentlyUsedTags'; -import PolicyTag from './PolicyTag'; +import PolicyTag, {PolicyTags} from './PolicyTag'; export type { Account, @@ -98,4 +98,6 @@ export type { RecentlyUsedCategories, RecentlyUsedTags, PolicyTag, + PolicyTags, + PolicyMembers, }; From 392e320554727161916e4547eb24afd7567a5641 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Fri, 6 Oct 2023 22:32:14 +0700 Subject: [PATCH 031/254] fix wait for browser history load url after navigate --- src/libs/actions/App.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index fbea58b17efe..753e73947c4e 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -336,14 +336,24 @@ function createWorkspaceAndNavigateToIt( if (shouldNavigateToAdminChat) { if (isThereModalToDismiss) { Navigation.dismissModal(adminsChatReportID); + setTimeout(() => { + Navigation.navigate(ROUTES.SETTINGS); + setTimeout(() => { + Navigation.navigate(ROUTES.SETTINGS_WORKSPACES); + setTimeout(() => { + Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); + }, 50); + }, 50); + }, 50); } else { Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(adminsChatReportID)); + setTimeout(() => { + Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); + }, 50); } + } else { + Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); } - return Navigation.isNavigationReady(); - }) - .then(() => { - Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); }) .then(endSignOnTransition); } From 334061b6f87f81addacb2f4795df22f09405661b Mon Sep 17 00:00:00 2001 From: Corinne Ofstad Date: Fri, 6 Oct 2023 11:16:19 -0500 Subject: [PATCH 032/254] Update Business-Bank-Accounts-USD.md Adding US Business Bank Account Aricle --- .../Business-Bank-Accounts-USD.md | 147 +++++++++++++++++- 1 file changed, 145 insertions(+), 2 deletions(-) diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md index 375b00d62eac..29667f14397d 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md @@ -1,5 +1,148 @@ --- title: Business Bank Accounts - USD -description: Business Bank Accounts - USD +description: How to add/remove Business Bank Accounts (US) --- -## Resource Coming Soon! +# Overview +Adding a verified business bank account unlocks a myriad of features and automation in Expensify. +Once you connect your business bank account, you can: +- Pay employee expense reports via direct deposit (US) +- Settle company bills via direct transfer +- Accept invoice payments through direct transfer +- Access the Expensify Card +# How to add a verified business bank account +To connect a business bank account to Expensify, follow the below steps: +1. Go to **Settings > Account > Payments** +2. Click **Add Verified Bank Account** +3. Click **Log into your bank** +4. Click **Continue** +5. When you hit the **Plaid** screen, you'll be shown a list of compatible banks that offer direct online login access +6. Login to the business bank account +- If the bank is not listed, click the X to go back to the connection type +- Here you’ll see the option to **Connect Manually** +- Enter your account and routing numbers +7. Enter your bank login credentials. +- If your bank requires additional security measures, you will be directed to obtain and enter a security code +- If you have more than one account available to choose from, you will be directed to choose the desired account +Next, to verify the bank account, you’ll enter some details about the business as well as some personal information. +## Enter company information +This is where you’ll add the legal business name as well as several other company details. +### Company address +The company address must: +- Be located in the US +- Be a physical location +If you input a maildrop address (PO box, UPS Store, etc.), the address will likely be flagged for review and adding the bank account to Expensify will be delayed. +### Tax Identification Number +This is the identification number that was assigned to the business by the IRS. +### Company website +A company website is required to use most of Expensify’s payment features. When adding the website of the business, format it as, https://www.domain.com. +### Industry Classification Code +You can locate a list of Industry Classification Codes here. +## Enter personal information +Whoever is connecting the bank account to Expensify, must enter their details under the Requestor Information section: +- The address must be a physical address +- The address must be located in the US +- The SSN must be US-issued +This does not need to be a signor on the bank account. If someone other than the Expensify account holder enters their personal information in this section, the details will be flagged for review and adding the bank account to Expensify will be delayed. +## Upload ID +After entering your personal details, you’ll be prompted to click a link or scan a QR code so that you can do the following: +1. Upload the front and back of your ID +2. Use your device to take a selfie and record a short video of yourself +It’s required that your ID is: +- Issued in the US +- Unexpired +## Additional Information +Check the appropriate box under **Additional Information**, accept the agreement terms, and verify that all of the information is true and accurate: +- A Beneficial Owner refers to an **individual** who owns 25% or more of the business. +- If you or another **individual** owns 25% or more of the business, please check the appropriate box +- If someone else owns 25% or more of the business, you will be prompted to provide their personal information +If no individual owns more than 25% of the company you do not need to list any beneficial owners. In that case, be sure to leave both boxes unchecked under the Beneficial Owner Additional Information section. +# How to validate the bank account +The account you set up can be found under **Settings > Account > Payment > Bank Accounts** section in either **Verifying** or **Pending** status. +If it is **Verifying**, then this means we sent you a message and need more information from you. Please check your Concierge chat which should include a message with specific details about what we require to move forward. +If it is **Pending**, then in 1-2 business days Expensify will administer 3 test transactions to your bank account. Please check your Concierge chat for further instructions. If you do not see these test transactions +After these transactions (2 withdrawals and 1 deposit) have been processed in your account, visit your Expensify Inbox, where you'll see a prompt to input the transaction amounts. +Once you've finished these steps, your business bank account is ready to use in Expensify! +# How to share a verified bank account +Only admins with access to the verified bank account can reimburse employees or pay vendor bills. To grant another admin access to the bank account in Expensify, go to **Settings > Account > Payments > Bank Accounts** and click **"Share"**. Enter their email address, and they will receive instructions from us. Please note, they must be a policy admin on a policy you also have access to in order to share the bank account with them. +When a bank account is shared, it must be revalidated with three new microtransactions to ensure the shared admin has access. This process takes 1-2 business days. Once received, the shared admin can enter the transactions via their Expensify account's Inbox tab. + +Note: A report is shared with all individuals with access to the same business bank account in Expensify for audit purposes. + + +# How to remove access to a verified bank account +This step is important when accountants and staff leave your business. +To remove an admin's access to a shared bank account, go to **Settings > Account > Payments > Shared Business Bank Accounts**. +You'll find a list of individuals who have access to the bank account. Next to each user, you'll see the option to Unshare the bank account. +# How to delete a verified bank account +If you need to delete a bank account from Expensify, run through the following steps: +1. Head to **Settings > Account > Payments** +2. Click the red **Delete** button under the corresponding bank account + +Be cautious, as if it hasn't been shared with someone else, the next user will need to set it up from the beginning. + +If the bank account is set as the settlement account for your Expensify Cards, you’ll need to designate another bank account as your settlement account under **Settings > Domains > Company Cards > Settings** before this account can be deleted. +# Deep Dive + +## Verified bank account requirements + +To add a business bank account to issue reimbursements via ACH (US), to pay invoices (US) or utilize the Expensify Card: +- You must enter a physical address for yourself, any Beneficial Owner (if one exists), and the business associated with the bank account. We **cannot** accept a PO Box or MailDrop location. +- If you are adding the bank account to Expensify, you must add it from **your** Expensify account settings. +- If you are adding a bank account to Expensify, we are required by law to verify your identity. Part of this process requires you to verify a US issued photo ID. For utilizing features related to US ACH, your idea must be issued by the United States. You and any Beneficial Owner (if one exists), must also have a US address +- You must have a valid website for your business to utilize the Expensify Card, or to pay invoices with Expensify. + +## Locked bank account +When you reimburse a report, you authorize Expensify to withdraw the funds from your account. If your bank rejects Expensify’s withdrawal request, your verified bank account is locked until the issue is resolved. + +Withdrawal requests can be rejected due to insufficient funds, or if the bank account has not been enabled for direct debit. +If you need to enable direct debits from your verified bank account, your bank will require the following details: +- The ACH CompanyIDs (1270239450 and 4270239450) +- The ACH Originator Name (Expensify) +To request to unlock the bank account, click **Fix** on your bank account under **Settings > Account > Payments > Bank Accounts**. +This sends a request to our support team to review exactly why the bank account was locked. +Please note, unlocking a bank account can take 4-5 business days to process. +## Error adding ID to Onfido +Expensify is required by both our sponsor bank and federal law to verify the identity of the individual that is initiating the movement of money. We use Onfido to confirm that the person adding a payment method is genuine and not impersonating someone else. + +If you get a generic error message that indicates, "Something's gone wrong", please go through the following steps: + +1. Ensure you are using either Safari (on iPhone) or Chrome (on Android) as your web browser. +2. Check your browser's permissions to make sure that the camera and microphone settings are set to "Allow" +3. Clear your web cache for Safari (on iPhone) or Chrome (on Android). +4. If using a corporate Wi-Fi network, confirm that your corporate firewall isn't blocking the website. +5. Make sure no other apps are overlapping your screen, such as the Facebook Messenger bubble, while recording the video. +6. On iPhone, if using iOS version 15 or later, disable the Hide IP address feature in Safari. +7. If possible, try these steps on another device +8. If you have another phone available, try to follow these steps on that device +If the issue persists, please contact your Account Manager or Concierge for further troubleshooting assistance. + +# FAQ +## What is a Beneficial Owner? + +A Beneficial Owner refers to an **individual** who owns 25% or more of the business. If no individual owns 25% or more of the business, the company does not have a Beneficial Owner. + + +## What do I do if the Beneficial Owner section only asks for personal details, but our business is owned by another company? + + +Please only indicate you have a Beneficial Owner, if it is an individual that owns 25% or more of the business. + +## Why can’t I input my address or upload my ID? + + +Are you entering a US address? When adding a verified business bank account in Expensify, the individual adding the account, and any beneficial owner (if one exists) are required to have a US address, US photo ID, and a US SSN. If you do not meet these requirements, you’ll need to have another admin add the bank account, and then share access with you once verified. + + +## Why am I being asked for documentation when adding my bank account? +When a bank account is added to Expensify, we complete a series of checks to verify the information provided to us. We conduct these checks to comply with both our sponsor bank's requirements and federal government regulations, specifically the Bank Secrecy Act / Anti-Money Laundering (BSA / AML) laws. Expensify also has anti-fraud measures in place. +If automatic verification fails, we may request manual verification, which could involve documents such as address verification for your business, a letter from your bank confirming bank account ownership, etc. + +If you have any questions regarding the documentation request you received, please contact Concierge and they will be happy to assist. + + +## I don’t see all three microtransactions I need to validate my bank account. What should I do? + +It's a good idea to wait till the end of that second business day. If you still don’t see them, please reach out to your bank and ask them to whitelist our ACH ID's **1270239450** and **4270239450**. Expensify’s ACH Originator Name is "Expensify". + +Make sure to reach out to your Account Manager or to Concierge once you have done so and our team will be able to re-trigger those 3 transactions! + From 3c41ef5638447698d902e9285a8505e5254d6b92 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Wed, 11 Oct 2023 11:43:12 +0200 Subject: [PATCH 033/254] Fixes in onyxkeys --- src/ONYXKEYS.ts | 2 +- src/libs/PolicyUtils.ts | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index b01ffbc37141..495b9c4595f9 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -370,7 +370,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.DOWNLOAD]: OnyxTypes.Download; [ONYXKEYS.COLLECTION.POLICY]: OnyxTypes.Policy; [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategory; - [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTag; + [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTags; [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories; [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMembers; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index c0bb7e539bec..7afac2ce5b67 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -13,10 +13,7 @@ type UnitRate = {rate: number}; * These are policies that we can use to create reports with in NewDot. */ function getActivePolicies(policies: OnyxCollection): Policy[] | undefined { - if (!policies) { - return; - } - return (Object.values(policies) ?? []).filter( + return (Object.values(policies ?? {}) ?? []).filter( (policy): policy is Policy => policy !== null && policy && (policy.isPolicyExpenseChatEnabled || policy.areChatRoomsEnabled) && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, ); @@ -91,8 +88,7 @@ function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry, policyMemb */ function shouldShowPolicy(policy: OnyxEntry, isOffline: boolean): boolean { return ( - policy !== null && - policy && + !!policy && policy?.isPolicyExpenseChatEnabled && policy?.role === CONST.POLICY.ROLE.ADMIN && (isOffline || policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policy.errors ?? {}).length > 0) @@ -120,7 +116,7 @@ const isPolicyAdmin = (policy: OnyxEntry): boolean => policy?.role === C * We only return members without errors. Otherwise, the members with errors would immediately be removed before the user has a chance to read the error. */ function getMemberAccountIDsForWorkspace(policyMembers: OnyxEntry, personalDetails: OnyxEntry): MemberEmailsToAccountIDs { - const memberEmailsToAccountIDs: Record = {}; + const memberEmailsToAccountIDs: MemberEmailsToAccountIDs = {}; Object.keys(policyMembers ?? {}).forEach((accountID) => { const member = policyMembers?.[accountID]; if (Object.keys(member?.errors ?? {})?.length > 0) { From bb6cd818ebb3b9963c67da198870e8ca5350d19b Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 11 Oct 2023 16:17:31 +0200 Subject: [PATCH 034/254] migrate useReportScrollManager and ReportScreenContext to TypeScript --- .../{index.native.js => index.native.ts} | 17 ++++++++++------- .../{index.js => index.ts} | 18 ++++++++++-------- src/pages/home/ReportScreenContext.js | 6 ------ src/pages/home/ReportScreenContext.ts | 17 +++++++++++++++++ 4 files changed, 37 insertions(+), 21 deletions(-) rename src/hooks/useReportScrollManager/{index.native.js => index.native.ts} (56%) rename src/hooks/useReportScrollManager/{index.js => index.ts} (58%) delete mode 100644 src/pages/home/ReportScreenContext.js create mode 100644 src/pages/home/ReportScreenContext.ts diff --git a/src/hooks/useReportScrollManager/index.native.js b/src/hooks/useReportScrollManager/index.native.ts similarity index 56% rename from src/hooks/useReportScrollManager/index.native.js rename to src/hooks/useReportScrollManager/index.native.ts index d44a40222ca5..e40ff049ca12 100644 --- a/src/hooks/useReportScrollManager/index.native.js +++ b/src/hooks/useReportScrollManager/index.native.ts @@ -1,27 +1,30 @@ import {useContext, useCallback} from 'react'; -import {ActionListContext} from '../../pages/home/ReportScreenContext'; +import {ActionListContext, ActionListContextType} from '../../pages/home/ReportScreenContext'; -function useReportScrollManager() { +function useReportScrollManager(): { + ref: ActionListContextType; + scrollToIndex: (index: number) => void; + scrollToBottom: () => void; +} { const flatListRef = useContext(ActionListContext); /** * Scroll to the provided index. * - * @param {Object} index */ - const scrollToIndex = (index) => { - if (!flatListRef.current) { + const scrollToIndex = (index: number) => { + if (!flatListRef?.current) { return; } - flatListRef.current.scrollToIndex(index); + flatListRef.current.scrollToIndex({index}); }; /** * Scroll to the bottom of the flatlist. */ const scrollToBottom = useCallback(() => { - if (!flatListRef.current) { + if (!flatListRef?.current) { return; } diff --git a/src/hooks/useReportScrollManager/index.js b/src/hooks/useReportScrollManager/index.ts similarity index 58% rename from src/hooks/useReportScrollManager/index.js rename to src/hooks/useReportScrollManager/index.ts index 9a3303504b92..c466b4050266 100644 --- a/src/hooks/useReportScrollManager/index.js +++ b/src/hooks/useReportScrollManager/index.ts @@ -1,29 +1,31 @@ import {useContext, useCallback} from 'react'; -import {ActionListContext} from '../../pages/home/ReportScreenContext'; +import {ActionListContext, ActionListContextType} from '../../pages/home/ReportScreenContext'; -function useReportScrollManager() { +function useReportScrollManager(): { + ref: ActionListContextType; + scrollToIndex: (index: number, isEditing: boolean) => void; + scrollToBottom: () => void; +} { const flatListRef = useContext(ActionListContext); /** * Scroll to the provided index. On non-native implementations we do not want to scroll when we are scrolling because * we are editing a comment. * - * @param {Object} index - * @param {Boolean} isEditing */ - const scrollToIndex = (index, isEditing) => { - if (!flatListRef.current || isEditing) { + const scrollToIndex = (index: number, isEditing: boolean) => { + if (!flatListRef?.current || isEditing) { return; } - flatListRef.current.scrollToIndex(index); + flatListRef.current.scrollToIndex({index}); }; /** * Scroll to the bottom of the flatlist. */ const scrollToBottom = useCallback(() => { - if (!flatListRef.current) { + if (!flatListRef?.current) { return; } diff --git a/src/pages/home/ReportScreenContext.js b/src/pages/home/ReportScreenContext.js deleted file mode 100644 index 1e8d30cf7585..000000000000 --- a/src/pages/home/ReportScreenContext.js +++ /dev/null @@ -1,6 +0,0 @@ -import {createContext} from 'react'; - -const ActionListContext = createContext(); -const ReactionListContext = createContext(); - -export {ActionListContext, ReactionListContext}; diff --git a/src/pages/home/ReportScreenContext.ts b/src/pages/home/ReportScreenContext.ts new file mode 100644 index 000000000000..a74c6d9797ff --- /dev/null +++ b/src/pages/home/ReportScreenContext.ts @@ -0,0 +1,17 @@ +import {RefObject, createContext} from 'react'; +import {FlatList, GestureResponderEvent} from 'react-native'; + +type ReactionListRefType = { + showReactionList: (event: GestureResponderEvent | undefined, reactionListAnchor: Element, emojiName: string, reportActionID: string) => void; + hideReactionList: () => void; + isActiveReportAction: (actionID: number | string) => boolean; +}; + +type ActionListContextType = RefObject> | null; +type ReactionListContextType = RefObject | null; + +const ActionListContext = createContext(null); +const ReactionListContext = createContext(null); + +export {ActionListContext, ReactionListContext}; +export type {ReactionListRefType, ActionListContextType, ReactionListContextType}; From 800ce65fcb7a954ce6738a111dfa4a62761a4991 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 11 Oct 2023 18:15:03 +0200 Subject: [PATCH 035/254] [TS migration] Migrate 'KeyboardShortcut' lib to TypeScript --- .../KeyDownPressListener/index.js | 9 - .../KeyDownPressListener/index.native.js | 4 - .../KeyDownPressListener/index.native.ts | 6 + .../KeyDownPressListener/index.ts | 11 + .../KeyDownPressListener/types.ts | 6 + .../bindHandlerToKeydownEvent/index.native.js | 33 --- .../bindHandlerToKeydownEvent/index.native.ts | 28 +++ .../{index.js => index.ts} | 32 +-- .../bindHandlerToKeydownEvent/types.ts | 11 + .../KeyboardShortcut/getKeyEventModifiers.js | 27 --- .../KeyboardShortcut/getKeyEventModifiers.ts | 29 +++ src/libs/KeyboardShortcut/index.js | 170 ---------------- src/libs/KeyboardShortcut/index.ts | 188 ++++++++++++++++++ ...position.js => isEnterWhileComposition.ts} | 8 +- .../ComposerWithSuggestions.js | 8 +- .../modules/react-native-key-command.d.ts | 4 +- src/types/modules/react-native.d.ts | 2 +- 17 files changed, 306 insertions(+), 270 deletions(-) delete mode 100644 src/libs/KeyboardShortcut/KeyDownPressListener/index.js delete mode 100644 src/libs/KeyboardShortcut/KeyDownPressListener/index.native.js create mode 100644 src/libs/KeyboardShortcut/KeyDownPressListener/index.native.ts create mode 100644 src/libs/KeyboardShortcut/KeyDownPressListener/index.ts create mode 100644 src/libs/KeyboardShortcut/KeyDownPressListener/types.ts delete mode 100644 src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.js create mode 100644 src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.ts rename src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/{index.js => index.ts} (52%) create mode 100644 src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/types.ts delete mode 100644 src/libs/KeyboardShortcut/getKeyEventModifiers.js create mode 100644 src/libs/KeyboardShortcut/getKeyEventModifiers.ts delete mode 100644 src/libs/KeyboardShortcut/index.js create mode 100644 src/libs/KeyboardShortcut/index.ts rename src/libs/KeyboardShortcut/{isEnterWhileComposition.js => isEnterWhileComposition.ts} (80%) diff --git a/src/libs/KeyboardShortcut/KeyDownPressListener/index.js b/src/libs/KeyboardShortcut/KeyDownPressListener/index.js deleted file mode 100644 index 4401beef1c59..000000000000 --- a/src/libs/KeyboardShortcut/KeyDownPressListener/index.js +++ /dev/null @@ -1,9 +0,0 @@ -function addKeyDownPressListner(callbackFunction) { - document.addEventListener('keydown', callbackFunction); -} - -function removeKeyDownPressListner(callbackFunction) { - document.removeEventListener('keydown', callbackFunction); -} - -export {addKeyDownPressListner, removeKeyDownPressListner}; diff --git a/src/libs/KeyboardShortcut/KeyDownPressListener/index.native.js b/src/libs/KeyboardShortcut/KeyDownPressListener/index.native.js deleted file mode 100644 index aa1ded824d22..000000000000 --- a/src/libs/KeyboardShortcut/KeyDownPressListener/index.native.js +++ /dev/null @@ -1,4 +0,0 @@ -function addKeyDownPressListner() {} -function removeKeyDownPressListner() {} - -export {addKeyDownPressListner, removeKeyDownPressListner}; diff --git a/src/libs/KeyboardShortcut/KeyDownPressListener/index.native.ts b/src/libs/KeyboardShortcut/KeyDownPressListener/index.native.ts new file mode 100644 index 000000000000..8b460a069f05 --- /dev/null +++ b/src/libs/KeyboardShortcut/KeyDownPressListener/index.native.ts @@ -0,0 +1,6 @@ +import {AddKeyDownPressListener, RemoveKeyDownPressListener} from './types'; + +const addKeyDownPressListener: AddKeyDownPressListener = () => {}; +const removeKeyDownPressListener: RemoveKeyDownPressListener = () => {}; + +export {addKeyDownPressListener, removeKeyDownPressListener}; diff --git a/src/libs/KeyboardShortcut/KeyDownPressListener/index.ts b/src/libs/KeyboardShortcut/KeyDownPressListener/index.ts new file mode 100644 index 000000000000..7e2b2a2ce319 --- /dev/null +++ b/src/libs/KeyboardShortcut/KeyDownPressListener/index.ts @@ -0,0 +1,11 @@ +import type {AddKeyDownPressListener, RemoveKeyDownPressListener} from './types'; + +const addKeyDownPressListener: AddKeyDownPressListener = (callbackFunction) => { + document.addEventListener('keydown', callbackFunction); +}; + +const removeKeyDownPressListener: RemoveKeyDownPressListener = (callbackFunction) => { + document.removeEventListener('keydown', callbackFunction); +}; + +export {addKeyDownPressListener, removeKeyDownPressListener}; diff --git a/src/libs/KeyboardShortcut/KeyDownPressListener/types.ts b/src/libs/KeyboardShortcut/KeyDownPressListener/types.ts new file mode 100644 index 000000000000..1e36051a794d --- /dev/null +++ b/src/libs/KeyboardShortcut/KeyDownPressListener/types.ts @@ -0,0 +1,6 @@ +type KeyDownPressCallback = (event: KeyboardEvent) => void; + +type AddKeyDownPressListener = (callbackFunction: KeyDownPressCallback) => void; +type RemoveKeyDownPressListener = (callbackFunction: KeyDownPressCallback) => void; + +export type {AddKeyDownPressListener, RemoveKeyDownPressListener}; diff --git a/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.js b/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.js deleted file mode 100644 index de59c819c504..000000000000 --- a/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.js +++ /dev/null @@ -1,33 +0,0 @@ -import _ from 'underscore'; -import getKeyEventModifiers from '../getKeyEventModifiers'; - -/** - * Checks if an event for that key is configured and if so, runs it. - * @param {Function} getDisplayName - * @param {Object} eventHandlers - * @param {Object} keycommandEvent - * @param {Event} event - * @private - */ -function bindHandlerToKeydownEvent(getDisplayName, eventHandlers, keycommandEvent, event) { - const eventModifiers = getKeyEventModifiers(keycommandEvent); - const displayName = getDisplayName(keycommandEvent.input, eventModifiers); - - // Loop over all the callbacks - _.every(eventHandlers[displayName], (callback) => { - // Determine if the event should bubble before executing the callback (which may have side-effects) - let shouldBubble = callback.shouldBubble || false; - if (_.isFunction(callback.shouldBubble)) { - shouldBubble = callback.shouldBubble(); - } - - if (_.isFunction(callback.callback)) { - callback.callback(event); - } - - // If the event should not bubble, short-circuit the loop - return shouldBubble; - }); -} - -export default bindHandlerToKeydownEvent; diff --git a/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.ts b/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.ts new file mode 100644 index 000000000000..d23d558fa1f8 --- /dev/null +++ b/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.ts @@ -0,0 +1,28 @@ +import getKeyEventModifiers from '../getKeyEventModifiers'; +import BindHandlerToKeydownEvent from './types'; + +/** + * Checks if an event for that key is configured and if so, runs it. + */ +const bindHandlerToKeydownEvent: BindHandlerToKeydownEvent = (getDisplayName, eventHandlers, keyCommandEvent, event) => { + const eventModifiers = getKeyEventModifiers(keyCommandEvent); + const displayName = getDisplayName(keyCommandEvent.input, eventModifiers); + + // Loop over all the callbacks + Object.values(eventHandlers[displayName]).every((callback) => { + // Determine if the event should bubble before executing the callback (which may have side-effects) + let shouldBubble: boolean | (() => void) | void = callback.shouldBubble || false; + if (typeof callback.shouldBubble === 'function') { + shouldBubble = callback.shouldBubble(); + } + + if (typeof callback.callback === 'function') { + callback.callback(event); + } + + // If the event should not bubble, short-circuit the loop + return shouldBubble; + }); +}; + +export default bindHandlerToKeydownEvent; diff --git a/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.js b/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.ts similarity index 52% rename from src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.js rename to src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.ts index 7b1cb00a408b..8f06c2fe9c2f 100644 --- a/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.js +++ b/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.ts @@ -1,44 +1,44 @@ -import _ from 'underscore'; import getKeyEventModifiers from '../getKeyEventModifiers'; import isEnterWhileComposition from '../isEnterWhileComposition'; +import BindHandlerToKeydownEvent from './types'; /** * Checks if an event for that key is configured and if so, runs it. - * @param {Function} getDisplayName - * @param {Object} eventHandlers - * @param {Object} keycommandEvent - * @param {Event} event - * @private */ -function bindHandlerToKeydownEvent(getDisplayName, eventHandlers, keycommandEvent, event) { +const bindHandlerToKeydownEvent: BindHandlerToKeydownEvent = (getDisplayName, eventHandlers, keyCommandEvent, event) => { if (!(event instanceof KeyboardEvent) || isEnterWhileComposition(event)) { return; } - const eventModifiers = getKeyEventModifiers(keycommandEvent); - const displayName = getDisplayName(keycommandEvent.input, eventModifiers); + const eventModifiers = getKeyEventModifiers(keyCommandEvent); + const displayName = getDisplayName(keyCommandEvent.input, eventModifiers); // Loop over all the callbacks - _.every(eventHandlers[displayName], (callback) => { + Object.values(eventHandlers[displayName]).every((callback) => { + const textArea = event.target as HTMLTextAreaElement; + const contentEditable = textArea?.contentEditable; + const nodeName = textArea?.nodeName; + // Early return for excludedNodes - if (_.contains(callback.excludedNodes, event.target.nodeName)) { + if (callback.excludedNodes.includes(nodeName)) { return true; } // If configured to do so, prevent input text control to trigger this event - if (!callback.captureOnInputs && (event.target.nodeName === 'INPUT' || event.target.nodeName === 'TEXTAREA' || event.target.contentEditable === 'true')) { + if (!callback.captureOnInputs && (nodeName === 'INPUT' || nodeName === 'TEXTAREA' || contentEditable === 'true')) { return true; } // Determine if the event should bubble before executing the callback (which may have side-effects) - let shouldBubble = callback.shouldBubble || false; - if (_.isFunction(callback.shouldBubble)) { + let shouldBubble: boolean | (() => void) | void = callback.shouldBubble || false; + if (typeof callback.shouldBubble === 'function') { shouldBubble = callback.shouldBubble(); } - if (_.isFunction(callback.callback)) { + if (typeof callback.callback === 'function') { callback.callback(event); } + if (callback.shouldPreventDefault) { event.preventDefault(); } @@ -46,6 +46,6 @@ function bindHandlerToKeydownEvent(getDisplayName, eventHandlers, keycommandEven // If the event should not bubble, short-circuit the loop return shouldBubble; }); -} +}; export default bindHandlerToKeydownEvent; diff --git a/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/types.ts b/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/types.ts new file mode 100644 index 000000000000..6a9aefb30676 --- /dev/null +++ b/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/types.ts @@ -0,0 +1,11 @@ +import type {EventHandler} from '../index'; + +type KeyCommandEvent = {input: string; modifierFlags?: string}; + +type GetDisplayName = (key: string, modifiers: string | string[]) => string; + +type BindHandlerToKeydownEvent = (getDisplayName: GetDisplayName, eventHandlers: Record, keyCommandEvent: KeyCommandEvent, event: KeyboardEvent) => void; + +export default BindHandlerToKeydownEvent; + +export type {KeyCommandEvent}; diff --git a/src/libs/KeyboardShortcut/getKeyEventModifiers.js b/src/libs/KeyboardShortcut/getKeyEventModifiers.js deleted file mode 100644 index 7865d51a0507..000000000000 --- a/src/libs/KeyboardShortcut/getKeyEventModifiers.js +++ /dev/null @@ -1,27 +0,0 @@ -import * as KeyCommand from 'react-native-key-command'; -import lodashGet from 'lodash/get'; - -/** - * Gets modifiers from a keyboard event. - * - * @param {Event} event - * @returns {Array} - */ -function getKeyEventModifiers(event) { - if (event.modifierFlags === lodashGet(KeyCommand, 'constants.keyModifierControl', 'keyModifierControl')) { - return ['CONTROL']; - } - if (event.modifierFlags === lodashGet(KeyCommand, 'constants.keyModifierCommand', 'keyModifierCommand')) { - return ['META']; - } - if (event.modifierFlags === lodashGet(KeyCommand, 'constants.keyModifierShiftControl', 'keyModifierShiftControl')) { - return ['CONTROL', 'Shift']; - } - if (event.modifierFlags === lodashGet(KeyCommand, 'constants.keyModifierShiftCommand', 'keyModifierShiftCommand')) { - return ['META', 'Shift']; - } - - return []; -} - -export default getKeyEventModifiers; diff --git a/src/libs/KeyboardShortcut/getKeyEventModifiers.ts b/src/libs/KeyboardShortcut/getKeyEventModifiers.ts new file mode 100644 index 000000000000..f82de725bb50 --- /dev/null +++ b/src/libs/KeyboardShortcut/getKeyEventModifiers.ts @@ -0,0 +1,29 @@ +import * as KeyCommand from 'react-native-key-command'; +import {KeyCommandEvent} from './bindHandlerToKeydownEvent/types'; + +const keyModifierControl = KeyCommand?.constants.keyModifierControl ?? 'keyModifierControl'; +const keyModifierCommand = KeyCommand?.constants.keyModifierCommand ?? 'keyModifierCommand'; +const keyModifierShiftControl = KeyCommand?.constants.keyModifierShiftControl ?? 'keyModifierShiftControl'; +const keyModifierShiftCommand = KeyCommand?.constants.keyModifierShiftCommand ?? 'keyModifierShiftCommand'; + +/** + * Gets modifiers from a keyboard event. + */ +function getKeyEventModifiers(event: KeyCommandEvent): string[] { + if (event.modifierFlags === keyModifierControl) { + return ['CONTROL']; + } + if (event.modifierFlags === keyModifierCommand) { + return ['META']; + } + if (event.modifierFlags === keyModifierShiftControl) { + return ['CONTROL', 'Shift']; + } + if (event.modifierFlags === keyModifierShiftCommand) { + return ['META', 'Shift']; + } + + return []; +} + +export default getKeyEventModifiers; diff --git a/src/libs/KeyboardShortcut/index.js b/src/libs/KeyboardShortcut/index.js deleted file mode 100644 index f91c81a1b856..000000000000 --- a/src/libs/KeyboardShortcut/index.js +++ /dev/null @@ -1,170 +0,0 @@ -import _ from 'underscore'; -import lodashGet from 'lodash/get'; -import Str from 'expensify-common/lib/str'; -import * as KeyCommand from 'react-native-key-command'; -import bindHandlerToKeydownEvent from './bindHandlerToKeydownEvent'; -import getOperatingSystem from '../getOperatingSystem'; -import CONST from '../../CONST'; - -const operatingSystem = getOperatingSystem(); - -// Handlers for the various keyboard listeners we set up -const eventHandlers = {}; - -// Documentation information for keyboard shortcuts that are displayed in the keyboard shortcuts informational modal -const documentedShortcuts = {}; - -/** - * @returns {Array} - */ -function getDocumentedShortcuts() { - return _.sortBy(_.values(documentedShortcuts), 'displayName'); -} - -/** - * Generates the normalized display name for keyboard shortcuts. - * - * @param {String} key - * @param {String|Array} modifiers - * @returns {String} - */ -function getDisplayName(key, modifiers) { - let displayName = (() => { - // Type of key is string and the type of KeyCommand.constants.* is number | string. Use _.isEqual to match different types. - if (_.isEqual(key.toLowerCase(), lodashGet(KeyCommand, 'constants.keyInputEnter', 'keyInputEnter').toString().toLowerCase())) { - return ['ENTER']; - } - if (_.isEqual(key.toLowerCase(), lodashGet(KeyCommand, 'constants.keyInputEscape', 'keyInputEscape').toString().toLowerCase())) { - return ['ESCAPE']; - } - if (_.isEqual(key.toLowerCase(), lodashGet(KeyCommand, 'constants.keyInputUpArrow', 'keyInputUpArrow').toString().toLowerCase())) { - return ['ARROWUP']; - } - if (_.isEqual(key.toLowerCase(), lodashGet(KeyCommand, 'constants.keyInputDownArrow', 'keyInputDownArrow').toString().toLowerCase())) { - return ['ARROWDOWN']; - } - if (_.isEqual(key.toLowerCase(), lodashGet(KeyCommand, 'constants.keyInputLeftArrow', 'keyInputLeftArrow').toString().toLowerCase())) { - return ['ARROWLEFT']; - } - if (_.isEqual(key.toLowerCase(), lodashGet(KeyCommand, 'constants.keyInputRightArrow', 'keyInputRightArrow').toString().toLowerCase())) { - return ['ARROWRIGHT']; - } - return [key.toUpperCase()]; - })(); - - if (_.isString(modifiers)) { - displayName.unshift(modifiers); - } else if (_.isArray(modifiers)) { - displayName = [..._.sortBy(modifiers), ...displayName]; - } - - displayName = _.map(displayName, (modifier) => lodashGet(CONST.KEYBOARD_SHORTCUT_KEY_DISPLAY_NAME, modifier.toUpperCase(), modifier)); - - return displayName.join(' + '); -} - -_.each(CONST.KEYBOARD_SHORTCUTS, (shortcut) => { - const shortcutTrigger = lodashGet(shortcut, ['trigger', operatingSystem], lodashGet(shortcut, 'trigger.DEFAULT')); - - // If there is no trigger for the current OS nor a default trigger, then we don't need to do anything - if (!shortcutTrigger) { - return; - } - - KeyCommand.addListener(shortcutTrigger, (keycommandEvent, event) => bindHandlerToKeydownEvent(getDisplayName, eventHandlers, keycommandEvent, event)); -}); - -/** - * Unsubscribes a keyboard event handler. - * - * @param {String} displayName The display name for the key combo to stop watching - * @param {String} callbackID The specific ID given to the callback at the time it was added - * @private - */ -function unsubscribe(displayName, callbackID) { - eventHandlers[displayName] = _.reject(eventHandlers[displayName], (callback) => callback.id === callbackID); - if (_.has(documentedShortcuts, displayName) && _.size(eventHandlers[displayName]) === 0) { - delete documentedShortcuts[displayName]; - } -} - -/** - * Return platform specific modifiers for keys like Control (CMD on macOS) - * - * @param {Array} keys - * @returns {Array} - */ -function getPlatformEquivalentForKeys(keys) { - return _.map(keys, (key) => { - if (!_.has(CONST.PLATFORM_SPECIFIC_KEYS, key)) { - return key; - } - - const platformModifiers = CONST.PLATFORM_SPECIFIC_KEYS[key]; - return lodashGet(platformModifiers, operatingSystem, platformModifiers.DEFAULT || key); - }); -} - -/** - * Subscribes to a keyboard event. - * @param {String} key The key to watch, i.e. 'K' or 'Escape' - * @param {Function} callback The callback to call - * @param {String} descriptionKey Translation key for shortcut description - * @param {Array} [modifiers] Can either be shift or control - * @param {Boolean} [captureOnInputs] Should we capture the event on inputs too? - * @param {Boolean|Function} [shouldBubble] Should the event bubble? - * @param {Number} [priority] The position the callback should take in the stack. 0 means top priority, and 1 means less priority than the most recently added. - * @param {Boolean} [shouldPreventDefault] Should call event.preventDefault after callback? - * @param {Array} [excludedNodes] Do not capture key events targeting excluded nodes (i.e. do not prevent default and let the event bubble) - * @returns {Function} clean up method - */ -function subscribe(key, callback, descriptionKey, modifiers = 'shift', captureOnInputs = false, shouldBubble = false, priority = 0, shouldPreventDefault = true, excludedNodes = []) { - const platformAdjustedModifiers = getPlatformEquivalentForKeys(modifiers); - const displayName = getDisplayName(key, platformAdjustedModifiers); - if (!_.has(eventHandlers, displayName)) { - eventHandlers[displayName] = []; - } - - const callbackID = Str.guid(); - eventHandlers[displayName].splice(priority, 0, { - id: callbackID, - callback, - captureOnInputs, - shouldPreventDefault, - shouldBubble, - excludedNodes, - }); - - if (descriptionKey) { - documentedShortcuts[displayName] = { - shortcutKey: key, - descriptionKey, - displayName, - modifiers, - }; - } - - return () => unsubscribe(displayName, callbackID); -} - -/** - * This module configures a global keyboard event handler. - * - * It uses a stack to store event handlers for each key combination. Some additional details: - * - * - By default, new handlers are pushed to the top of the stack. If you pass a >0 priority when subscribing to the key event, - * then the handler will get pushed further down the stack. This means that priority of 0 is higher than priority 1. - * - * - When a key event occurs, we trigger callbacks for that key starting from the top of the stack. - * By default, events do not bubble, and only the handler at the top of the stack will be executed. - * Individual callbacks can be configured with the shouldBubble parameter, to allow the next event handler on the stack execute. - * - * - Each handler has a unique callbackID, so calling the `unsubscribe` function (returned from `subscribe`) will unsubscribe the expected handler, - * regardless of its position in the stack. - */ -const KeyboardShortcut = { - subscribe, - getDocumentedShortcuts, -}; - -export default KeyboardShortcut; diff --git a/src/libs/KeyboardShortcut/index.ts b/src/libs/KeyboardShortcut/index.ts new file mode 100644 index 000000000000..e0da1a2edf15 --- /dev/null +++ b/src/libs/KeyboardShortcut/index.ts @@ -0,0 +1,188 @@ +import Str from 'expensify-common/lib/str'; +import * as KeyCommand from 'react-native-key-command'; +import bindHandlerToKeydownEvent from './bindHandlerToKeydownEvent'; +import getOperatingSystem from '../getOperatingSystem'; +import CONST from '../../CONST'; + +const operatingSystem = getOperatingSystem(); + +type EventHandler = { + id: string; + callback: (event?: KeyboardEvent) => void; + captureOnInputs: boolean; + shouldPreventDefault: boolean; + shouldBubble: boolean | (() => void); + excludedNodes: string[]; +}; + +// Handlers for the various keyboard listeners we set up +const eventHandlers: Record = {}; + +type Shortcut = { + displayName: string; + shortcutKey: string; + descriptionKey: string; + modifiers: string[]; +}; + +// Documentation information for keyboard shortcuts that are displayed in the keyboard shortcuts informational modal +const documentedShortcuts: Record = {}; + +function getDocumentedShortcuts(): Shortcut[] { + return Object.values(documentedShortcuts).sort((a, b) => a.displayName.localeCompare(b.displayName)); +} + +const keyInputEnter = KeyCommand?.constants?.keyInputEnter ?? 'keyInputEnter'; +const keyInputEscape = KeyCommand?.constants?.keyInputEscape ?? 'keyInputEscape'; +const keyInputUpArrow = KeyCommand?.constants?.keyInputUpArrow ?? 'keyInputUpArrow'; +const keyInputDownArrow = KeyCommand?.constants?.keyInputDownArrow ?? 'keyInputDownArrow'; +const keyInputLeftArrow = KeyCommand?.constants?.keyInputLeftArrow ?? 'keyInputLeftArrow'; +const keyInputRightArrow = KeyCommand?.constants?.keyInputRightArrow ?? 'keyInputRightArrow'; + +/** + * Generates the normalized display name for keyboard shortcuts. + */ +function getDisplayName(key: string, modifiers: string | string[]): string { + let displayName = (() => { + // Type of key is string and the type of KeyCommand.constants.* is number | string. + if (key.toLowerCase() === keyInputEnter.toLowerCase()) { + return ['ENTER']; + } + if (key.toLowerCase() === keyInputEscape.toLowerCase()) { + return ['ESCAPE']; + } + if (key.toLowerCase() === keyInputUpArrow.toLowerCase()) { + return ['ARROWUP']; + } + if (key.toLowerCase() === keyInputDownArrow.toLowerCase()) { + return ['ARROWDOWN']; + } + if (key.toLowerCase() === keyInputLeftArrow.toLowerCase()) { + return ['ARROWLEFT']; + } + if (key.toLowerCase() === keyInputRightArrow.toLowerCase()) { + return ['ARROWRIGHT']; + } + return [key.toUpperCase()]; + })(); + + if (typeof modifiers === 'string') { + displayName.unshift(modifiers); + } else if (Array.isArray(modifiers)) { + displayName = [...modifiers.sort(), ...displayName]; + } + + displayName = displayName.map((modifier) => CONST.KEYBOARD_SHORTCUT_KEY_DISPLAY_NAME[modifier.toUpperCase() as keyof typeof CONST.KEYBOARD_SHORTCUT_KEY_DISPLAY_NAME] ?? modifier); + + return displayName.join(' + '); +} + +Object.values(CONST.KEYBOARD_SHORTCUTS).forEach((shortcut) => { + // If there is no trigger for the current OS nor a default trigger, then we don't need to do anything + if (!('trigger' in shortcut)) { + return; + } + + const shortcutTrigger = (operatingSystem && shortcut.trigger[operatingSystem as keyof typeof shortcut.trigger]) ?? shortcut.trigger.DEFAULT; + + KeyCommand.addListener(shortcutTrigger, (keyCommandEvent, event) => bindHandlerToKeydownEvent(getDisplayName, eventHandlers, keyCommandEvent, event)); +}); + +/** + * Unsubscribes a keyboard event handler. + */ +function unsubscribe(displayName: string, callbackID: string) { + eventHandlers[displayName] = eventHandlers[displayName].filter((callback) => callback.id !== callbackID); + if (eventHandlers[displayName]?.length === 0) { + delete documentedShortcuts[displayName]; + } +} + +/** + * Return platform specific modifiers for keys like Control (CMD on macOS) + */ +function getPlatformEquivalentForKeys(keys: string[]): string[] { + return keys.map((key) => { + if (!(key in CONST.PLATFORM_SPECIFIC_KEYS)) { + return key; + } + + const platformModifiers = CONST.PLATFORM_SPECIFIC_KEYS[key as keyof typeof CONST.PLATFORM_SPECIFIC_KEYS]; + return platformModifiers?.[operatingSystem as keyof typeof platformModifiers] ?? platformModifiers.DEFAULT ?? key; + }); +} + +/** + * Subscribes to a keyboard event. + * @param key The key to watch, i.e. 'K' or 'Escape' + * @param callback The callback to call + * @param descriptionKey Translation key for shortcut description + * @param [modifiers] Can either be shift or control + * @param [captureOnInputs] Should we capture the event on inputs too? + * @param [shouldBubble] Should the event bubble? + * @param [priority] The position the callback should take in the stack. 0 means top priority, and 1 means less priority than the most recently added. + * @param [shouldPreventDefault] Should call event.preventDefault after callback? + * @param [excludedNodes] Do not capture key events targeting excluded nodes (i.e. do not prevent default and let the event bubble) + * @returns clean up method + */ +function subscribe( + key: string, + callback: () => void, + descriptionKey: string, + modifiers: string[] = ['shift'], + captureOnInputs = false, + shouldBubble = false, + priority = 0, + shouldPreventDefault = true, + excludedNodes = [], +) { + const platformAdjustedModifiers = getPlatformEquivalentForKeys(modifiers); + const displayName = getDisplayName(key, platformAdjustedModifiers); + if (!eventHandlers[displayName]) { + eventHandlers[displayName] = []; + } + + const callbackID = Str.guid(); + eventHandlers[displayName].splice(priority, 0, { + id: callbackID, + callback, + captureOnInputs, + shouldPreventDefault, + shouldBubble, + excludedNodes, + }); + + if (descriptionKey) { + documentedShortcuts[displayName] = { + shortcutKey: key, + descriptionKey, + displayName, + modifiers, + }; + } + + return () => unsubscribe(displayName, callbackID); +} + +/** + * This module configures a global keyboard event handler. + * + * It uses a stack to store event handlers for each key combination. Some additional details: + * + * - By default, new handlers are pushed to the top of the stack. If you pass a >0 priority when subscribing to the key event, + * then the handler will get pushed further down the stack. This means that priority of 0 is higher than priority 1. + * + * - When a key event occurs, we trigger callbacks for that key starting from the top of the stack. + * By default, events do not bubble, and only the handler at the top of the stack will be executed. + * Individual callbacks can be configured with the shouldBubble parameter, to allow the next event handler on the stack execute. + * + * - Each handler has a unique callbackID, so calling the `unsubscribe` function (returned from `subscribe`) will unsubscribe the expected handler, + * regardless of its position in the stack. + */ +const KeyboardShortcut = { + subscribe, + getDocumentedShortcuts, +}; + +export default KeyboardShortcut; +export type {EventHandler}; diff --git a/src/libs/KeyboardShortcut/isEnterWhileComposition.js b/src/libs/KeyboardShortcut/isEnterWhileComposition.ts similarity index 80% rename from src/libs/KeyboardShortcut/isEnterWhileComposition.js rename to src/libs/KeyboardShortcut/isEnterWhileComposition.ts index 6269440716b5..2a0a2fec110f 100644 --- a/src/libs/KeyboardShortcut/isEnterWhileComposition.js +++ b/src/libs/KeyboardShortcut/isEnterWhileComposition.ts @@ -1,13 +1,12 @@ +import {NativeSyntheticEvent} from 'react-native'; import * as Browser from '../Browser'; import CONST from '../../CONST'; /** * Check if the Enter key was pressed during IME confirmation (i.e. while the text is being composed). * See {@link https://en.wikipedia.org/wiki/Input_method} - * @param {Event} event - * @returns {boolean} */ -const isEnterWhileComposition = (event) => { +const isEnterWhileComposition = (event: KeyboardEvent): boolean => { // if on mobile chrome, the enter key event is never fired when the enter key is pressed while composition. if (Browser.isMobileChrome()) { return false; @@ -18,7 +17,8 @@ const isEnterWhileComposition = (event) => { if (CONST.BROWSER.SAFARI === Browser.getBrowser()) { return event.keyCode === 229; } - return event.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey && event.nativeEvent && event.nativeEvent.isComposing; + + return event.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey && (event as unknown as NativeSyntheticEvent)?.nativeEvent?.isComposing; }; export default isEnterWhileComposition; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js index faa710d2cd6b..7284b965de50 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js @@ -447,19 +447,19 @@ function ComposerWithSuggestions({ }, []); useEffect(() => { - const unsubscribeNavigationBlur = navigation.addListener('blur', () => KeyDownListener.removeKeyDownPressListner(focusComposerOnKeyPress)); + const unsubscribeNavigationBlur = navigation.addListener('blur', () => KeyDownListener.removeKeyDownPressListener(focusComposerOnKeyPress)); const unsubscribeNavigationFocus = navigation.addListener('focus', () => { - KeyDownListener.addKeyDownPressListner(focusComposerOnKeyPress); + KeyDownListener.addKeyDownPressListener(focusComposerOnKeyPress); setUpComposeFocusManager(); }); - KeyDownListener.addKeyDownPressListner(focusComposerOnKeyPress); + KeyDownListener.addKeyDownPressListener(focusComposerOnKeyPress); setUpComposeFocusManager(); return () => { ReportActionComposeFocusManager.clear(true); - KeyDownListener.removeKeyDownPressListner(focusComposerOnKeyPress); + KeyDownListener.removeKeyDownPressListener(focusComposerOnKeyPress); unsubscribeNavigationBlur(); unsubscribeNavigationFocus(); }; diff --git a/src/types/modules/react-native-key-command.d.ts b/src/types/modules/react-native-key-command.d.ts index f93204891e84..6af989e33814 100644 --- a/src/types/modules/react-native-key-command.d.ts +++ b/src/types/modules/react-native-key-command.d.ts @@ -21,9 +21,9 @@ declare module 'react-native-key-command' { keyModifierAlternate: 'keyModifierAlternate', } as const; - type KeyCommand = {input: string; modifierFlags: string}; + type KeyCommandEvent = {input: string; modifierFlags?: string}; - declare function addListener(keyCommand: KeyCommand, callback: (keycommandEvent: KeyCommand, event: Event) => void): () => void; + declare function addListener(keyCommand: KeyCommandEvent, callback: (keyCommandEvent: KeyCommand, event: KeyboardEvent) => void): () => void; // eslint-disable-next-line import/prefer-default-export export {constants, addListener}; diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts index ebe0974db690..b9102dfbb0ff 100644 --- a/src/types/modules/react-native.d.ts +++ b/src/types/modules/react-native.d.ts @@ -3,7 +3,7 @@ import 'react-native'; import {BootSplashModule} from '../../libs/BootSplash/types'; declare module 'react-native' { - interface TextInput { + interface TextI2put { // Typescript type declaration is missing in React Native for setting text selection. setSelection: (start: number, end: number) => void; } From 36ef1c56ec17b86fceddeebf6e50619cb983d2b6 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 11 Oct 2023 18:18:12 +0200 Subject: [PATCH 036/254] Revert a typo --- src/types/modules/react-native.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts index b9102dfbb0ff..6e75a5a0077b 100644 --- a/src/types/modules/react-native.d.ts +++ b/src/types/modules/react-native.d.ts @@ -3,7 +3,7 @@ import 'react-native'; import {BootSplashModule} from '../../libs/BootSplash/types'; declare module 'react-native' { - interface TextI2put { + interface TextIput { // Typescript type declaration is missing in React Native for setting text selection. setSelection: (start: number, end: number) => void; } From c3df845adfe24f1377d7d9ca03fdb21b476ea566 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Wed, 11 Oct 2023 23:07:57 -0400 Subject: [PATCH 037/254] resolve merge conflicts --- src/pages/home/report/ContextMenu/ContextMenuActions.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 01b4e5a64b04..0c2ace646cdb 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -148,13 +148,12 @@ export default [ getDescription: () => {}, }, { -<<<<<<< HEAD isAnonymousAction: false, textTranslateKey: 'reportActionContextMenu.subscribeToThread', // textTranslateKey: lodashGet(reportAction, 'childReportNotificationPreference', '0'), icon: Expensicons.Bell, successTextTranslateKey: '', - successIcon: null,g + successIcon: null, shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID) => { const subscribed = lodashGet(reportAction, 'childReportNotificationPreference', '') !== "hidden"; if (type !== CONTEXT_MENU_TYPES.REPORT_ACTION) { @@ -213,10 +212,7 @@ export default [ getDescription: () => {}, }, { - isAnonymousAction: false, -======= isAnonymousAction: true, ->>>>>>> main textTranslateKey: 'reportActionContextMenu.copyURLToClipboard', icon: Expensicons.Copy, successTextTranslateKey: 'reportActionContextMenu.copied', From 5248bc6e175d9bf72e448fd32b2b0d2d013ebaff Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 5 Oct 2023 16:36:44 +0200 Subject: [PATCH 038/254] Form validation fixes --- src/components/Form/FormProvider.js | 12 ++++++++++++ src/components/Form/FormWrapper.js | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/Form/FormProvider.js b/src/components/Form/FormProvider.js index 76471aeab51a..c20cc45f2fd4 100644 --- a/src/components/Form/FormProvider.js +++ b/src/components/Form/FormProvider.js @@ -186,6 +186,18 @@ function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, c propsToParse.onTouched(event); } }, + onPress: (event) => { + setTouchedInput(inputID); + if (_.isFunction(propsToParse.onPress)) { + propsToParse.onPress(event); + } + }, + onPressIn: (event) => { + setTouchedInput(inputID); + if (_.isFunction(propsToParse.onPressIn)) { + propsToParse.onPressIn(event); + } + }, onBlur: (event) => { // Only run validation when user proactively blurs the input. if (Visibility.isVisible() && Visibility.hasFocus()) { diff --git a/src/components/Form/FormWrapper.js b/src/components/Form/FormWrapper.js index 44bfee1a9e4a..d7317a673795 100644 --- a/src/components/Form/FormWrapper.js +++ b/src/components/Form/FormWrapper.js @@ -36,7 +36,7 @@ const propTypes = { isLoading: PropTypes.bool, /** Server side errors keyed by microtime */ - errors: PropTypes.objectOf(PropTypes.oneOf([PropTypes.string, PropTypes.arrayOf(PropTypes.string)])), + errors: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)])), /** Field-specific server side errors keyed by microtime */ errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), @@ -59,7 +59,7 @@ const propTypes = { /** Custom content to display in the footer after submit button */ footerContent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), - errors: PropTypes.objectOf(PropTypes.string).isRequired, + errors: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)])), inputRefs: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object])).isRequired, }; From 5b4a41982b8edc7c38f8b2f36761279bb990d114 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Fri, 6 Oct 2023 11:10:27 +0200 Subject: [PATCH 039/254] Form docs improvements --- src/components/Form/FormProvider.js | 2 +- src/components/Form/FormWrapper.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Form/FormProvider.js b/src/components/Form/FormProvider.js index c20cc45f2fd4..2a5c5c5a5ff1 100644 --- a/src/components/Form/FormProvider.js +++ b/src/components/Form/FormProvider.js @@ -240,7 +240,7 @@ function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, c }, }; }, - [errors, formState, inputValues, onValidate, setTouchedInput, shouldValidateOnBlur, shouldValidateOnChange], + [errors, formState, hasServerError, inputValues, onValidate, setTouchedInput, shouldValidateOnBlur, shouldValidateOnChange], ); const value = useMemo(() => ({registerInput}), [registerInput]); diff --git a/src/components/Form/FormWrapper.js b/src/components/Form/FormWrapper.js index d7317a673795..7b8cda333507 100644 --- a/src/components/Form/FormWrapper.js +++ b/src/components/Form/FormWrapper.js @@ -59,7 +59,7 @@ const propTypes = { /** Custom content to display in the footer after submit button */ footerContent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), - errors: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)])), + errors: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)])).isRequired, inputRefs: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object])).isRequired, }; From bd873794098eb82cde1649312b695398ba70e3a5 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Fri, 6 Oct 2023 15:00:02 +0200 Subject: [PATCH 040/254] Fix prop types --- src/components/Form/FormWrapper.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Form/FormWrapper.js b/src/components/Form/FormWrapper.js index 7b8cda333507..4358078f5229 100644 --- a/src/components/Form/FormWrapper.js +++ b/src/components/Form/FormWrapper.js @@ -36,7 +36,7 @@ const propTypes = { isLoading: PropTypes.bool, /** Server side errors keyed by microtime */ - errors: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)])), + errors: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]))])), /** Field-specific server side errors keyed by microtime */ errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), @@ -59,7 +59,7 @@ const propTypes = { /** Custom content to display in the footer after submit button */ footerContent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), - errors: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)])).isRequired, + errors: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]))])).isRequired, inputRefs: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object])).isRequired, }; @@ -84,6 +84,7 @@ function FormWrapper(props) { const latestErrorMessage = ErrorUtils.getLatestErrorMessage(formState); return typeof latestErrorMessage === 'string' ? latestErrorMessage : ''; }, [formState]); + console.log({errors}) const scrollViewContent = useCallback( (safeAreaPaddingBottomStyle) => ( From 10ea700bb9f2d363a43127eeb7859ac1fbf5df70 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Fri, 6 Oct 2023 15:00:28 +0200 Subject: [PATCH 041/254] Remove console log --- src/components/Form/FormWrapper.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Form/FormWrapper.js b/src/components/Form/FormWrapper.js index 4358078f5229..c00c0458d81d 100644 --- a/src/components/Form/FormWrapper.js +++ b/src/components/Form/FormWrapper.js @@ -84,7 +84,6 @@ function FormWrapper(props) { const latestErrorMessage = ErrorUtils.getLatestErrorMessage(formState); return typeof latestErrorMessage === 'string' ? latestErrorMessage : ''; }, [formState]); - console.log({errors}) const scrollViewContent = useCallback( (safeAreaPaddingBottomStyle) => ( From 56b46a12f5b79a0fbf221f4ace4cf2e85c5a1a64 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Thu, 12 Oct 2023 10:55:58 +0200 Subject: [PATCH 042/254] extract return values to a separate type --- src/hooks/useReportScrollManager/index.native.ts | 6 ++++-- src/hooks/useReportScrollManager/index.ts | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/hooks/useReportScrollManager/index.native.ts b/src/hooks/useReportScrollManager/index.native.ts index e40ff049ca12..bda8d1cbdee5 100644 --- a/src/hooks/useReportScrollManager/index.native.ts +++ b/src/hooks/useReportScrollManager/index.native.ts @@ -1,11 +1,13 @@ import {useContext, useCallback} from 'react'; import {ActionListContext, ActionListContextType} from '../../pages/home/ReportScreenContext'; -function useReportScrollManager(): { +type ReportScrollManagerData = { ref: ActionListContextType; scrollToIndex: (index: number) => void; scrollToBottom: () => void; -} { +}; + +function useReportScrollManager(): ReportScrollManagerData { const flatListRef = useContext(ActionListContext); /** diff --git a/src/hooks/useReportScrollManager/index.ts b/src/hooks/useReportScrollManager/index.ts index c466b4050266..2cf3d1733360 100644 --- a/src/hooks/useReportScrollManager/index.ts +++ b/src/hooks/useReportScrollManager/index.ts @@ -1,11 +1,13 @@ import {useContext, useCallback} from 'react'; import {ActionListContext, ActionListContextType} from '../../pages/home/ReportScreenContext'; -function useReportScrollManager(): { +type ReportScrollManagerData = { ref: ActionListContextType; scrollToIndex: (index: number, isEditing: boolean) => void; scrollToBottom: () => void; -} { +}; + +function useReportScrollManager(): ReportScrollManagerData { const flatListRef = useContext(ActionListContext); /** From 568767c93df919b79f7bfc358d4c230af9e421dd Mon Sep 17 00:00:00 2001 From: Hardik Choudhary Date: Thu, 12 Oct 2023 17:16:50 +0530 Subject: [PATCH 043/254] RTL Text gets renderd properly --- src/components/Composer/index.js | 2 +- src/libs/convertToLTR/index.ts | 10 +++++++++- .../ReportActionCompose/ComposerWithSuggestions.js | 5 +++-- src/styles/styles.js | 4 ++++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/components/Composer/index.js b/src/components/Composer/index.js index ad7a84cc1828..97a592a546ab 100755 --- a/src/components/Composer/index.js +++ b/src/components/Composer/index.js @@ -457,7 +457,7 @@ function Composer({ placeholderTextColor={themeColors.placeholderText} ref={(el) => (textInput.current = el)} selection={selection} - style={inputStyleMemo} + style={(Browser.isMobileSafari() || Browser.isSafari()) ? [inputStyleMemo, styles.rtlTextRenderForSafari] : [inputStyleMemo]} value={value} forwardedRef={forwardedRef} defaultValue={defaultValue} diff --git a/src/libs/convertToLTR/index.ts b/src/libs/convertToLTR/index.ts index 58d8be93836e..1742f4bf2135 100644 --- a/src/libs/convertToLTR/index.ts +++ b/src/libs/convertToLTR/index.ts @@ -1,5 +1,13 @@ +import CONST from '../../CONST'; import ConvertToLTR from './types'; -const convertToLTR: ConvertToLTR = (text) => text; +const convertToLTR: ConvertToLTR = (text) => { + // Check if the text already starts with the LTR marker (if so, return as is). + if (text.startsWith(CONST.UNICODE.LTR)) { + return text; + } + // // Add the LTR marker to the beginning of the text. + return `${CONST.UNICODE.LTR}${text}`; +}; export default convertToLTR; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js index faa710d2cd6b..043cda5d7562 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js @@ -1,5 +1,5 @@ import React, {useEffect, useCallback, useState, useRef, useMemo, useImperativeHandle} from 'react'; -import {View, NativeModules, findNodeHandle} from 'react-native'; +import {View, NativeModules, findNodeHandle, Platform} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import lodashGet from 'lodash/get'; @@ -33,6 +33,7 @@ import withKeyboardState from '../../../../components/withKeyboardState'; import {propTypes, defaultProps} from './composerWithSuggestionsProps'; import focusWithDelay from '../../../../libs/focusWithDelay'; import useDebounce from '../../../../hooks/useDebounce'; +import convertToLTR from '../../../../libs/convertToLTR'; const {RNTextInputReset} = NativeModules; @@ -212,7 +213,7 @@ function ComposerWithSuggestions({ } emojisPresentBefore.current = emojis; setIsCommentEmpty(!!newComment.match(/^(\s)*$/)); - setValue(newComment); + setValue(Platform.OS === 'android' ?newComment : convertToLTR(newComment)); if (commentValue !== newComment) { // Ensure emoji suggestions are hidden even when the selection is not changed (so calculateEmojiSuggestion would not be called). if (suggestionsRef.current) { diff --git a/src/styles/styles.js b/src/styles/styles.js index 8fa81cd98b21..1954205c2b59 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -195,6 +195,10 @@ const styles = (theme) => ({ alignItems: 'center', }, + rtlTextRenderForSafari: { + textAlign:"left", + }, + emojiSuggestionsEmoji: { fontSize: variables.fontSizeMedium, width: 51, From f83a33f0aa15819df5c3075bec5bb794677ab108 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Thu, 12 Oct 2023 14:29:51 +0200 Subject: [PATCH 044/254] Remove dead code --- src/components/withAnimatedRef.js | 33 ------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 src/components/withAnimatedRef.js diff --git a/src/components/withAnimatedRef.js b/src/components/withAnimatedRef.js deleted file mode 100644 index 71ef130b9ce7..000000000000 --- a/src/components/withAnimatedRef.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import {useAnimatedRef} from 'react-native-reanimated'; -import getComponentDisplayName from '../libs/getComponentDisplayName'; -import refPropTypes from './refPropTypes'; - -export default function withAnimatedRef(WrappedComponent) { - function WithAnimatedRef(props) { - const animatedRef = useAnimatedRef(); - return ( - - ); - } - WithAnimatedRef.displayName = `withAnimatedRef(${getComponentDisplayName(WrappedComponent)})`; - WithAnimatedRef.propTypes = { - forwardedRef: refPropTypes, - }; - WithAnimatedRef.defaultProps = { - forwardedRef: undefined, - }; - - return React.forwardRef((props, ref) => ( - - )); -} From eab18e6e62e0ef4944e164b53a1f6706633fe1d1 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 12 Oct 2023 15:36:48 +0200 Subject: [PATCH 045/254] Refactor roomNameInput --- src/components/RoomNameInput/index.js | 7 +++++-- src/components/RoomNameInput/index.native.js | 7 +++++-- src/components/RoomNameInput/roomNameInputPropTypes.js | 4 ++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/components/RoomNameInput/index.js b/src/components/RoomNameInput/index.js index 37d131108e9e..32f162d1cf24 100644 --- a/src/components/RoomNameInput/index.js +++ b/src/components/RoomNameInput/index.js @@ -5,8 +5,9 @@ import TextInput from '../TextInput'; import useLocalize from '../../hooks/useLocalize'; import * as roomNameInputPropTypes from './roomNameInputPropTypes'; import * as RoomNameInputUtils from '../../libs/RoomNameInputUtils'; +import InputWrapper from "../Form/InputWrapper"; -function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, value, onBlur, onChangeText, onInputChange, shouldDelayFocus}) { +function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, value, onBlur, onChangeText, onInputChange, shouldDelayFocus, inputID}) { const {translate} = useLocalize(); const [selection, setSelection] = useState(); @@ -43,7 +44,9 @@ function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, }; return ( - {}, - inputID: undefined, onBlur: () => {}, autoFocus: false, shouldDelayFocus: false, From 40a6da22e47632e56b2c9cedd7f387f8cbc4af50 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 12 Oct 2023 15:37:09 +0200 Subject: [PATCH 046/254] Refactor RoomNamePage --- src/pages/settings/Report/RoomNamePage.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Report/RoomNamePage.js b/src/pages/settings/Report/RoomNamePage.js index 985d83e7fd95..cb14ca336d57 100644 --- a/src/pages/settings/Report/RoomNamePage.js +++ b/src/pages/settings/Report/RoomNamePage.js @@ -21,6 +21,7 @@ import * as Report from '../../../libs/actions/Report'; import RoomNameInput from '../../../components/RoomNameInput'; import * as ReportUtils from '../../../libs/ReportUtils'; import FullPageNotFoundView from '../../../components/BlockingViews/FullPageNotFoundView'; +import FormProvider from "../../../components/Form/FormProvider"; const propTypes = { ...withLocalizePropTypes, @@ -90,7 +91,7 @@ function RoomNamePage(props) { title={translate('newRoomPage.roomName')} onBackButtonPress={() => Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report.reportID))} /> -
Report.updatePolicyRoomNameAndNavigate(report, values.roomName)} @@ -106,7 +107,7 @@ function RoomNamePage(props) { isFocused={isFocused} /> -
+
); From e35bddca007bd7445d5a1323aba3c6b099d95066 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 12 Oct 2023 15:37:28 +0200 Subject: [PATCH 047/254] Refactor WorkspaceNewRoomPage --- src/pages/workspace/WorkspaceNewRoomPage.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index e1fbab65486b..8c6d5c60db16 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -26,6 +26,8 @@ import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoun import compose from '../../libs/compose'; import variables from '../../styles/variables'; import useDelayedInputFocus from '../../hooks/useDelayedInputFocus'; +import FormProvider from "../../components/Form/FormProvider"; +import InputWrapper from "../../components/Form/InputWrapper"; const propTypes = { /** All reports shared with the user */ @@ -172,7 +174,7 @@ function WorkspaceNewRoomPage(props) { // This is because when wrapping whole screen the screen was freezing when changing Tabs. keyboardVerticalOffset={variables.contentHeaderHeight + variables.tabSelectorButtonHeight + variables.tabSelectorButtonPadding + insets.top} > -
- {isPolicyAdmin && ( - )} - {visibilityDescription} - + )} From c2a4275d851ff9b560e389019518ec0025073260 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 12 Oct 2023 15:57:02 +0200 Subject: [PATCH 048/254] Fix a type x2 --- src/types/modules/react-native.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts index 6e75a5a0077b..ebe0974db690 100644 --- a/src/types/modules/react-native.d.ts +++ b/src/types/modules/react-native.d.ts @@ -3,7 +3,7 @@ import 'react-native'; import {BootSplashModule} from '../../libs/BootSplash/types'; declare module 'react-native' { - interface TextIput { + interface TextInput { // Typescript type declaration is missing in React Native for setting text selection. setSelection: (start: number, end: number) => void; } From 1715578eaf4ee453a35119009e025792dcc32f7a Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 12 Oct 2023 16:18:39 +0200 Subject: [PATCH 049/254] Address code review --- src/libs/SidebarUtils.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index ca7ddf38ca63..a7b807809cfa 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -401,7 +401,7 @@ function getOptionData( // If the last actor's details are not currently saved in Onyx Collection, // then try to get that from the last report action if that action is valid // to get data from. - let lastActorDetails: ActorDetails | null = report.lastActorAccountID ? personalDetails[report.lastActorAccountID] : null; + let lastActorDetails: ActorDetails | null = report.lastActorAccountID && personalDetails?.[report.lastActorAccountID] ? personalDetails[report.lastActorAccountID] : null; if (!lastActorDetails && visibleReportActionItems[report.reportID]) { const lastActorDisplayName = visibleReportActionItems[report.reportID]?.person?.[0]?.text; lastActorDetails = lastActorDisplayName @@ -411,11 +411,10 @@ function getOptionData( } : null; } - const lastActorDisplayName = - hasMultipleParticipants && lastActorDetails && lastActorDetails.accountID && Number(lastActorDetails.accountID) !== currentUserAccountID ? lastActorDetails.displayName : ''; + const lastActorDisplayName = hasMultipleParticipants && lastActorDetails?.accountID && Number(lastActorDetails.accountID) !== currentUserAccountID ? lastActorDetails.displayName : ''; let lastMessageText = lastMessageTextFromReport; - const reportAction = lastReportActions[report.reportID]; + const reportAction = lastReportActions?.[report.reportID]; if (result.isArchivedRoom && reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED) { const archiveReason = reportAction?.originalMessage?.reason ?? CONST.REPORT.ARCHIVE_REASON.DEFAULT; @@ -435,7 +434,7 @@ function getOptionData( result.alternateText = `${Localize.translate(preferredLocale, 'task.messages.reopened')}: ${report.reportName}`; } else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED) { result.alternateText = `${Localize.translate(preferredLocale, 'task.messages.completed')}: ${report.reportName}`; - } else if (lastAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && lastActorDisplayName && lastMessageTextFromReport) { + } else if (lastAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && lastActorDisplayName && lastMessageTextFromReport) { result.alternateText = `${lastActorDisplayName}: ${lastMessageText}`; } else { result.alternateText = lastMessageTextFromReport.length > 0 ? lastMessageText : Localize.translate(preferredLocale, 'report.noActivityYet'); From 2a252907d746867b3bd48fb0d6aae6adcd5a8964 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 12 Oct 2023 16:26:49 +0200 Subject: [PATCH 050/254] Add errors to ReportActionBase --- src/types/onyx/ReportAction.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index 3d32a13dd951..a2939e5d365f 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -66,8 +66,8 @@ type ReportActionBase = { /** Whether we have received a response back from the server */ isLoading?: boolean; - /** Error message that's come back from the server. */ - error?: string; + /** An error message to display to the user */ + errors?: OnyxCommon.Errors; /** accountIDs of the people to which the whisper was sent to (if any). Returns empty array if it is not a whisper */ whisperedToAccountIDs?: number[]; From 0e7d40daf212782d4209d5ec6d2ab455c6515700 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 12 Oct 2023 18:11:47 +0200 Subject: [PATCH 051/254] Add optional chaining to fix tests --- src/libs/SidebarUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index a7b807809cfa..955725ad899c 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -415,7 +415,7 @@ function getOptionData( let lastMessageText = lastMessageTextFromReport; const reportAction = lastReportActions?.[report.reportID]; - if (result.isArchivedRoom && reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED) { + if (result.isArchivedRoom && reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED) { const archiveReason = reportAction?.originalMessage?.reason ?? CONST.REPORT.ARCHIVE_REASON.DEFAULT; lastMessageText = Localize.translate(preferredLocale, `reportArchiveReasons.${archiveReason}`, { From ccd959e2f42eebd39e8f37a70c5d69571990e437 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 12 Oct 2023 18:20:04 +0200 Subject: [PATCH 052/254] Fix remaining tests --- src/libs/SidebarUtils.ts | 5 ++--- tests/unit/SidebarTest.js | 46 --------------------------------------- 2 files changed, 2 insertions(+), 49 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 955725ad899c..5a3a6c066a8c 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -415,9 +415,8 @@ function getOptionData( let lastMessageText = lastMessageTextFromReport; const reportAction = lastReportActions?.[report.reportID]; - if (result.isArchivedRoom && reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED) { - const archiveReason = reportAction?.originalMessage?.reason ?? CONST.REPORT.ARCHIVE_REASON.DEFAULT; - + if (result.isArchivedRoom) { + const archiveReason = (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED && reportAction?.originalMessage?.reason) || CONST.REPORT.ARCHIVE_REASON.DEFAULT; lastMessageText = Localize.translate(preferredLocale, `reportArchiveReasons.${archiveReason}`, { displayName: PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails, 'displayName'), policyName: ReportUtils.getPolicyName(report, false, policy), diff --git a/tests/unit/SidebarTest.js b/tests/unit/SidebarTest.js index 1b5daa323da5..1ad10a3bfaf0 100644 --- a/tests/unit/SidebarTest.js +++ b/tests/unit/SidebarTest.js @@ -81,51 +81,5 @@ describe('Sidebar', () => { }) ); }); - it('renders the policy deleted archive reason as the preview message of the chat', () => { - const report = { - ...LHNTestUtils.getFakeReport(['email1@test.com', 'email2@test.com'], 3, true), - policyName: 'Vikings Policy', - chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, - statusNum: CONST.REPORT.STATUS.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, - }; - const action = { - ...LHNTestUtils.getFakeReportAction('email1@test.com', 3, true), - actionName: 'CLOSED', - originalMessage: { - policyName: 'Vikings Policy', - reason: 'policyDeleted', - }, - }; - - // Given the user is in all betas - const betas = [CONST.BETAS.DEFAULT_ROOMS, CONST.BETAS.POLICY_ROOMS]; - LHNTestUtils.getDefaultRenderedSidebarLinks('0'); - return ( - waitForBatchedUpdates() - // When Onyx is updated with the data and the sidebar re-renders - .then(() => - Onyx.multiSet({ - [ONYXKEYS.BETAS]: betas, - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, - [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, - [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, - [`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`]: report, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`]: {[action.reportActionId]: action}, - }), - ) - .then(() => { - const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); - const displayNames = screen.queryAllByLabelText(hintText); - expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Report (archived)'); - - const hintMessagePreviewText = Localize.translateLocal('accessibilityHints.lastChatMessagePreview'); - const messagePreviewTexts = screen.queryAllByLabelText(hintMessagePreviewText); - expect(lodashGet(messagePreviewTexts, [0, 'props', 'children'])).toBe( - 'This workspace chat is no longer active because Vikings Policy is no longer an active workspace.', - ); - }) - ); - }); }); }); From 89d24014bd09a8d2930a6e611ba6578706f16b46 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 12 Oct 2023 18:40:11 +0200 Subject: [PATCH 053/254] Fix android --- src/libs/KeyboardShortcut/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libs/KeyboardShortcut/index.ts b/src/libs/KeyboardShortcut/index.ts index aa8a8b20e563..3109ccda8aaf 100644 --- a/src/libs/KeyboardShortcut/index.ts +++ b/src/libs/KeyboardShortcut/index.ts @@ -32,12 +32,12 @@ function getDocumentedShortcuts(): Shortcut[] { return Object.values(documentedShortcuts).sort((a, b) => a.displayName.localeCompare(b.displayName)); } -const keyInputEnter = KeyCommand?.constants?.keyInputEnter ?? 'keyInputEnter'; -const keyInputEscape = KeyCommand?.constants?.keyInputEscape ?? 'keyInputEscape'; -const keyInputUpArrow = KeyCommand?.constants?.keyInputUpArrow ?? 'keyInputUpArrow'; -const keyInputDownArrow = KeyCommand?.constants?.keyInputDownArrow ?? 'keyInputDownArrow'; -const keyInputLeftArrow = KeyCommand?.constants?.keyInputLeftArrow ?? 'keyInputLeftArrow'; -const keyInputRightArrow = KeyCommand?.constants?.keyInputRightArrow ?? 'keyInputRightArrow'; +const keyInputEnter = KeyCommand?.constants?.keyInputEnter?.toString() ?? 'keyInputEnter'; +const keyInputEscape = KeyCommand?.constants?.keyInputEscape?.toString() ?? 'keyInputEscape'; +const keyInputUpArrow = KeyCommand?.constants?.keyInputUpArrow?.toString() ?? 'keyInputUpArrow'; +const keyInputDownArrow = KeyCommand?.constants?.keyInputDownArrow?.toString() ?? 'keyInputDownArrow'; +const keyInputLeftArrow = KeyCommand?.constants?.keyInputLeftArrow?.toString() ?? 'keyInputLeftArrow'; +const keyInputRightArrow = KeyCommand?.constants?.keyInputRightArrow?.toString() ?? 'keyInputRightArrow'; /** * Generates the normalized display name for keyboard shortcuts. From 4f6af3d076ce65aaea0c2f25b422c065ed635ee4 Mon Sep 17 00:00:00 2001 From: Corinne Ofstad Date: Thu, 12 Oct 2023 15:09:05 -0500 Subject: [PATCH 054/254] Update docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md Co-authored-by: Rushat Gabhane --- .../business-bank-accounts/Business-Bank-Accounts-USD.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md index 29667f14397d..fae7bde22810 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md @@ -9,6 +9,7 @@ Once you connect your business bank account, you can: - Settle company bills via direct transfer - Accept invoice payments through direct transfer - Access the Expensify Card + # How to add a verified business bank account To connect a business bank account to Expensify, follow the below steps: 1. Go to **Settings > Account > Payments** From 9c2335d0b80dee2a78b2b80cc18cae42c7775df2 Mon Sep 17 00:00:00 2001 From: Corinne Ofstad Date: Thu, 12 Oct 2023 15:09:15 -0500 Subject: [PATCH 055/254] Update docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md Co-authored-by: Rushat Gabhane --- .../business-bank-accounts/Business-Bank-Accounts-USD.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md index fae7bde22810..301d299d649b 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md @@ -25,6 +25,7 @@ To connect a business bank account to Expensify, follow the below steps: - If your bank requires additional security measures, you will be directed to obtain and enter a security code - If you have more than one account available to choose from, you will be directed to choose the desired account Next, to verify the bank account, you’ll enter some details about the business as well as some personal information. + ## Enter company information This is where you’ll add the legal business name as well as several other company details. ### Company address From 31c109cd6ac1c05a4b025e638322968bada0acea Mon Sep 17 00:00:00 2001 From: Corinne Ofstad Date: Thu, 12 Oct 2023 15:09:28 -0500 Subject: [PATCH 056/254] Update docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md Co-authored-by: Rushat Gabhane --- .../business-bank-accounts/Business-Bank-Accounts-USD.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md index 301d299d649b..96bb636ce222 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md @@ -28,6 +28,7 @@ Next, to verify the bank account, you’ll enter some details about the business ## Enter company information This is where you’ll add the legal business name as well as several other company details. + ### Company address The company address must: - Be located in the US From 81842f889c9f8ef2da289422f6f4c4e67dc7f158 Mon Sep 17 00:00:00 2001 From: Corinne Ofstad Date: Thu, 12 Oct 2023 15:09:36 -0500 Subject: [PATCH 057/254] Update docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md Co-authored-by: Rushat Gabhane --- .../business-bank-accounts/Business-Bank-Accounts-USD.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md index 96bb636ce222..8f41dcfb9598 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md @@ -34,6 +34,7 @@ The company address must: - Be located in the US - Be a physical location If you input a maildrop address (PO box, UPS Store, etc.), the address will likely be flagged for review and adding the bank account to Expensify will be delayed. + ### Tax Identification Number This is the identification number that was assigned to the business by the IRS. ### Company website From a04eb50a1a781bc0383c36fd8bdaf31fa588a6cb Mon Sep 17 00:00:00 2001 From: Corinne Ofstad Date: Thu, 12 Oct 2023 15:09:52 -0500 Subject: [PATCH 058/254] Update docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md Co-authored-by: Rushat Gabhane --- .../business-bank-accounts/Business-Bank-Accounts-USD.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md index 8f41dcfb9598..37af9dae22bf 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md @@ -47,6 +47,7 @@ Whoever is connecting the bank account to Expensify, must enter their details un - The address must be located in the US - The SSN must be US-issued This does not need to be a signor on the bank account. If someone other than the Expensify account holder enters their personal information in this section, the details will be flagged for review and adding the bank account to Expensify will be delayed. + ## Upload ID After entering your personal details, you’ll be prompted to click a link or scan a QR code so that you can do the following: 1. Upload the front and back of your ID From 266928fb35ad0b417d47c3de1b0d75bacd38b82d Mon Sep 17 00:00:00 2001 From: Corinne Ofstad Date: Thu, 12 Oct 2023 15:10:00 -0500 Subject: [PATCH 059/254] Update docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md Co-authored-by: Rushat Gabhane --- .../business-bank-accounts/Business-Bank-Accounts-USD.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md index 37af9dae22bf..0b1e79e922cd 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md @@ -61,6 +61,7 @@ Check the appropriate box under **Additional Information**, accept the agreement - If you or another **individual** owns 25% or more of the business, please check the appropriate box - If someone else owns 25% or more of the business, you will be prompted to provide their personal information If no individual owns more than 25% of the company you do not need to list any beneficial owners. In that case, be sure to leave both boxes unchecked under the Beneficial Owner Additional Information section. + # How to validate the bank account The account you set up can be found under **Settings > Account > Payment > Bank Accounts** section in either **Verifying** or **Pending** status. If it is **Verifying**, then this means we sent you a message and need more information from you. Please check your Concierge chat which should include a message with specific details about what we require to move forward. From ba56c91715faf75b416ac658e30cca02dfd74c6a Mon Sep 17 00:00:00 2001 From: Corinne Ofstad Date: Thu, 12 Oct 2023 15:10:12 -0500 Subject: [PATCH 060/254] Update docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md Co-authored-by: Rushat Gabhane --- .../business-bank-accounts/Business-Bank-Accounts-USD.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md index 0b1e79e922cd..7fbec3e66083 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md @@ -55,6 +55,7 @@ After entering your personal details, you’ll be prompted to click a link or sc It’s required that your ID is: - Issued in the US - Unexpired + ## Additional Information Check the appropriate box under **Additional Information**, accept the agreement terms, and verify that all of the information is true and accurate: - A Beneficial Owner refers to an **individual** who owns 25% or more of the business. From e8c91ef1a79eb2b678f9696796fbd58e88963858 Mon Sep 17 00:00:00 2001 From: Corinne Ofstad Date: Thu, 12 Oct 2023 15:10:23 -0500 Subject: [PATCH 061/254] Update docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md Co-authored-by: Rushat Gabhane --- .../business-bank-accounts/Business-Bank-Accounts-USD.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md index 7fbec3e66083..44c5da286f66 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md @@ -69,6 +69,7 @@ If it is **Verifying**, then this means we sent you a message and need more info If it is **Pending**, then in 1-2 business days Expensify will administer 3 test transactions to your bank account. Please check your Concierge chat for further instructions. If you do not see these test transactions After these transactions (2 withdrawals and 1 deposit) have been processed in your account, visit your Expensify Inbox, where you'll see a prompt to input the transaction amounts. Once you've finished these steps, your business bank account is ready to use in Expensify! + # How to share a verified bank account Only admins with access to the verified bank account can reimburse employees or pay vendor bills. To grant another admin access to the bank account in Expensify, go to **Settings > Account > Payments > Bank Accounts** and click **"Share"**. Enter their email address, and they will receive instructions from us. Please note, they must be a policy admin on a policy you also have access to in order to share the bank account with them. When a bank account is shared, it must be revalidated with three new microtransactions to ensure the shared admin has access. This process takes 1-2 business days. Once received, the shared admin can enter the transactions via their Expensify account's Inbox tab. From e0180357bd1d7684b101edf26a55d1ab526abe1c Mon Sep 17 00:00:00 2001 From: Corinne Ofstad Date: Thu, 12 Oct 2023 15:10:31 -0500 Subject: [PATCH 062/254] Update docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md Co-authored-by: Rushat Gabhane --- .../business-bank-accounts/Business-Bank-Accounts-USD.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md index 44c5da286f66..9e453e8f05bc 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md @@ -81,6 +81,7 @@ Note: A report is shared with all individuals with access to the same business b This step is important when accountants and staff leave your business. To remove an admin's access to a shared bank account, go to **Settings > Account > Payments > Shared Business Bank Accounts**. You'll find a list of individuals who have access to the bank account. Next to each user, you'll see the option to Unshare the bank account. + # How to delete a verified bank account If you need to delete a bank account from Expensify, run through the following steps: 1. Head to **Settings > Account > Payments** From bf4504dfbbc5194b9135f83ce52c6eea5966e2aa Mon Sep 17 00:00:00 2001 From: Corinne Ofstad Date: Thu, 12 Oct 2023 15:10:39 -0500 Subject: [PATCH 063/254] Update docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md Co-authored-by: Rushat Gabhane --- .../business-bank-accounts/Business-Bank-Accounts-USD.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md index 9e453e8f05bc..f508c04f2620 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md @@ -90,6 +90,7 @@ If you need to delete a bank account from Expensify, run through the following s Be cautious, as if it hasn't been shared with someone else, the next user will need to set it up from the beginning. If the bank account is set as the settlement account for your Expensify Cards, you’ll need to designate another bank account as your settlement account under **Settings > Domains > Company Cards > Settings** before this account can be deleted. + # Deep Dive ## Verified bank account requirements From e929211a579f2a85e4ac1e31a9584b2db1757728 Mon Sep 17 00:00:00 2001 From: Corinne Ofstad Date: Thu, 12 Oct 2023 15:10:46 -0500 Subject: [PATCH 064/254] Update docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md Co-authored-by: Rushat Gabhane --- .../business-bank-accounts/Business-Bank-Accounts-USD.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md index f508c04f2620..2fbdac02e85c 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md @@ -111,6 +111,7 @@ If you need to enable direct debits from your verified bank account, your bank w To request to unlock the bank account, click **Fix** on your bank account under **Settings > Account > Payments > Bank Accounts**. This sends a request to our support team to review exactly why the bank account was locked. Please note, unlocking a bank account can take 4-5 business days to process. + ## Error adding ID to Onfido Expensify is required by both our sponsor bank and federal law to verify the identity of the individual that is initiating the movement of money. We use Onfido to confirm that the person adding a payment method is genuine and not impersonating someone else. From d13cfb8f19496b08da088c6b5d3cfd217a1ebb9b Mon Sep 17 00:00:00 2001 From: Hardik Choudhary Date: Fri, 13 Oct 2023 21:39:18 +0530 Subject: [PATCH 065/254] RTL text gets rendered properly for safari --- src/styles/styles.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/styles.js b/src/styles/styles.js index 1954205c2b59..94bbecd611b8 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -197,6 +197,7 @@ const styles = (theme) => ({ rtlTextRenderForSafari: { textAlign:"left", + ...writingDirection.ltr }, emojiSuggestionsEmoji: { From 669410f83ca39ba4c369a13d2dd9c2e840a0cf4a Mon Sep 17 00:00:00 2001 From: Hardik Choudhary Date: Fri, 13 Oct 2023 22:55:41 +0530 Subject: [PATCH 066/254] RTL text gets rendered properly for safari --- src/styles/styles.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/styles/styles.ts b/src/styles/styles.ts index ba3c4d888858..ec33be2f5c41 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -244,6 +244,11 @@ const styles = (theme: ThemeDefault) => alignItems: 'center', }, + rtlTextRenderForSafari: { + textAlign: 'left', + ...writingDirection.ltr, + }, + emojiSuggestionsEmoji: { fontSize: variables.fontSizeMedium, width: 51, From 9a562e897f3b17f2f75cc2b1d05f5b25b99bc81f Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 13 Oct 2023 15:28:54 -0700 Subject: [PATCH 067/254] Implement review feedback --- .../DotIndicatorMessageWithClose.js | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/components/DotIndicatorMessageWithClose.js b/src/components/DotIndicatorMessageWithClose.js index 7626a956489f..0f23c3783336 100644 --- a/src/components/DotIndicatorMessageWithClose.js +++ b/src/components/DotIndicatorMessageWithClose.js @@ -11,7 +11,7 @@ import CONST from '../CONST'; import * as StyleUtils from '../styles/StyleUtils'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; import stylePropTypes from '../styles/stylePropTypes'; -import withLocalize, {withLocalizePropTypes} from './withLocalize'; +import useLocalize from '../hooks/useLocalize'; const propTypes = { // The error messages to display @@ -25,8 +25,6 @@ const propTypes = { /** Additional style object for the container */ containerStyles: stylePropTypes, - - ...withLocalizePropTypes, }; const defaultProps = { @@ -35,24 +33,25 @@ const defaultProps = { containerStyles: [], }; -function DotIndicatorMessageWithClose(props) { - if (_.isEmpty(props.messages)) { +function DotIndicatorMessageWithClose({messages, type, onClose, containerStyles}) { + const {translate} = useLocalize(); + if (_.isEmpty(messages)) { return null; } return ( - + - + @@ -65,4 +64,4 @@ DotIndicatorMessageWithClose.propTypes = propTypes; DotIndicatorMessageWithClose.defaultProps = defaultProps; DotIndicatorMessageWithClose.displayName = 'DotIndicatorMessageWithClose'; -export default withLocalize(DotIndicatorMessageWithClose); +export default DotIndicatorMessageWithClose; From 010b47b660a11449b6e64babfe9bfdee232e450d Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Mon, 16 Oct 2023 10:34:48 +0200 Subject: [PATCH 068/254] Quick fixes for policy utils --- src/libs/PolicyUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 7afac2ce5b67..e75fdd48be0e 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -13,7 +13,7 @@ type UnitRate = {rate: number}; * These are policies that we can use to create reports with in NewDot. */ function getActivePolicies(policies: OnyxCollection): Policy[] | undefined { - return (Object.values(policies ?? {}) ?? []).filter( + return Object.values(policies ?? {}).filter( (policy): policy is Policy => policy !== null && policy && (policy.isPolicyExpenseChatEnabled || policy.areChatRoomsEnabled) && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, ); From 68a0ee71c8795e6a536d98c2874b29a15c692705 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 16 Oct 2023 14:26:08 +0200 Subject: [PATCH 069/254] add platform specific types --- src/hooks/useReportScrollManager/types.native.ts | 10 ++++++++++ src/hooks/useReportScrollManager/types.ts | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/hooks/useReportScrollManager/types.native.ts create mode 100644 src/hooks/useReportScrollManager/types.ts diff --git a/src/hooks/useReportScrollManager/types.native.ts b/src/hooks/useReportScrollManager/types.native.ts new file mode 100644 index 000000000000..c09f4ba659cf --- /dev/null +++ b/src/hooks/useReportScrollManager/types.native.ts @@ -0,0 +1,10 @@ +import {ActionListContextType} from '../../pages/home/ReportScreenContext'; + +type ReportScrollManagerData = { + ref: ActionListContextType; + scrollToIndex: (index: number) => void; + scrollToBottom: () => void; +}; + +// eslint-disable-next-line import/prefer-default-export +export type {ReportScrollManagerData}; diff --git a/src/hooks/useReportScrollManager/types.ts b/src/hooks/useReportScrollManager/types.ts new file mode 100644 index 000000000000..19f99d267484 --- /dev/null +++ b/src/hooks/useReportScrollManager/types.ts @@ -0,0 +1,10 @@ +import {ActionListContextType} from '../../pages/home/ReportScreenContext'; + +type ReportScrollManagerData = { + ref: ActionListContextType; + scrollToIndex: (index: number, isEditing: boolean) => void; + scrollToBottom: () => void; +}; + +// eslint-disable-next-line import/prefer-default-export +export type {ReportScrollManagerData}; From 1fd92cc621c5e49406f1edf5dee3aaac1122796f Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Mon, 16 Oct 2023 15:07:33 +0200 Subject: [PATCH 070/254] Post-merge fixes --- src/pages/settings/Report/RoomNamePage.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/settings/Report/RoomNamePage.js b/src/pages/settings/Report/RoomNamePage.js index cb14ca336d57..342ba9b41dd6 100644 --- a/src/pages/settings/Report/RoomNamePage.js +++ b/src/pages/settings/Report/RoomNamePage.js @@ -7,7 +7,6 @@ import CONST from '../../../CONST'; import ScreenWrapper from '../../../components/ScreenWrapper'; import HeaderWithBackButton from '../../../components/HeaderWithBackButton'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; -import Form from '../../../components/Form'; import ONYXKEYS from '../../../ONYXKEYS'; import styles from '../../../styles/styles'; import Navigation from '../../../libs/Navigation/Navigation'; From ba1a78d9e5bfe31c4e7afff6091237eca6c1a4f8 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Mon, 16 Oct 2023 15:08:44 +0200 Subject: [PATCH 071/254] Prettier fixes --- src/components/RoomNameInput/index.js | 2 +- src/components/RoomNameInput/index.native.js | 2 +- src/components/RoomNameInput/roomNameInputPropTypes.js | 1 - src/pages/settings/Report/RoomNamePage.js | 2 +- src/pages/workspace/WorkspaceNewRoomPage.js | 4 ++-- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/RoomNameInput/index.js b/src/components/RoomNameInput/index.js index 32f162d1cf24..173b906c8867 100644 --- a/src/components/RoomNameInput/index.js +++ b/src/components/RoomNameInput/index.js @@ -5,7 +5,7 @@ import TextInput from '../TextInput'; import useLocalize from '../../hooks/useLocalize'; import * as roomNameInputPropTypes from './roomNameInputPropTypes'; import * as RoomNameInputUtils from '../../libs/RoomNameInputUtils'; -import InputWrapper from "../Form/InputWrapper"; +import InputWrapper from '../Form/InputWrapper'; function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, value, onBlur, onChangeText, onInputChange, shouldDelayFocus, inputID}) { const {translate} = useLocalize(); diff --git a/src/components/RoomNameInput/index.native.js b/src/components/RoomNameInput/index.native.js index 1b1a26746ea0..d46a9db9ec40 100644 --- a/src/components/RoomNameInput/index.native.js +++ b/src/components/RoomNameInput/index.native.js @@ -6,7 +6,7 @@ import TextInput from '../TextInput'; import * as roomNameInputPropTypes from './roomNameInputPropTypes'; import * as RoomNameInputUtils from '../../libs/RoomNameInputUtils'; import getOperatingSystem from '../../libs/getOperatingSystem'; -import InputWrapper from "../Form/InputWrapper"; +import InputWrapper from '../Form/InputWrapper'; function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, value, onBlur, onChangeText, onInputChange, shouldDelayFocus, inputID}) { const {translate} = useLocalize(); diff --git a/src/components/RoomNameInput/roomNameInputPropTypes.js b/src/components/RoomNameInput/roomNameInputPropTypes.js index 17afe833ea81..098d3fc1be68 100644 --- a/src/components/RoomNameInput/roomNameInputPropTypes.js +++ b/src/components/RoomNameInput/roomNameInputPropTypes.js @@ -29,7 +29,6 @@ const propTypes = { /** Whether we should wait before focusing the TextInput, useful when using transitions on Android */ shouldDelayFocus: PropTypes.bool, - ...withNavigationFocusPropTypes, }; diff --git a/src/pages/settings/Report/RoomNamePage.js b/src/pages/settings/Report/RoomNamePage.js index 342ba9b41dd6..3abf50d3e7db 100644 --- a/src/pages/settings/Report/RoomNamePage.js +++ b/src/pages/settings/Report/RoomNamePage.js @@ -20,7 +20,7 @@ import * as Report from '../../../libs/actions/Report'; import RoomNameInput from '../../../components/RoomNameInput'; import * as ReportUtils from '../../../libs/ReportUtils'; import FullPageNotFoundView from '../../../components/BlockingViews/FullPageNotFoundView'; -import FormProvider from "../../../components/Form/FormProvider"; +import FormProvider from '../../../components/Form/FormProvider'; const propTypes = { ...withLocalizePropTypes, diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index dd0aba2bafe8..9ce3a3f45b2d 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -26,8 +26,8 @@ import compose from '../../libs/compose'; import variables from '../../styles/variables'; import useDelayedInputFocus from '../../hooks/useDelayedInputFocus'; import ValuePicker from '../../components/ValuePicker'; -import FormProvider from "../../components/Form/FormProvider"; -import InputWrapper from "../../components/Form/InputWrapper"; +import FormProvider from '../../components/Form/FormProvider'; +import InputWrapper from '../../components/Form/InputWrapper'; const propTypes = { /** All reports shared with the user */ From 1ccbae3710411b9f0449f4df940b0a073ba7e447 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 16 Oct 2023 15:27:33 +0200 Subject: [PATCH 072/254] improve scrolling to index, minor fixes --- src/hooks/useReportScrollManager/index.native.ts | 10 ++-------- src/hooks/useReportScrollManager/index.ts | 12 +++--------- src/pages/home/ReportScreenContext.ts | 6 +++--- src/pages/home/report/ReportActionItemMessageEdit.js | 4 ++-- 4 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/hooks/useReportScrollManager/index.native.ts b/src/hooks/useReportScrollManager/index.native.ts index bda8d1cbdee5..4b8d949b216a 100644 --- a/src/hooks/useReportScrollManager/index.native.ts +++ b/src/hooks/useReportScrollManager/index.native.ts @@ -1,18 +1,12 @@ import {useContext, useCallback} from 'react'; -import {ActionListContext, ActionListContextType} from '../../pages/home/ReportScreenContext'; - -type ReportScrollManagerData = { - ref: ActionListContextType; - scrollToIndex: (index: number) => void; - scrollToBottom: () => void; -}; +import {ActionListContext} from '../../pages/home/ReportScreenContext'; +import {ReportScrollManagerData} from './types'; function useReportScrollManager(): ReportScrollManagerData { const flatListRef = useContext(ActionListContext); /** * Scroll to the provided index. - * */ const scrollToIndex = (index: number) => { if (!flatListRef?.current) { diff --git a/src/hooks/useReportScrollManager/index.ts b/src/hooks/useReportScrollManager/index.ts index 2cf3d1733360..5e1ead1995a6 100644 --- a/src/hooks/useReportScrollManager/index.ts +++ b/src/hooks/useReportScrollManager/index.ts @@ -1,11 +1,6 @@ import {useContext, useCallback} from 'react'; -import {ActionListContext, ActionListContextType} from '../../pages/home/ReportScreenContext'; - -type ReportScrollManagerData = { - ref: ActionListContextType; - scrollToIndex: (index: number, isEditing: boolean) => void; - scrollToBottom: () => void; -}; +import {ActionListContext} from '../../pages/home/ReportScreenContext'; +import {ReportScrollManagerData} from './types'; function useReportScrollManager(): ReportScrollManagerData { const flatListRef = useContext(ActionListContext); @@ -13,14 +8,13 @@ function useReportScrollManager(): ReportScrollManagerData { /** * Scroll to the provided index. On non-native implementations we do not want to scroll when we are scrolling because * we are editing a comment. - * */ const scrollToIndex = (index: number, isEditing: boolean) => { if (!flatListRef?.current || isEditing) { return; } - flatListRef.current.scrollToIndex({index}); + flatListRef.current.scrollToIndex({index, animated: true}); }; /** diff --git a/src/pages/home/ReportScreenContext.ts b/src/pages/home/ReportScreenContext.ts index a74c6d9797ff..83f76d8d8e2f 100644 --- a/src/pages/home/ReportScreenContext.ts +++ b/src/pages/home/ReportScreenContext.ts @@ -1,17 +1,17 @@ import {RefObject, createContext} from 'react'; import {FlatList, GestureResponderEvent} from 'react-native'; -type ReactionListRefType = { +type ReactionListRef = { showReactionList: (event: GestureResponderEvent | undefined, reactionListAnchor: Element, emojiName: string, reportActionID: string) => void; hideReactionList: () => void; isActiveReportAction: (actionID: number | string) => boolean; }; type ActionListContextType = RefObject> | null; -type ReactionListContextType = RefObject | null; +type ReactionListContextType = RefObject | null; const ActionListContext = createContext(null); const ReactionListContext = createContext(null); export {ActionListContext, ReactionListContext}; -export type {ReactionListRefType, ActionListContextType, ReactionListContextType}; +export type {ReactionListRef, ActionListContextType, ReactionListContextType}; diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index cb756ee40491..73c34f12ecf6 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -249,7 +249,7 @@ function ReportActionItemMessageEdit(props) { // Scroll to the last comment after editing to make sure the whole comment is clearly visible in the report. if (props.index === 0) { const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => { - reportScrollManager.scrollToIndex({animated: true, index: props.index}, false); + reportScrollManager.scrollToIndex(props.index, false); keyboardDidHideListener.remove(); }); } @@ -364,7 +364,7 @@ function ReportActionItemMessageEdit(props) { style={[styles.textInputCompose, styles.flex1, styles.bgTransparent]} onFocus={() => { setIsFocused(true); - reportScrollManager.scrollToIndex({animated: true, index: props.index}, true); + reportScrollManager.scrollToIndex(props.index, true); setShouldShowComposeInputKeyboardAware(false); // Clear active report action when another action gets focused From e22bd99ba4bead6b9c0df158e174fb2a543657b0 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 16 Oct 2023 09:46:58 -0700 Subject: [PATCH 073/254] Show message about users added by primary login --- .../SelectionList/BaseSelectionList.js | 2 ++ .../SelectionList/selectionListPropTypes.js | 3 +++ src/pages/workspace/WorkspaceMembersPage.js | 16 ++++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index fdb1f92ca73b..0878e404aafe 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -48,6 +48,7 @@ function BaseSelectionList({ headerMessage = '', confirmButtonText = '', onConfirm, + headerContent, footerContent, showScrollIndicator = false, showLoadingPlaceholder = false, @@ -389,6 +390,7 @@ function BaseSelectionList({ {headerMessage} )} + {Boolean(headerContent) && headerContent} {flattenedSections.allOptions.length === 0 && showLoadingPlaceholder ? ( ) : ( diff --git a/src/components/SelectionList/selectionListPropTypes.js b/src/components/SelectionList/selectionListPropTypes.js index e75335e39b23..58aecb0da1a1 100644 --- a/src/components/SelectionList/selectionListPropTypes.js +++ b/src/components/SelectionList/selectionListPropTypes.js @@ -174,6 +174,9 @@ const propTypes = { /** A ref to forward to the TextInput */ inputRef: PropTypes.oneOfType([PropTypes.object]), + /** Custom content to display in the header */ + headerContent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), + /** Custom content to display in the footer */ footerContent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), }; diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index e0e818ba90d6..120d818fcd20 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -262,6 +262,7 @@ function WorkspaceMembersPage(props) { const currentUserLogin = lodashGet(props.currentUserPersonalDetails, 'login'); const policyID = lodashGet(props.route, 'params.policyID'); const policyName = lodashGet(props.policy, 'name'); + const invitedPrimaryToSecondaryLogins = _.invert(props.policy.primaryLoginsInvited); const getMemberOptions = () => { let result = []; @@ -354,6 +355,20 @@ function WorkspaceMembersPage(props) { return searchValue.trim() && !data.length ? props.translate('workspace.common.memberNotFound') : ''; }; + const getHeaderContent = () => { + if (_.isEmpty(invitedPrimaryToSecondaryLogins)) { + return null; + } + return ( + Policy.dismissAddedWithPrimaryMessages(policyID)} + /> + ); + }; + return ( toggleUser(item.keyForList)} onSelectAll={() => toggleAllUsers(data)} onDismissError={dismissError} From 6e747221a7e920e7e5ec8fe4670574c5dc488402 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Tue, 17 Oct 2023 03:06:31 -0400 Subject: [PATCH 074/254] re-add functionality --- src/libs/actions/Report.js | 2 +- .../report/ContextMenu/ContextMenuActions.js | 50 ++++++++++--------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index af052c717425..c9ab02f127f4 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1351,7 +1351,7 @@ function updateNotificationPreference(reportID, previousValue, newValue, navigat function toggleSubscribeToChildReport(childReportID = '0', parentReportAction = {}, parentReportID = '0', prevNotificationPreference) { if (childReportID !== '0') { openReport(childReportID); - if (prevNotificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { + if (!prevNotificationPreference || prevNotificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { updateNotificationPreference(childReportID, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false) } else { updateNotificationPreference(childReportID, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, false) diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 0c2ace646cdb..468c06326064 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -155,7 +155,8 @@ export default [ successTextTranslateKey: '', successIcon: null, shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID) => { - const subscribed = lodashGet(reportAction, 'childReportNotificationPreference', '') !== "hidden"; + const childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', ''); + const subscribed = childReportNotificationPreference && (childReportNotificationPreference !== "hidden"); if (type !== CONTEXT_MENU_TYPES.REPORT_ACTION) { return false; } @@ -165,17 +166,17 @@ export default [ return !subscribed && (isCommentAction || isReportPreviewAction || isIOUAction); }, onPress: (closePopover, {reportAction, reportID}) => { - Log.info("sparsisparsi start"); - Log.info(lodashGet(reportAction, 'childReportNotificationPreference', '0')); - Log.info("sparsisparsi done"); - debugger; - // if (closePopover) { - // hideContextMenu(false, () => { - // ReportActionComposeFocusManager.focus(); - // Report.subscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID); - // }); - // return; - // } + const childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', ''); + if (closePopover) { + hideContextMenu(false, () => { + ReportActionComposeFocusManager.focus(); + Report.toggleSubscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID, childReportNotificationPreference); + }); + return; + } else { + ReportActionComposeFocusManager.focus(); + Report.toggleSubscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID, childReportNotificationPreference); + } }, getDescription: () => {}, }, @@ -187,7 +188,8 @@ export default [ successTextTranslateKey: '', successIcon: null, shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID) => { - const subscribed = lodashGet(reportAction, 'childReportNotificationPreference', '0') !== "hidden"; + const childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', ''); + const subscribed = childReportNotificationPreference && (childReportNotificationPreference !== "hidden"); if (type !== CONTEXT_MENU_TYPES.REPORT_ACTION) { return false; } @@ -197,17 +199,17 @@ export default [ return subscribed && (isCommentAction || isReportPreviewAction || isIOUAction); }, onPress: (closePopover, {reportAction, reportID}) => { - Log.info("sparsisparsi start"); - Log.info(lodashGet(reportAction, 'childReportNotificationPreference', '0')); - Log.info("sparsisparsi done"); - debugger; - // if (closePopover) { - // hideContextMenu(false, () => { - // ReportActionComposeFocusManager.focus(); - // Report.subscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID); - // }); - // return; - // } + const childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', ''); + if (closePopover) { + hideContextMenu(false, () => { + ReportActionComposeFocusManager.focus(); + Report.toggleSubscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID, childReportNotificationPreference); + }); + return; + } else { + ReportActionComposeFocusManager.focus(); + Report.toggleSubscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID, childReportNotificationPreference); + } }, getDescription: () => {}, }, From bad56feb0f2c0e2a618a90eab0c29dca631835c2 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Tue, 17 Oct 2023 03:17:37 -0400 Subject: [PATCH 075/254] lint --- assets/images/bell.svg | 7 +++++-- assets/images/bellSlash.svg | 7 +++++-- src/libs/actions/Report.js | 4 ++-- .../report/ContextMenu/ContextMenuActions.js | 20 ++++++++----------- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/assets/images/bell.svg b/assets/images/bell.svg index a53c9508cbd6..6ba600dc695b 100644 --- a/assets/images/bell.svg +++ b/assets/images/bell.svg @@ -1,3 +1,6 @@ - - + + + + diff --git a/assets/images/bellSlash.svg b/assets/images/bellSlash.svg index 2cacb07f4268..488acc4de05e 100644 --- a/assets/images/bellSlash.svg +++ b/assets/images/bellSlash.svg @@ -1,3 +1,6 @@ - - + + + + diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index c9ab02f127f4..d7c8be68977c 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1352,9 +1352,9 @@ function toggleSubscribeToChildReport(childReportID = '0', parentReportAction = if (childReportID !== '0') { openReport(childReportID); if (!prevNotificationPreference || prevNotificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { - updateNotificationPreference(childReportID, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false) + updateNotificationPreference(childReportID, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false); } else { - updateNotificationPreference(childReportID, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, false) + updateNotificationPreference(childReportID, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, false); } } else { const participantAccountIDs = _.uniq([currentUserAccountID, Number(parentReportAction.actorAccountID)]); diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 468c06326064..ba5c274ed59a 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -22,10 +22,6 @@ import MiniQuickEmojiReactions from '../../../../components/Reactions/MiniQuickE import Navigation from '../../../../libs/Navigation/Navigation'; import ROUTES from '../../../../ROUTES'; import * as Task from '../../../../libs/actions/Task'; -import * as Localize from '../../../../libs/Localize'; -import * as TransactionUtils from '../../../../libs/TransactionUtils'; -import * as CurrencyUtils from '../../../../libs/CurrencyUtils'; -import Log from '../../../../libs/Log'; /** * Gets the HTML version of the message in an action. @@ -156,7 +152,7 @@ export default [ successIcon: null, shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID) => { const childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', ''); - const subscribed = childReportNotificationPreference && (childReportNotificationPreference !== "hidden"); + const subscribed = childReportNotificationPreference && childReportNotificationPreference !== 'hidden'; if (type !== CONTEXT_MENU_TYPES.REPORT_ACTION) { return false; } @@ -173,10 +169,10 @@ export default [ Report.toggleSubscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID, childReportNotificationPreference); }); return; - } else { - ReportActionComposeFocusManager.focus(); - Report.toggleSubscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID, childReportNotificationPreference); } + + ReportActionComposeFocusManager.focus(); + Report.toggleSubscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID, childReportNotificationPreference); }, getDescription: () => {}, }, @@ -189,7 +185,7 @@ export default [ successIcon: null, shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID) => { const childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', ''); - const subscribed = childReportNotificationPreference && (childReportNotificationPreference !== "hidden"); + const subscribed = childReportNotificationPreference && childReportNotificationPreference !== 'hidden'; if (type !== CONTEXT_MENU_TYPES.REPORT_ACTION) { return false; } @@ -206,10 +202,10 @@ export default [ Report.toggleSubscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID, childReportNotificationPreference); }); return; - } else { - ReportActionComposeFocusManager.focus(); - Report.toggleSubscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID, childReportNotificationPreference); } + + ReportActionComposeFocusManager.focus(); + Report.toggleSubscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID, childReportNotificationPreference); }, getDescription: () => {}, }, From f016bb803da86d178715ce4d8179e5b999606641 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Tue, 17 Oct 2023 03:23:22 -0400 Subject: [PATCH 076/254] prettier --- src/pages/home/report/ContextMenu/ContextMenuActions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index ba5c274ed59a..8006cd3e3f29 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -170,7 +170,7 @@ export default [ }); return; } - + ReportActionComposeFocusManager.focus(); Report.toggleSubscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID, childReportNotificationPreference); }, @@ -203,7 +203,7 @@ export default [ }); return; } - + ReportActionComposeFocusManager.focus(); Report.toggleSubscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID, childReportNotificationPreference); }, From 7f6c9f0dafda8a76784d4e1681a197382be83c79 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 17 Oct 2023 14:08:59 +0200 Subject: [PATCH 077/254] [TS migration] Migrate 'FormScrollView.js' component to TypeScript --- src/components/FormScrollView.js | 25 ------------------------- src/components/FormScrollView.tsx | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 25 deletions(-) delete mode 100644 src/components/FormScrollView.js create mode 100644 src/components/FormScrollView.tsx diff --git a/src/components/FormScrollView.js b/src/components/FormScrollView.js deleted file mode 100644 index aa84bfefcc2f..000000000000 --- a/src/components/FormScrollView.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import {ScrollView} from 'react-native'; -import styles from '../styles/styles'; - -const propTypes = { - /** Form elements */ - children: PropTypes.node.isRequired, -}; - -const FormScrollView = React.forwardRef((props, ref) => ( - - {props.children} - -)); - -FormScrollView.propTypes = propTypes; -export default FormScrollView; diff --git a/src/components/FormScrollView.tsx b/src/components/FormScrollView.tsx new file mode 100644 index 000000000000..3705e5e7ffeb --- /dev/null +++ b/src/components/FormScrollView.tsx @@ -0,0 +1,25 @@ +import React, {ForwardedRef} from 'react'; +import {ScrollView} from 'react-native'; +import styles from '../styles/styles'; + +type FormScrollViewProps = Partial & { + /** Form elements */ + children: React.ReactNode; +}; + +function FormScrollView({children, ...rest}: FormScrollViewProps, ref: ForwardedRef) { + return ( + + {children} + + ); +} + +export default React.forwardRef(FormScrollView); From b022e3238b7cf3cc50eeb56ff85987fc592d1e24 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 17 Oct 2023 14:56:35 +0200 Subject: [PATCH 078/254] [TS migration] Migrate 'SignInPageForm' component to TypeScript --- src/components/SignInPageForm/index.native.js | 10 ---------- src/components/SignInPageForm/index.native.tsx | 12 ++++++++++++ .../SignInPageForm/{index.js => index.tsx} | 11 ++++++----- src/components/SignInPageForm/types.ts | 5 +++++ 4 files changed, 23 insertions(+), 15 deletions(-) delete mode 100644 src/components/SignInPageForm/index.native.js create mode 100644 src/components/SignInPageForm/index.native.tsx rename src/components/SignInPageForm/{index.js => index.tsx} (80%) create mode 100644 src/components/SignInPageForm/types.ts diff --git a/src/components/SignInPageForm/index.native.js b/src/components/SignInPageForm/index.native.js deleted file mode 100644 index acd1dfe0d197..000000000000 --- a/src/components/SignInPageForm/index.native.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import FormElement from '../FormElement'; - -function Form(props) { - // eslint-disable-next-line react/jsx-props-no-spreading - return ; -} - -Form.displayName = 'Form'; -export default Form; diff --git a/src/components/SignInPageForm/index.native.tsx b/src/components/SignInPageForm/index.native.tsx new file mode 100644 index 000000000000..487822c62de9 --- /dev/null +++ b/src/components/SignInPageForm/index.native.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import FormElement from '../FormElement'; +import SignInPageFormProps from './types'; + +function SignInPageForm(props: SignInPageFormProps) { + // eslint-disable-next-line react/jsx-props-no-spreading + return ; +} + +SignInPageForm.displayName = 'SignInPageForm'; + +export default SignInPageForm; diff --git a/src/components/SignInPageForm/index.js b/src/components/SignInPageForm/index.tsx similarity index 80% rename from src/components/SignInPageForm/index.js rename to src/components/SignInPageForm/index.tsx index 5a1e5a62ff23..fd415dbbd930 100644 --- a/src/components/SignInPageForm/index.js +++ b/src/components/SignInPageForm/index.tsx @@ -1,14 +1,15 @@ import React, {useEffect, useRef} from 'react'; import FormElement from '../FormElement'; +import SignInPageFormProps from './types'; -const preventFormDefault = (event) => { +const preventFormDefault = (event: SubmitEvent) => { // When enter is pressed form is submitted to action url (POST /). // As we are using controlled component, we need to disable it here. event.preventDefault(); }; -function Form(props) { - const form = useRef(null); +function SignInPageForm(props: SignInPageFormProps) { + const form = useRef(null); useEffect(() => { const formCurrent = form.current; @@ -42,6 +43,6 @@ function Form(props) { ); } -Form.displayName = 'Form'; +SignInPageForm.displayName = 'SignInPageForm'; -export default Form; +export default SignInPageForm; diff --git a/src/components/SignInPageForm/types.ts b/src/components/SignInPageForm/types.ts new file mode 100644 index 000000000000..02d948f917b9 --- /dev/null +++ b/src/components/SignInPageForm/types.ts @@ -0,0 +1,5 @@ +import {ViewProps} from 'react-native'; + +type SignInPageFormProps = ViewProps; + +export default SignInPageFormProps; From fefdb2b3bcf3076daf99e446e6837356d697a4a6 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Tue, 17 Oct 2023 15:08:19 +0200 Subject: [PATCH 079/254] Add value parser to the new form --- src/components/Form/FormProvider.js | 8 ++++++-- src/components/Form/InputWrapper.js | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/Form/FormProvider.js b/src/components/Form/FormProvider.js index ada40c24ed89..863e3b707bd1 100644 --- a/src/components/Form/FormProvider.js +++ b/src/components/Form/FormProvider.js @@ -233,7 +233,7 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC inputID, key: propsToParse.key || inputID, errorText: errors[inputID] || fieldErrorMessage, - value: inputValues[inputID], + value: propsToParse.valueParser && !_.isUndefined(inputValues[`${inputID}ToDisplay`]) ? inputValues[`${inputID}ToDisplay`] : inputValues[inputID], // As the text input is controlled, we never set the defaultValue prop // as this is already happening by the value prop. defaultValue: undefined, @@ -276,7 +276,11 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC onInputChange: (value, key) => { const inputKey = key || inputID; setInputValues((prevState) => { - const newState = { + const newState = _.isFunction(propsToParse.valueParser) ? { + ...prevState, + [inputKey]: propsToParse.valueParser(value), + [`${inputKey}ToDisplay`]: value, + } : { ...prevState, [inputKey]: value, }; diff --git a/src/components/Form/InputWrapper.js b/src/components/Form/InputWrapper.js index 43064b5a6690..584740f925a3 100644 --- a/src/components/Form/InputWrapper.js +++ b/src/components/Form/InputWrapper.js @@ -7,11 +7,13 @@ const propTypes = { inputID: PropTypes.string.isRequired, valueType: PropTypes.string, forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(React.Component)})]), + valueParser: PropTypes.func }; const defaultProps = { forwardedRef: undefined, valueType: 'string', + valueParser: undefined, }; function InputWrapper(props) { From e05b537a1588d62f66342c391edd5a8792462135 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Tue, 17 Oct 2023 15:08:46 +0200 Subject: [PATCH 080/254] Remove native room name input --- src/components/RoomNameInput/index.native.js | 65 -------------------- 1 file changed, 65 deletions(-) delete mode 100644 src/components/RoomNameInput/index.native.js diff --git a/src/components/RoomNameInput/index.native.js b/src/components/RoomNameInput/index.native.js deleted file mode 100644 index d46a9db9ec40..000000000000 --- a/src/components/RoomNameInput/index.native.js +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; -import _ from 'underscore'; -import CONST from '../../CONST'; -import useLocalize from '../../hooks/useLocalize'; -import TextInput from '../TextInput'; -import * as roomNameInputPropTypes from './roomNameInputPropTypes'; -import * as RoomNameInputUtils from '../../libs/RoomNameInputUtils'; -import getOperatingSystem from '../../libs/getOperatingSystem'; -import InputWrapper from '../Form/InputWrapper'; - -function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, value, onBlur, onChangeText, onInputChange, shouldDelayFocus, inputID}) { - const {translate} = useLocalize(); - - /** - * Calls the onChangeText callback with a modified room name - * @param {Event} event - */ - const setModifiedRoomName = (event) => { - const roomName = event.nativeEvent.text; - const modifiedRoomName = RoomNameInputUtils.modifyRoomName(roomName); - onChangeText(modifiedRoomName); - - // if custom component has onInputChange, use it to trigger changes (Form input) - if (_.isFunction(onInputChange)) { - onInputChange(modifiedRoomName); - } - }; - - const keyboardType = getOperatingSystem() === CONST.OS.IOS ? CONST.KEYBOARD_TYPE.ASCII_CAPABLE : CONST.KEYBOARD_TYPE.VISIBLE_PASSWORD; - - return ( - isFocused && onBlur()} - autoFocus={isFocused && autoFocus} - autoCapitalize="none" - shouldDelayFocus={shouldDelayFocus} - /> - ); -} - -RoomNameInput.propTypes = roomNameInputPropTypes.propTypes; -RoomNameInput.defaultProps = roomNameInputPropTypes.defaultProps; -RoomNameInput.displayName = 'RoomNameInput'; - -export default React.forwardRef((props, ref) => ( - -)); From eff3e88fcd590acc9d62099571bc0488e6500670 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Tue, 17 Oct 2023 15:09:02 +0200 Subject: [PATCH 081/254] Update room name input --- src/components/RoomNameInput/index.js | 47 +++++---------------------- 1 file changed, 8 insertions(+), 39 deletions(-) diff --git a/src/components/RoomNameInput/index.js b/src/components/RoomNameInput/index.js index 173b906c8867..8bc86e5b730b 100644 --- a/src/components/RoomNameInput/index.js +++ b/src/components/RoomNameInput/index.js @@ -1,47 +1,18 @@ -import React, {useState} from 'react'; -import _ from 'underscore'; +import React from 'react'; import CONST from '../../CONST'; import TextInput from '../TextInput'; import useLocalize from '../../hooks/useLocalize'; import * as roomNameInputPropTypes from './roomNameInputPropTypes'; -import * as RoomNameInputUtils from '../../libs/RoomNameInputUtils'; import InputWrapper from '../Form/InputWrapper'; +import getOperatingSystem from "../../libs/getOperatingSystem"; +import * as RoomNameInputUtils from "../../libs/RoomNameInputUtils"; -function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, value, onBlur, onChangeText, onInputChange, shouldDelayFocus, inputID}) { +function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, onBlur, shouldDelayFocus, inputID}) { const {translate} = useLocalize(); - const [selection, setSelection] = useState(); + const keyboardType = getOperatingSystem() === CONST.OS.IOS ? CONST.KEYBOARD_TYPE.ASCII_CAPABLE : CONST.KEYBOARD_TYPE.VISIBLE_PASSWORD; - /** - * Calls the onChangeText callback with a modified room name - * @param {Event} event - */ - const setModifiedRoomName = (event) => { - const roomName = event.nativeEvent.text; - const modifiedRoomName = RoomNameInputUtils.modifyRoomName(roomName); - onChangeText(modifiedRoomName); - - // if custom component has onInputChange, use it to trigger changes (Form input) - if (_.isFunction(onInputChange)) { - onInputChange(modifiedRoomName); - } - - // Prevent cursor jump behaviour: - // Check if newRoomNameWithHash is the same as modifiedRoomName - // If it is then the room name is valid (does not contain unallowed characters); no action required - // If not then the room name contains unvalid characters and we must adjust the cursor position manually - // Read more: https://github.com/Expensify/App/issues/12741 - const oldRoomNameWithHash = value || ''; - const newRoomNameWithHash = `${CONST.POLICY.ROOM_PREFIX}${roomName}`; - if (modifiedRoomName !== newRoomNameWithHash) { - const offset = modifiedRoomName.length - oldRoomNameWithHash.length; - const newSelection = { - start: selection.start + offset, - end: selection.end + offset, - }; - setSelection(newSelection); - } - }; + const valueParser = (roomName) => RoomNameInputUtils.modifyRoomName(roomName) return ( setSelection(event.nativeEvent.selection)} errorText={errorText} + valueParser={valueParser} autoCapitalize="none" onBlur={() => isFocused && onBlur()} shouldDelayFocus={shouldDelayFocus} @@ -66,6 +34,7 @@ function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, maxLength={CONST.REPORT.MAX_ROOM_NAME_LENGTH} spellCheck={false} shouldInterceptSwipe + keyboardType={keyboardType} // this is a bit hacky solution to a RN issue https://github.com/facebook/react-native/issues/27449 /> ); } From f467ac252b417f8357667072fbd6ad2e9fe31bbb Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Tue, 17 Oct 2023 15:48:55 +0200 Subject: [PATCH 082/254] remove platform specific type file, add default export --- src/hooks/useReportScrollManager/index.native.ts | 2 +- src/hooks/useReportScrollManager/index.ts | 4 ++-- src/hooks/useReportScrollManager/types.native.ts | 10 ---------- src/hooks/useReportScrollManager/types.ts | 5 ++--- 4 files changed, 5 insertions(+), 16 deletions(-) delete mode 100644 src/hooks/useReportScrollManager/types.native.ts diff --git a/src/hooks/useReportScrollManager/index.native.ts b/src/hooks/useReportScrollManager/index.native.ts index 4b8d949b216a..ed9b7968636c 100644 --- a/src/hooks/useReportScrollManager/index.native.ts +++ b/src/hooks/useReportScrollManager/index.native.ts @@ -1,6 +1,6 @@ import {useContext, useCallback} from 'react'; import {ActionListContext} from '../../pages/home/ReportScreenContext'; -import {ReportScrollManagerData} from './types'; +import ReportScrollManagerData from './types'; function useReportScrollManager(): ReportScrollManagerData { const flatListRef = useContext(ActionListContext); diff --git a/src/hooks/useReportScrollManager/index.ts b/src/hooks/useReportScrollManager/index.ts index 5e1ead1995a6..fd2c884e5b4c 100644 --- a/src/hooks/useReportScrollManager/index.ts +++ b/src/hooks/useReportScrollManager/index.ts @@ -1,6 +1,6 @@ import {useContext, useCallback} from 'react'; import {ActionListContext} from '../../pages/home/ReportScreenContext'; -import {ReportScrollManagerData} from './types'; +import ReportScrollManagerData from './types'; function useReportScrollManager(): ReportScrollManagerData { const flatListRef = useContext(ActionListContext); @@ -9,7 +9,7 @@ function useReportScrollManager(): ReportScrollManagerData { * Scroll to the provided index. On non-native implementations we do not want to scroll when we are scrolling because * we are editing a comment. */ - const scrollToIndex = (index: number, isEditing: boolean) => { + const scrollToIndex = (index: number, isEditing?: boolean) => { if (!flatListRef?.current || isEditing) { return; } diff --git a/src/hooks/useReportScrollManager/types.native.ts b/src/hooks/useReportScrollManager/types.native.ts deleted file mode 100644 index c09f4ba659cf..000000000000 --- a/src/hooks/useReportScrollManager/types.native.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {ActionListContextType} from '../../pages/home/ReportScreenContext'; - -type ReportScrollManagerData = { - ref: ActionListContextType; - scrollToIndex: (index: number) => void; - scrollToBottom: () => void; -}; - -// eslint-disable-next-line import/prefer-default-export -export type {ReportScrollManagerData}; diff --git a/src/hooks/useReportScrollManager/types.ts b/src/hooks/useReportScrollManager/types.ts index 19f99d267484..f5ff9b2f35cd 100644 --- a/src/hooks/useReportScrollManager/types.ts +++ b/src/hooks/useReportScrollManager/types.ts @@ -2,9 +2,8 @@ import {ActionListContextType} from '../../pages/home/ReportScreenContext'; type ReportScrollManagerData = { ref: ActionListContextType; - scrollToIndex: (index: number, isEditing: boolean) => void; + scrollToIndex: (index: number, isEditing?: boolean) => void; scrollToBottom: () => void; }; -// eslint-disable-next-line import/prefer-default-export -export type {ReportScrollManagerData}; +export default ReportScrollManagerData; From 398b8427f1742f516ffcee81643f6bfe71337e34 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Tue, 17 Oct 2023 16:04:22 +0200 Subject: [PATCH 083/254] Prettier --- src/components/Form/FormProvider.js | 18 ++++++++++-------- src/components/Form/InputWrapper.js | 2 +- src/components/RoomNameInput/index.js | 6 +++--- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/components/Form/FormProvider.js b/src/components/Form/FormProvider.js index 863e3b707bd1..becdbf99e136 100644 --- a/src/components/Form/FormProvider.js +++ b/src/components/Form/FormProvider.js @@ -276,14 +276,16 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC onInputChange: (value, key) => { const inputKey = key || inputID; setInputValues((prevState) => { - const newState = _.isFunction(propsToParse.valueParser) ? { - ...prevState, - [inputKey]: propsToParse.valueParser(value), - [`${inputKey}ToDisplay`]: value, - } : { - ...prevState, - [inputKey]: value, - }; + const newState = _.isFunction(propsToParse.valueParser) + ? { + ...prevState, + [inputKey]: propsToParse.valueParser(value), + [`${inputKey}ToDisplay`]: value, + } + : { + ...prevState, + [inputKey]: value, + }; if (shouldValidateOnChange) { onValidate(newState); diff --git a/src/components/Form/InputWrapper.js b/src/components/Form/InputWrapper.js index 584740f925a3..b7a73239d11e 100644 --- a/src/components/Form/InputWrapper.js +++ b/src/components/Form/InputWrapper.js @@ -7,7 +7,7 @@ const propTypes = { inputID: PropTypes.string.isRequired, valueType: PropTypes.string, forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(React.Component)})]), - valueParser: PropTypes.func + valueParser: PropTypes.func, }; const defaultProps = { diff --git a/src/components/RoomNameInput/index.js b/src/components/RoomNameInput/index.js index 8bc86e5b730b..b76fd39bbcbf 100644 --- a/src/components/RoomNameInput/index.js +++ b/src/components/RoomNameInput/index.js @@ -4,15 +4,15 @@ import TextInput from '../TextInput'; import useLocalize from '../../hooks/useLocalize'; import * as roomNameInputPropTypes from './roomNameInputPropTypes'; import InputWrapper from '../Form/InputWrapper'; -import getOperatingSystem from "../../libs/getOperatingSystem"; -import * as RoomNameInputUtils from "../../libs/RoomNameInputUtils"; +import getOperatingSystem from '../../libs/getOperatingSystem'; +import * as RoomNameInputUtils from '../../libs/RoomNameInputUtils'; function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, onBlur, shouldDelayFocus, inputID}) { const {translate} = useLocalize(); const keyboardType = getOperatingSystem() === CONST.OS.IOS ? CONST.KEYBOARD_TYPE.ASCII_CAPABLE : CONST.KEYBOARD_TYPE.VISIBLE_PASSWORD; - const valueParser = (roomName) => RoomNameInputUtils.modifyRoomName(roomName) + const valueParser = (roomName) => RoomNameInputUtils.modifyRoomName(roomName); return ( Date: Tue, 17 Oct 2023 08:44:20 -0700 Subject: [PATCH 084/254] Use a more simple component name --- src/components/DotIndicatorMessageWithClose.js | 10 +++++----- src/components/OfflineWithFeedback.js | 4 ++-- src/pages/workspace/WorkspaceMembersPage.js | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/DotIndicatorMessageWithClose.js b/src/components/DotIndicatorMessageWithClose.js index 0f23c3783336..37366ce5a442 100644 --- a/src/components/DotIndicatorMessageWithClose.js +++ b/src/components/DotIndicatorMessageWithClose.js @@ -33,7 +33,7 @@ const defaultProps = { containerStyles: [], }; -function DotIndicatorMessageWithClose({messages, type, onClose, containerStyles}) { +function MessagesRow({messages, type, onClose, containerStyles}) { const {translate} = useLocalize(); if (_.isEmpty(messages)) { return null; @@ -60,8 +60,8 @@ function DotIndicatorMessageWithClose({messages, type, onClose, containerStyles} ); } -DotIndicatorMessageWithClose.propTypes = propTypes; -DotIndicatorMessageWithClose.defaultProps = defaultProps; -DotIndicatorMessageWithClose.displayName = 'DotIndicatorMessageWithClose'; +MessagesRow.propTypes = propTypes; +MessagesRow.defaultProps = defaultProps; +MessagesRow.displayName = 'MessagesRow'; -export default DotIndicatorMessageWithClose; +export default MessagesRow; diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js index e0f0de5df6d7..23b0facf8366 100644 --- a/src/components/OfflineWithFeedback.js +++ b/src/components/OfflineWithFeedback.js @@ -7,7 +7,7 @@ import stylePropTypes from '../styles/stylePropTypes'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; import shouldRenderOffscreen from '../libs/shouldRenderOffscreen'; -import DotIndicatorMessageWithClose from './DotIndicatorMessageWithClose'; +import MessagesRow from './MessagesRow'; import useNetwork from '../hooks/useNetwork'; /** @@ -118,7 +118,7 @@ function OfflineWithFeedback(props) { )} {props.shouldShowErrorMessages && hasErrorMessages && ( - Date: Tue, 17 Oct 2023 08:58:27 -0700 Subject: [PATCH 085/254] Woops, rename file too --- .../{DotIndicatorMessageWithClose.js => MessagesRow.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/components/{DotIndicatorMessageWithClose.js => MessagesRow.js} (100%) diff --git a/src/components/DotIndicatorMessageWithClose.js b/src/components/MessagesRow.js similarity index 100% rename from src/components/DotIndicatorMessageWithClose.js rename to src/components/MessagesRow.js From 3b928cb9e56d823614710239336e5082d6717086 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 17 Oct 2023 09:27:53 -0700 Subject: [PATCH 086/254] WIP display invited secondary login below member --- src/components/SelectionList/UserListItem.js | 5 +++++ src/pages/workspace/WorkspaceMembersPage.js | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/components/SelectionList/UserListItem.js b/src/components/SelectionList/UserListItem.js index 436ae8cb056b..faef86b68aed 100644 --- a/src/components/SelectionList/UserListItem.js +++ b/src/components/SelectionList/UserListItem.js @@ -6,8 +6,10 @@ import Text from '../Text'; import {userListItemPropTypes} from './selectionListPropTypes'; import Tooltip from '../Tooltip'; import SubscriptAvatar from '../SubscriptAvatar'; +import useLocalize from '../../hooks/useLocalize'; function UserListItem({item, isFocused = false, showTooltip}) { + const {translate} = useLocalize(); return ( <> {Boolean(item.icons) && ( @@ -42,6 +44,9 @@ function UserListItem({item, isFocused = false, showTooltip}) { )} + {Boolean(item.invitedSecondaryLogin) && ( + {translate('workspace.people.invitedBySecondaryLogin', {secondaryLogin: item.invitedSecondaryLogin})} + )} {Boolean(item.rightElement) && item.rightElement} diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 2bd045a12e5d..769e5b07ea0e 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -339,6 +339,9 @@ function WorkspaceMembersPage(props) { ], errors: policyMember.errors, pendingAction: policyMember.pendingAction, + + // Note which secondary login was used to invite this primary login + invitedSecondaryLogin: invitedPrimaryToSecondaryLogins[details.login] || '', }); }); From b5e45fc36b4b181eb34c24d0a90bd7f833f9703a Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Sat, 7 Oct 2023 11:38:14 +0200 Subject: [PATCH 087/254] chore: add flashlist dependency --- ios/Podfile.lock | 6 +++++ package-lock.json | 60 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 3 files changed, 67 insertions(+) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index cb120bca2b88..97143f53b867 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -753,6 +753,8 @@ PODS: - Firebase/Performance (= 8.8.0) - React-Core - RNFBApp + - RNFlashList (1.6.1): + - React-Core - RNFS (2.20.0): - React-Core - RNGestureHandler (2.12.0): @@ -925,6 +927,7 @@ DEPENDENCIES: - "RNFBApp (from `../node_modules/@react-native-firebase/app`)" - "RNFBCrashlytics (from `../node_modules/@react-native-firebase/crashlytics`)" - "RNFBPerf (from `../node_modules/@react-native-firebase/perf`)" + - "RNFlashList (from `../node_modules/@shopify/flash-list`)" - RNFS (from `../node_modules/react-native-fs`) - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - "RNGoogleSignin (from `../node_modules/@react-native-google-signin/google-signin`)" @@ -1134,6 +1137,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-firebase/crashlytics" RNFBPerf: :path: "../node_modules/@react-native-firebase/perf" + RNFlashList: + :path: "../node_modules/@shopify/flash-list" RNFS: :path: "../node_modules/react-native-fs" RNGestureHandler: @@ -1273,6 +1278,7 @@ SPEC CHECKSUMS: RNFBApp: 729c0666395b1953198dc4a1ec6deb8fbe1c302e RNFBCrashlytics: 2061ca863e8e2fa1aae9b12477d7dfa8e88ca0f9 RNFBPerf: 389914cda4000fe0d996a752532a591132cbf3f9 + RNFlashList: 236646d48f224a034f35baa0242e1b77db063b1e RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: dec4645026e7401a0899f2846d864403478ff6a5 RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 diff --git a/package-lock.json b/package-lock.json index 922c2a158654..2193576568dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "@react-navigation/stack": "6.3.16", "@react-ng/bounds-observer": "^0.2.1", "@rnmapbox/maps": "^10.0.11", + "@shopify/flash-list": "^1.6.1", "@types/node": "^18.14.0", "@ua/react-native-airship": "^15.2.6", "awesome-phonenumber": "^5.4.0", @@ -9133,6 +9134,34 @@ "version": "1.14.1", "license": "0BSD" }, + "node_modules/@shopify/flash-list": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@shopify/flash-list/-/flash-list-1.6.1.tgz", + "integrity": "sha512-SlBlpP7+zol6D1SKaf402aS30Qgwdjwb8/Qn2CupYwXnTcu2l8TiXB766vcsIvKTqUO7ELfQnCwCq8NXx55FsQ==", + "dependencies": { + "recyclerlistview": "4.2.0", + "tslib": "2.4.0" + }, + "peerDependencies": { + "@babel/runtime": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/@shopify/flash-list/node_modules/recyclerlistview": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/recyclerlistview/-/recyclerlistview-4.2.0.tgz", + "integrity": "sha512-uuBCi0c+ggqHKwrzPX4Z/mJOzsBbjZEAwGGmlwpD/sD7raXixdAbdJ6BTcAmuWG50Cg4ru9p12M94Njwhr/27A==", + "dependencies": { + "lodash.debounce": "4.0.8", + "prop-types": "15.8.1", + "ts-object-utils": "0.0.5" + }, + "peerDependencies": { + "react": ">= 15.2.1", + "react-native": ">= 0.30.0" + } + }, "node_modules/@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -50106,6 +50135,11 @@ "node": ">=6.10" } }, + "node_modules/ts-object-utils": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/ts-object-utils/-/ts-object-utils-0.0.5.tgz", + "integrity": "sha512-iV0GvHqOmilbIKJsfyfJY9/dNHCs969z3so90dQWsO1eMMozvTpnB1MEaUbb3FYtZTGjv5sIy/xmslEz0Rg2TA==" + }, "node_modules/ts-pnp": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", @@ -59447,6 +59481,27 @@ } } }, + "@shopify/flash-list": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@shopify/flash-list/-/flash-list-1.6.1.tgz", + "integrity": "sha512-SlBlpP7+zol6D1SKaf402aS30Qgwdjwb8/Qn2CupYwXnTcu2l8TiXB766vcsIvKTqUO7ELfQnCwCq8NXx55FsQ==", + "requires": { + "recyclerlistview": "4.2.0", + "tslib": "2.4.0" + }, + "dependencies": { + "recyclerlistview": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/recyclerlistview/-/recyclerlistview-4.2.0.tgz", + "integrity": "sha512-uuBCi0c+ggqHKwrzPX4Z/mJOzsBbjZEAwGGmlwpD/sD7raXixdAbdJ6BTcAmuWG50Cg4ru9p12M94Njwhr/27A==", + "requires": { + "lodash.debounce": "4.0.8", + "prop-types": "15.8.1", + "ts-object-utils": "0.0.5" + } + } + } + }, "@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -88988,6 +89043,11 @@ "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", "dev": true }, + "ts-object-utils": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/ts-object-utils/-/ts-object-utils-0.0.5.tgz", + "integrity": "sha512-iV0GvHqOmilbIKJsfyfJY9/dNHCs969z3so90dQWsO1eMMozvTpnB1MEaUbb3FYtZTGjv5sIy/xmslEz0Rg2TA==" + }, "ts-pnp": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", diff --git a/package.json b/package.json index cd5c6034161a..e2fbf3da8ca1 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "@react-navigation/stack": "6.3.16", "@react-ng/bounds-observer": "^0.2.1", "@rnmapbox/maps": "^10.0.11", + "@shopify/flash-list": "^1.6.1", "@types/node": "^18.14.0", "@ua/react-native-airship": "^15.2.6", "awesome-phonenumber": "^5.4.0", From 22e6de2d4c46bffb918d12b01b66741176d64e0d Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Sat, 7 Oct 2023 11:38:48 +0200 Subject: [PATCH 088/254] chore: add flashlist to the jest setup --- jest/setup.js | 1 + 1 file changed, 1 insertion(+) diff --git a/jest/setup.js b/jest/setup.js index 4def7d1efad5..a54a90678491 100644 --- a/jest/setup.js +++ b/jest/setup.js @@ -1,5 +1,6 @@ import 'setimmediate'; import 'react-native-gesture-handler/jestSetup'; +import '@shopify/flash-list/jestSetup'; import * as reanimatedJestUtils from 'react-native-reanimated/src/reanimated2/jestUtils'; import mockClipboard from '@react-native-clipboard/clipboard/jest/clipboard-mock'; import setupMockImages from './setupMockImages'; From 0de9d9d5d4b1ef105d7bbf80b9701e9f22f8585f Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Mon, 9 Oct 2023 11:58:14 +0200 Subject: [PATCH 089/254] refactor: migrate PaymentMethodList to use FlashList --- .../settings/Wallet/PaymentMethodList.js | 96 ++++++++++--------- 1 file changed, 52 insertions(+), 44 deletions(-) diff --git a/src/pages/settings/Wallet/PaymentMethodList.js b/src/pages/settings/Wallet/PaymentMethodList.js index 2a533a784a62..145864b75f9e 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.js +++ b/src/pages/settings/Wallet/PaymentMethodList.js @@ -1,17 +1,14 @@ import _ from 'underscore'; import React, {useCallback, useMemo} from 'react'; import PropTypes from 'prop-types'; -import {FlatList} from 'react-native'; +import {FlashList} from '@shopify/flash-list'; import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; -import {withNetwork} from '../../../components/OnyxProvider'; import styles from '../../../styles/styles'; import * as StyleUtils from '../../../styles/StyleUtils'; import MenuItem from '../../../components/MenuItem'; import Button from '../../../components/Button'; import Text from '../../../components/Text'; -import compose from '../../../libs/compose'; -import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import ONYXKEYS from '../../../ONYXKEYS'; import CONST from '../../../CONST'; import * as Expensicons from '../../../components/Icon/Expensicons'; @@ -21,6 +18,8 @@ import * as PaymentUtils from '../../../libs/PaymentUtils'; import FormAlertWrapper from '../../../components/FormAlertWrapper'; import OfflineWithFeedback from '../../../components/OfflineWithFeedback'; import * as PaymentMethods from '../../../libs/actions/PaymentMethods'; +import useLocalize from '../../../hooks/useLocalize'; +import useNetwork from '../../../hooks/useNetwork'; import Log from '../../../libs/Log'; import stylePropTypes from '../../../styles/stylePropTypes'; import Navigation from '../../../libs/Navigation/Navigation'; @@ -84,6 +83,9 @@ const propTypes = { /** Callback for whenever FlatList component size changes */ onListContentSizeChange: PropTypes.func, + /** Should menu items be selectable with a checkbox */ + shouldShowSelectedState: PropTypes.bool, + /** React ref being forwarded to the PaymentMethodList Button */ buttonRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), @@ -92,8 +94,6 @@ const propTypes = { /** List container style */ style: stylePropTypes, - - ...withLocalizePropTypes, }; const defaultProps = { @@ -118,6 +118,7 @@ const defaultProps = { onListContentSizeChange: () => {}, shouldEnableScroll: true, style: {}, + shouldShowSelectedState: false, }; /** @@ -173,6 +174,13 @@ function shouldShowDefaultBadge(filteredPaymentMethods, isDefault = false) { function isPaymentMethodActive(actionPaymentMethodType, activePaymentMethodID, paymentMethod) { return paymentMethod.accountType === actionPaymentMethodType && paymentMethod.methodID === activePaymentMethodID; } + +function keyExtractor(item) { + return item.key; +} + +const ESTIMATED_ITEM_SIZE = 81; + function PaymentMethodList({ actionPaymentMethodType, activePaymentMethodID, @@ -183,7 +191,6 @@ function PaymentMethodList({ filterType, isLoadingPaymentMethods, listHeaderComponent, - network, onListContentSizeChange, onPress, shouldEnableScroll, @@ -194,11 +201,11 @@ function PaymentMethodList({ shouldShowAssignedCards, selectedMethodID, style, - translate, }) { - const filteredPaymentMethods = useMemo(() => { - const paymentCardList = fundList || {}; + const {translate} = useLocalize(); + const {isOffline: isNetworkOffline} = useNetwork(); + const filteredPaymentMethods = useMemo(() => { if (shouldShowAssignedCards) { const assignedCards = _.chain(cardList) .filter((card) => CONST.EXPENSIFY_CARD.ACTIVE_STATES.includes(card.state)) @@ -222,6 +229,8 @@ function PaymentMethodList({ }); } + const paymentCardList = fundList || {}; + // Hide any billing cards that are not P2P debit cards for now because you cannot make them your default method, or delete them const filteredCardList = _.filter(paymentCardList, (card) => card.accountData.additionalData.isP2PDebitCard); let combinedPaymentMethods = PaymentUtils.formatPaymentMethods(bankAccountList, filteredCardList); @@ -230,25 +239,27 @@ function PaymentMethodList({ combinedPaymentMethods = _.filter(combinedPaymentMethods, (paymentMethod) => paymentMethod.accountType === filterType); } - if (!network.isOffline) { + if (!isNetworkOffline) { combinedPaymentMethods = _.filter( combinedPaymentMethods, (paymentMethod) => paymentMethod.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !_.isEmpty(paymentMethod.errors), ); } - combinedPaymentMethods = _.map(combinedPaymentMethods, (paymentMethod) => ({ - ...paymentMethod, - onPress: (e) => onPress(e, paymentMethod.accountType, paymentMethod.accountData, paymentMethod.isDefault, paymentMethod.methodID), - iconFill: isPaymentMethodActive(actionPaymentMethodType, activePaymentMethodID, paymentMethod) ? StyleUtils.getIconFillColor(CONST.BUTTON_STATES.PRESSED) : null, - wrapperStyle: isPaymentMethodActive(actionPaymentMethodType, activePaymentMethodID, paymentMethod) - ? [StyleUtils.getButtonBackgroundColorStyle(CONST.BUTTON_STATES.PRESSED)] - : null, - disabled: paymentMethod.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - })); + combinedPaymentMethods = _.map(combinedPaymentMethods, (paymentMethod) => { + const isMethodActive = isPaymentMethodActive(actionPaymentMethodType, activePaymentMethodID, paymentMethod); + + return { + ...paymentMethod, + onPress: (e) => onPress(e, paymentMethod.accountType, paymentMethod.accountData, paymentMethod.isDefault, paymentMethod.methodID), + iconFill: isMethodActive ? StyleUtils.getIconFillColor(CONST.BUTTON_STATES.PRESSED) : null, + wrapperStyle: isMethodActive ? [StyleUtils.getButtonBackgroundColorStyle(CONST.BUTTON_STATES.PRESSED)] : null, + disabled: paymentMethod.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }; + }); return combinedPaymentMethods; - }, [fundList, shouldShowAssignedCards, bankAccountList, filterType, network.isOffline, cardList, translate, actionPaymentMethodType, activePaymentMethodID, onPress]); + }, [shouldShowAssignedCards, fundList, bankAccountList, filterType, isNetworkOffline, cardList, translate, actionPaymentMethodType, activePaymentMethodID, onPress]); /** * Render placeholder when there are no payments methods @@ -309,10 +320,11 @@ function PaymentMethodList({ return ( <> - item.key} + keyExtractor={keyExtractor} ListEmptyComponent={shouldShowEmptyListMessage ? renderListEmptyComponent : null} ListHeaderComponent={listHeaderComponent} ListFooterComponent={shouldShowAddBankAccount ? renderListFooterComponent : null} @@ -347,24 +359,20 @@ PaymentMethodList.propTypes = propTypes; PaymentMethodList.defaultProps = defaultProps; PaymentMethodList.displayName = 'PaymentMethodList'; -export default compose( - withLocalize, - withNetwork(), - withOnyx({ - bankAccountList: { - key: ONYXKEYS.BANK_ACCOUNT_LIST, - }, - cardList: { - key: ONYXKEYS.CARD_LIST, - }, - fundList: { - key: ONYXKEYS.FUND_LIST, - }, - isLoadingPaymentMethods: { - key: ONYXKEYS.IS_LOADING_PAYMENT_METHODS, - }, - userWallet: { - key: ONYXKEYS.USER_WALLET, - }, - }), -)(PaymentMethodList); +export default withOnyx({ + bankAccountList: { + key: ONYXKEYS.BANK_ACCOUNT_LIST, + }, + cardList: { + key: ONYXKEYS.CARD_LIST, + }, + fundList: { + key: ONYXKEYS.FUND_LIST, + }, + isLoadingPaymentMethods: { + key: ONYXKEYS.IS_LOADING_PAYMENT_METHODS, + }, + userWallet: { + key: ONYXKEYS.USER_WALLET, + }, +})(PaymentMethodList); From 6f098762b87e7ce9e61f0d38e86536c4e601d1fe Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Mon, 9 Oct 2023 17:25:33 +0200 Subject: [PATCH 090/254] fix: lint error with missing props declaration --- src/pages/settings/Wallet/PaymentMethodList.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/Wallet/PaymentMethodList.js b/src/pages/settings/Wallet/PaymentMethodList.js index 145864b75f9e..2b369099f124 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.js +++ b/src/pages/settings/Wallet/PaymentMethodList.js @@ -190,16 +190,16 @@ function PaymentMethodList({ fundList, filterType, isLoadingPaymentMethods, - listHeaderComponent, - onListContentSizeChange, onPress, - shouldEnableScroll, shouldShowSelectedState, shouldShowAddPaymentMethodButton, shouldShowAddBankAccount, shouldShowEmptyListMessage, shouldShowAssignedCards, selectedMethodID, + listHeaderComponent, + onListContentSizeChange, + shouldEnableScroll, style, }) { const {translate} = useLocalize(); From 188326efb4c0d2d1a6f062d185fbed14cd294068 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Mon, 9 Oct 2023 18:22:37 +0200 Subject: [PATCH 091/254] fix: improved estimated_item_size for list config, added jsdoc comments --- src/pages/settings/Wallet/PaymentMethodList.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Wallet/PaymentMethodList.js b/src/pages/settings/Wallet/PaymentMethodList.js index 2b369099f124..280fae13ea40 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.js +++ b/src/pages/settings/Wallet/PaymentMethodList.js @@ -175,11 +175,20 @@ function isPaymentMethodActive(actionPaymentMethodType, activePaymentMethodID, p return paymentMethod.accountType === actionPaymentMethodType && paymentMethod.methodID === activePaymentMethodID; } +/** + * @param {Object} item + * @returns {String} + */ function keyExtractor(item) { return item.key; } -const ESTIMATED_ITEM_SIZE = 81; +/** + * This is a part of the FlashList configuration. It is used to estimate the size of each item in the list. + * https://shopify.github.io/flash-list/docs/usage/#estimateditemsize + * Measured using Element Inspector: 64. + */ +const ESTIMATED_ITEM_SIZE = 64; function PaymentMethodList({ actionPaymentMethodType, From 9beba29d8cd3fbc95723ae061aa9e99dba9ed519 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Tue, 17 Oct 2023 18:36:18 -0400 Subject: [PATCH 092/254] pass in prev notification preference --- src/libs/actions/Report.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index d7c8be68977c..96975d9b2383 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1352,9 +1352,9 @@ function toggleSubscribeToChildReport(childReportID = '0', parentReportAction = if (childReportID !== '0') { openReport(childReportID); if (!prevNotificationPreference || prevNotificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { - updateNotificationPreference(childReportID, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false); + updateNotificationPreference(childReportID, prevNotificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false); } else { - updateNotificationPreference(childReportID, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, false); + updateNotificationPreference(childReportID, prevNotificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, false); } } else { const participantAccountIDs = _.uniq([currentUserAccountID, Number(parentReportAction.actorAccountID)]); From 341d267aff9c5c1ec9fa86832cb2c226c6434c38 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Tue, 17 Oct 2023 21:33:38 -0400 Subject: [PATCH 093/254] update notification preferences optimistically --- src/libs/actions/Report.js | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 96975d9b2383..223ee41a5991 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1311,8 +1311,10 @@ function saveReportActionDraftNumberOfLines(reportID, reportActionID, numberOfLi * @param {String} previousValue * @param {String} newValue * @param {boolean} navigate + * @param {String} parentReportID + * @param {String} parentReportActionID */ -function updateNotificationPreference(reportID, previousValue, newValue, navigate) { +function updateNotificationPreference(reportID, previousValue, newValue, navigate, parentReportID = 0, parentReportActionID = 0) { if (previousValue === newValue) { if (navigate) { Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(reportID)); @@ -1333,7 +1335,23 @@ function updateNotificationPreference(reportID, previousValue, newValue, navigat value: {notificationPreference: previousValue}, }, ]; - API.write('UpdateReportNotificationPreference', {reportID, notificationPreference: newValue}, {optimisticData, failureData}); + if (parentReportID && parentReportActionID) { + optimisticData.push( + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, + value: {[parentReportActionID] : {childReportNotificationPreference: newValue}}, + } + ); + failureData.push( + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, + value: {[parentReportActionID] : {childReportNotificationPreference: previousValue}}, + } + ) + } + API.write('UpdateReportNotificationPreference', {reportID, notificationPreference: newValue, parentReportID, parentReportActionID}, {optimisticData, failureData}); if (navigate) { Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(reportID)); } @@ -1351,10 +1369,11 @@ function updateNotificationPreference(reportID, previousValue, newValue, navigat function toggleSubscribeToChildReport(childReportID = '0', parentReportAction = {}, parentReportID = '0', prevNotificationPreference) { if (childReportID !== '0') { openReport(childReportID); + const parentReportActionID = lodashGet(parentReportAction, 'reportActionID', '0'); if (!prevNotificationPreference || prevNotificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { - updateNotificationPreference(childReportID, prevNotificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false); + updateNotificationPreference(childReportID, prevNotificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false, parentReportID, parentReportActionID); } else { - updateNotificationPreference(childReportID, prevNotificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, false); + updateNotificationPreference(childReportID, prevNotificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, false, parentReportID, parentReportActionID); } } else { const participantAccountIDs = _.uniq([currentUserAccountID, Number(parentReportAction.actorAccountID)]); From e6851b38c971df65cf722272100efcad5cfc3a7d Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Tue, 17 Oct 2023 22:30:28 -0400 Subject: [PATCH 094/254] prettier --- src/libs/actions/Report.js | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 223ee41a5991..8506807a0fff 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1336,20 +1336,16 @@ function updateNotificationPreference(reportID, previousValue, newValue, navigat }, ]; if (parentReportID && parentReportActionID) { - optimisticData.push( - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, - value: {[parentReportActionID] : {childReportNotificationPreference: newValue}}, - } - ); - failureData.push( - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, - value: {[parentReportActionID] : {childReportNotificationPreference: previousValue}}, - } - ) + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, + value: {[parentReportActionID]: {childReportNotificationPreference: newValue}}, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, + value: {[parentReportActionID]: {childReportNotificationPreference: previousValue}}, + }); } API.write('UpdateReportNotificationPreference', {reportID, notificationPreference: newValue, parentReportID, parentReportActionID}, {optimisticData, failureData}); if (navigate) { From d30f297412c3450f1175c5e2cdf4d58f50fcac60 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Tue, 17 Oct 2023 22:42:38 -0400 Subject: [PATCH 095/254] update notification preference on new reports --- src/libs/actions/Report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 8506807a0fff..6dd66d570450 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1391,7 +1391,7 @@ function toggleSubscribeToChildReport(childReportID = '0', parentReportAction = const participantLogins = PersonalDetailsUtils.getLoginsByAccountIDs(newChat.participantAccountIDs); openReport(newChat.reportID, participantLogins, newChat, parentReportAction.reportActionID); - Navigation.navigate(ROUTES.getReportRoute(newChat.reportID)); + updateNotificationPreference(newChat.reportID, prevNotificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false, parentReportID, parentReportAction.reportActionID); } } From f9c66e185ccf95ef0ac17c5f9fb1e36f0ed7c77f Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Wed, 18 Oct 2023 14:46:33 +0530 Subject: [PATCH 096/254] added allOptions to useCallback dependencies --- src/components/SelectionList/BaseSelectionList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index fdb1f92ca73b..685c6cb7c0d4 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -176,7 +176,7 @@ function BaseSelectionList({ // If we don't disable dependencies here, we would need to make sure that the `sections` prop is stable in every usage of this component. // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [flattenedSections.allOptions]); /** * Logic to run when a row is selected, either with click/press or keyboard hotkeys. From a5980135dbb4175fcae978585e272fb26a9bfe93 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Wed, 18 Oct 2023 15:13:48 +0530 Subject: [PATCH 097/254] fix: prettier diff detected --- .../SelectionList/BaseSelectionList.js | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 685c6cb7c0d4..47a947bc2db5 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -152,31 +152,34 @@ function BaseSelectionList({ * @param {Number} index - the index of the item to scroll to * @param {Boolean} animated - whether to animate the scroll */ - const scrollToIndex = useCallback((index, animated = true) => { - const item = flattenedSections.allOptions[index]; + const scrollToIndex = useCallback( + (index, animated = true) => { + const item = flattenedSections.allOptions[index]; - if (!listRef.current || !item) { - return; - } + if (!listRef.current || !item) { + return; + } - const itemIndex = item.index; - const sectionIndex = item.sectionIndex; + const itemIndex = item.index; + const sectionIndex = item.sectionIndex; - // Note: react-native's SectionList automatically strips out any empty sections. - // So we need to reduce the sectionIndex to remove any empty sections in front of the one we're trying to scroll to. - // Otherwise, it will cause an index-out-of-bounds error and crash the app. - let adjustedSectionIndex = sectionIndex; - for (let i = 0; i < sectionIndex; i++) { - if (_.isEmpty(lodashGet(sections, `[${i}].data`))) { - adjustedSectionIndex--; + // Note: react-native's SectionList automatically strips out any empty sections. + // So we need to reduce the sectionIndex to remove any empty sections in front of the one we're trying to scroll to. + // Otherwise, it will cause an index-out-of-bounds error and crash the app. + let adjustedSectionIndex = sectionIndex; + for (let i = 0; i < sectionIndex; i++) { + if (_.isEmpty(lodashGet(sections, `[${i}].data`))) { + adjustedSectionIndex--; + } } - } - listRef.current.scrollToLocation({sectionIndex: adjustedSectionIndex, itemIndex, animated, viewOffset: variables.contentHeaderHeight}); + listRef.current.scrollToLocation({sectionIndex: adjustedSectionIndex, itemIndex, animated, viewOffset: variables.contentHeaderHeight}); - // If we don't disable dependencies here, we would need to make sure that the `sections` prop is stable in every usage of this component. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [flattenedSections.allOptions]); + // If we don't disable dependencies here, we would need to make sure that the `sections` prop is stable in every usage of this component. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, + [flattenedSections.allOptions], + ); /** * Logic to run when a row is selected, either with click/press or keyboard hotkeys. From bd686b2dee356ed680bc74c3ece8902e6e6efd0d Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Wed, 18 Oct 2023 11:48:57 +0200 Subject: [PATCH 098/254] Code review changes --- src/components/Form/FormProvider.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/Form/FormProvider.js b/src/components/Form/FormProvider.js index becdbf99e136..35abee3d3776 100644 --- a/src/components/Form/FormProvider.js +++ b/src/components/Form/FormProvider.js @@ -227,13 +227,15 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC .first() .value() || ''; + const value = !_.isUndefined(inputValues[`${inputID}ToDisplay`]) ? inputValues[`${inputID}ToDisplay`] : inputValues[inputID]; + return { ...propsToParse, ref: newRef, inputID, key: propsToParse.key || inputID, errorText: errors[inputID] || fieldErrorMessage, - value: propsToParse.valueParser && !_.isUndefined(inputValues[`${inputID}ToDisplay`]) ? inputValues[`${inputID}ToDisplay`] : inputValues[inputID], + value, // As the text input is controlled, we never set the defaultValue prop // as this is already happening by the value prop. defaultValue: undefined, @@ -273,18 +275,18 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC propsToParse.onBlur(event); } }, - onInputChange: (value, key) => { + onInputChange: (inputValue, key) => { const inputKey = key || inputID; setInputValues((prevState) => { const newState = _.isFunction(propsToParse.valueParser) ? { ...prevState, - [inputKey]: propsToParse.valueParser(value), - [`${inputKey}ToDisplay`]: value, + [inputKey]: propsToParse.valueParser(inputValue), + [`${inputKey}ToDisplay`]: inputValue, } : { ...prevState, - [inputKey]: value, + [inputKey]: inputValue, }; if (shouldValidateOnChange) { @@ -294,11 +296,11 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC }); if (propsToParse.shouldSaveDraft) { - FormActions.setDraftValues(propsToParse.formID, {[inputKey]: value}); + FormActions.setDraftValues(propsToParse.formID, {[inputKey]: inputValue}); } if (_.isFunction(propsToParse.onValueChange)) { - propsToParse.onValueChange(value, inputKey); + propsToParse.onValueChange(inputValue, inputKey); } }, }; From 947220bace13ce281031d7b18459f8e705abfe71 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 18 Oct 2023 13:07:43 +0200 Subject: [PATCH 099/254] Adjust after internal review --- src/components/FormScrollView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/FormScrollView.tsx b/src/components/FormScrollView.tsx index 3705e5e7ffeb..17e302f61636 100644 --- a/src/components/FormScrollView.tsx +++ b/src/components/FormScrollView.tsx @@ -1,8 +1,8 @@ import React, {ForwardedRef} from 'react'; -import {ScrollView} from 'react-native'; +import {ScrollView, ScrollViewProps} from 'react-native'; import styles from '../styles/styles'; -type FormScrollViewProps = Partial & { +type FormScrollViewProps = ScrollViewProps & { /** Form elements */ children: React.ReactNode; }; From db319a7b9114bdbb3b9489def5bcf04051ae57ac Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Wed, 18 Oct 2023 16:42:26 +0530 Subject: [PATCH 100/254] added sections to scrollToIndex dependencies --- src/components/SelectionList/BaseSelectionList.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 47a947bc2db5..a2f3661f0c59 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -174,11 +174,8 @@ function BaseSelectionList({ } listRef.current.scrollToLocation({sectionIndex: adjustedSectionIndex, itemIndex, animated, viewOffset: variables.contentHeaderHeight}); - - // If we don't disable dependencies here, we would need to make sure that the `sections` prop is stable in every usage of this component. - // eslint-disable-next-line react-hooks/exhaustive-deps }, - [flattenedSections.allOptions], + [flattenedSections.allOptions, sections], ); /** From 2ca79c84e1a50d16f53a01e49087e602101d3b86 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 18 Oct 2023 10:17:33 -0700 Subject: [PATCH 101/254] Invited secondary login message row below item --- src/components/SelectionList/BaseListItem.js | 10 ++++++++-- src/components/SelectionList/UserListItem.js | 5 ----- src/styles/utilities/spacing.ts | 4 ++++ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/components/SelectionList/BaseListItem.js b/src/components/SelectionList/BaseListItem.js index 171a58ee9fa9..497bba20ceb4 100644 --- a/src/components/SelectionList/BaseListItem.js +++ b/src/components/SelectionList/BaseListItem.js @@ -12,6 +12,8 @@ import UserListItem from './UserListItem'; import RadioListItem from './RadioListItem'; import OfflineWithFeedback from '../OfflineWithFeedback'; import CONST from '../../CONST'; +import useLocalize from '../../hooks/useLocalize'; +import Text from '../Text'; function BaseListItem({ item, @@ -23,6 +25,7 @@ function BaseListItem({ onSelectRow, onDismissError = () => {}, }) { + const {translate} = useLocalize(); const isUserItem = lodashGet(item, 'icons.length', 0) > 0; const ListItem = isUserItem ? UserListItem : RadioListItem; @@ -76,7 +79,6 @@ function BaseListItem({
)} - - {!canSelectMultiple && item.isSelected && ( )} + {Boolean(item.invitedSecondaryLogin) && ( + + {translate('workspace.people.invitedBySecondaryLogin', {secondaryLogin: item.invitedSecondaryLogin})} + + )} ); diff --git a/src/components/SelectionList/UserListItem.js b/src/components/SelectionList/UserListItem.js index faef86b68aed..436ae8cb056b 100644 --- a/src/components/SelectionList/UserListItem.js +++ b/src/components/SelectionList/UserListItem.js @@ -6,10 +6,8 @@ import Text from '../Text'; import {userListItemPropTypes} from './selectionListPropTypes'; import Tooltip from '../Tooltip'; import SubscriptAvatar from '../SubscriptAvatar'; -import useLocalize from '../../hooks/useLocalize'; function UserListItem({item, isFocused = false, showTooltip}) { - const {translate} = useLocalize(); return ( <> {Boolean(item.icons) && ( @@ -44,9 +42,6 @@ function UserListItem({item, isFocused = false, showTooltip}) { )} - {Boolean(item.invitedSecondaryLogin) && ( - {translate('workspace.people.invitedBySecondaryLogin', {secondaryLogin: item.invitedSecondaryLogin})} - )} {Boolean(item.rightElement) && item.rightElement} diff --git a/src/styles/utilities/spacing.ts b/src/styles/utilities/spacing.ts index e2b161ca0d62..4d30d9136785 100644 --- a/src/styles/utilities/spacing.ts +++ b/src/styles/utilities/spacing.ts @@ -155,6 +155,10 @@ export default { marginLeft: 32, }, + ml9: { + marginLeft: 36, + }, + ml10: { marginLeft: 40, }, From 81c8c58687a01b43e711c989710fc0e6a1335e5f Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Thu, 19 Oct 2023 05:01:14 +0530 Subject: [PATCH 102/254] plaid modal opne --- src/ONYXKEYS.ts | 3 +++ src/components/AddPlaidBankAccount.js | 1 + src/components/PlaidLink/index.native.js | 7 ++++++- src/libs/actions/BankAccounts.ts | 7 ++++++- .../ReimbursementAccount/ReimbursementAccountPage.js | 9 +++++++-- 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index c3003699378c..8bf8ba0fc5c5 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -119,6 +119,9 @@ const ONYXKEYS = { /** Token needed to initialize Plaid link */ PLAID_LINK_TOKEN: 'plaidLinkToken', + /** Capture Plaid event */ + PLAID_CURRENT_EVENT: 'plaidCurrentEvent', + /** Token needed to initialize Onfido */ ONFIDO_TOKEN: 'onfidoToken', diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js index dbe7e46ff6aa..246514f3420a 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.js @@ -203,6 +203,7 @@ function AddPlaidBankAccount({ Log.hmmm('[PlaidLink] Error: ', error.message); }} onEvent={(event, metadata) => { + BankAccounts.setPlaidEvent(event); // Handle Plaid login errors (will potentially reset plaid token and item depending on the error) if (event === 'ERROR') { Log.hmmm('[PlaidLink] Error: ', metadata); diff --git a/src/components/PlaidLink/index.native.js b/src/components/PlaidLink/index.native.js index 48cd41e283c3..71ed551af235 100644 --- a/src/components/PlaidLink/index.native.js +++ b/src/components/PlaidLink/index.native.js @@ -1,5 +1,5 @@ import {useEffect} from 'react'; -import {openLink, useDeepLinkRedirector, usePlaidEmitter} from 'react-native-plaid-link-sdk'; +import {openLink, useDeepLinkRedirector, usePlaidEmitter, dismissLink} from 'react-native-plaid-link-sdk'; import Log from '../../libs/Log'; import {plaidLinkPropTypes, plaidLinkDefaultProps} from './plaidLinkPropTypes'; @@ -10,6 +10,7 @@ function PlaidLink(props) { props.onEvent(event.eventName, event.metadata); }); useEffect(() => { + props.onEvent('OPEN', {}); openLink({ tokenConfig: { token: props.token, @@ -23,6 +24,10 @@ function PlaidLink(props) { }, }); + return () => { + dismissLink(); + }; + // We generally do not need to include the token as a dependency here as it is only provided once via props and should not change // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 249d7de9293a..5b56e8321a3f 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -35,7 +35,7 @@ type ReimbursementAccountSubStep = BankAccountSubStep | ''; function clearPlaid(): Promise { Onyx.set(ONYXKEYS.PLAID_LINK_TOKEN, ''); - + Onyx.set(ONYXKEYS.PLAID_CURRENT_EVENT, null); return Onyx.set(ONYXKEYS.PLAID_DATA, PlaidDataProps.plaidDataDefaultProps); } @@ -43,6 +43,10 @@ function openPlaidView() { clearPlaid().then(() => ReimbursementAccount.setBankAccountSubStep(CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID)); } +function setPlaidEvent(eventName: string) { + Onyx.set(ONYXKEYS.PLAID_CURRENT_EVENT, eventName); +} + /** * Open the personal bank account setup flow, with an optional exitReportID to redirect to once the flow is finished. */ @@ -426,6 +430,7 @@ export { clearOnfidoToken, clearPersonalBankAccount, clearPlaid, + setPlaidEvent, openPlaidView, connectBankAccountManually, connectBankAccountWithPlaid, diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index a99e3d7332a0..e37058c2a21d 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -340,6 +340,7 @@ class ReimbursementAccountPage extends React.Component { } if (subStep) { BankAccounts.setBankAccountSubStep(null); + BankAccounts.setPlaidEvent(null); } else { Navigation.goBack(backTo); } @@ -396,8 +397,9 @@ class ReimbursementAccountPage extends React.Component { ); } - - const isLoading = this.props.isLoadingReportData || this.props.account.isLoading || this.props.reimbursementAccount.isLoading; + const isLoading = + (this.props.isLoadingReportData || this.props.account.isLoading || this.props.reimbursementAccount.isLoading) && + (!this.props.plaidCurrentEvent || this.props.plaidCurrentEvent === 'EXIT'); // Prevent the full-page blocking offline view from being displayed for these steps if the device goes offline. const shouldShowOfflineLoader = !( @@ -551,6 +553,9 @@ export default compose( plaidLinkToken: { key: ONYXKEYS.PLAID_LINK_TOKEN, }, + plaidCurrentEvent: { + key: ONYXKEYS.PLAID_CURRENT_EVENT, + }, onfidoToken: { key: ONYXKEYS.ONFIDO_TOKEN, }, From ae942cf4ef3eeebee9363e1a7fb1cccc9cd9aa3a Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Thu, 19 Oct 2023 10:58:11 +0700 Subject: [PATCH 103/254] Update request money header title --- src/pages/iou/steps/MoneyRequestConfirmPage.js | 4 ++++ .../MoneyRequestParticipantsPage.js | 16 +++++++++++++--- .../MoneyRequestParticipantsSelector.js | 8 +++++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 46367e275af4..616955e1cd80 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -308,6 +308,10 @@ function MoneyRequestConfirmPage(props) { return props.translate('common.send'); } + if (isScanRequest) { + return props.translate('tabSelector.scan'); + } + return props.translate('tabSelector.manual'); }; diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js index 25e41ba78556..fb3799336cc8 100644 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js @@ -54,6 +54,7 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route}) { const isScanRequest = MoneyRequestUtils.isScanRequest(selectedTab); const isSplitRequest = iou.id === CONST.IOU.MONEY_REQUEST_TYPE.SPLIT; const [headerTitle, setHeaderTitle] = useState(); + const [selectedParticipants, setSelectedParticipants] = useState([]); useEffect(() => { if (isDistanceRequest) { @@ -66,10 +67,18 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route}) { return; } - setHeaderTitle(_.isEmpty(iou.participants) ? translate('tabSelector.manual') : translate('iou.split')); - }, [iou.participants, isDistanceRequest, isSendRequest, translate]); + if (isScanRequest) { + setHeaderTitle(translate('tabSelector.scan')); + return; + } + + setHeaderTitle(_.isEmpty(selectedParticipants) ? translate('tabSelector.manual') : translate('iou.split')); + }, [selectedParticipants, isDistanceRequest, translate, isScanRequest]); const navigateToConfirmationStep = (moneyRequestType) => { + if (moneyRequestType === iouType.current) { + setSelectedParticipants([]); + } IOU.setMoneyRequestId(moneyRequestType); Navigation.navigate(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(moneyRequestType, reportID.current)); }; @@ -118,7 +127,7 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route}) { /> (optionsSelectorRef.current = el)} - participants={iou.participants} + participants={selectedParticipants} onAddParticipants={IOU.setMoneyRequestParticipants} navigateToRequest={() => navigateToConfirmationStep(iouType.current)} navigateToSplit={() => navigateToConfirmationStep(CONST.IOU.MONEY_REQUEST_TYPE.SPLIT)} @@ -126,6 +135,7 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route}) { iouType={iouType.current} isDistanceRequest={isDistanceRequest} isScanRequest={isScanRequest} + setSelectedParticipants={setSelectedParticipants} /> )} diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 547d2b7c363a..5a3a5a797946 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -47,6 +47,9 @@ const propTypes = { /** All of the personal details for everyone */ personalDetails: PropTypes.objectOf(personalDetailsPropType), + /** Callback to request parent modal to go to next step, which should be split */ + setSelectedParticipants: PropTypes.func.isRequired, + /** All reports shared with the user */ reports: PropTypes.objectOf(reportPropTypes), @@ -85,6 +88,7 @@ function MoneyRequestParticipantsSelector({ safeAreaPaddingBottomStyle, iouType, isDistanceRequest, + setSelectedParticipants, }) { const [searchTerm, setSearchTerm] = useState(''); const [newChatOptions, setNewChatOptions] = useState({ @@ -198,9 +202,11 @@ function MoneyRequestParticipantsSelector({ ]; } + setSelectedParticipants(newSelectedOptions); + onAddParticipants(newSelectedOptions); }, - [participants, onAddParticipants], + [participants, onAddParticipants, setSelectedParticipants], ); const headerMessage = OptionsListUtils.getHeaderMessage( From c34e28403dc574b6b5f5a6fffae3c6b5755ae1ba Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Thu, 19 Oct 2023 11:24:55 +0700 Subject: [PATCH 104/254] fix lint --- .../MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js index fb3799336cc8..9e2f1a3a1f45 100644 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js @@ -73,7 +73,7 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route}) { } setHeaderTitle(_.isEmpty(selectedParticipants) ? translate('tabSelector.manual') : translate('iou.split')); - }, [selectedParticipants, isDistanceRequest, translate, isScanRequest]); + }, [selectedParticipants, isDistanceRequest, translate, isScanRequest, isSendRequest]); const navigateToConfirmationStep = (moneyRequestType) => { if (moneyRequestType === iouType.current) { From 28105ea2ca5838ee6cb9230b836f7fa58636600e Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Thu, 19 Oct 2023 03:55:34 -0400 Subject: [PATCH 105/254] restore ios folder from main --- ios/NewExpensify.xcodeproj/project.pbxproj | 18 ++---------------- ios/NewExpensify/Info.plist | 4 ++-- ios/NewExpensifyTests/Info.plist | 4 ++-- ios/Podfile.lock | 14 +++++++------- ios/tmp.xcconfig | 10 +--------- 5 files changed, 14 insertions(+), 36 deletions(-) diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index d1cd2d066833..64ed3fda8b02 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -29,7 +29,7 @@ 70CF6E82262E297300711ADC /* BootSplash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 70CF6E81262E297300711ADC /* BootSplash.storyboard */; }; BDB853621F354EBB84E619C2 /* ExpensifyNewKansas-MediumItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = D2AFB39EC1D44BF9B91D3227 /* ExpensifyNewKansas-MediumItalic.otf */; }; DD79042B2792E76D004484B4 /* RCTBootSplash.m in Sources */ = {isa = PBXBuildFile; fileRef = DD79042A2792E76D004484B4 /* RCTBootSplash.m */; }; - E51DC681C7DEE40AEBDDFBFE /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; E9DF872D2525201700607FDC /* AirshipConfig.plist in Resources */ = {isa = PBXBuildFile; fileRef = E9DF872C2525201700607FDC /* AirshipConfig.plist */; }; ED222ED90E074A5481A854FA /* ExpensifyNeue-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 8B28D84EF339436DBD42A203 /* ExpensifyNeue-BoldItalic.otf */; }; F0C450EA2705020500FD2970 /* colors.json in Resources */ = {isa = PBXBuildFile; fileRef = F0C450E92705020500FD2970 /* colors.json */; }; @@ -124,7 +124,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E51DC681C7DEE40AEBDDFBFE /* (null) in Frameworks */, + E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */, 5A464BC8112CDB1DE1E38F1C /* libPods-NewExpensify.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -722,11 +722,9 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = NewExpensify/Chat.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 368M544MTT; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 368M544MTT; ENABLE_BITCODE = NO; INFOPLIST_FILE = "$(SRCROOT)/NewExpensify/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -740,7 +738,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev; PRODUCT_NAME = "New Expensify Dev"; PROVISIONING_PROFILE_SPECIFIER = expensify_chat_dev; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = expensify_chat_dev; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -757,11 +754,9 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = NewExpensify/Chat.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 368M544MTT; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 368M544MTT; INFOPLIST_FILE = NewExpensify/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -774,7 +769,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev; PRODUCT_NAME = "New Expensify Dev"; PROVISIONING_PROFILE_SPECIFIER = expensify_chat_dev; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = expensify_chat_dev; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -982,7 +976,6 @@ CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 368M544MTT; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 368M544MTT; ENABLE_BITCODE = NO; INFOPLIST_FILE = "$(SRCROOT)/NewExpensify/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -996,7 +989,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.chat.expensify.chat; PRODUCT_NAME = "New Expensify"; PROVISIONING_PROFILE_SPECIFIER = chat_expensify_appstore; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = chat_expensify_appstore; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1106,7 +1098,6 @@ CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 368M544MTT; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 368M544MTT; ENABLE_BITCODE = NO; INFOPLIST_FILE = "$(SRCROOT)/NewExpensify/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -1120,7 +1111,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.adhoc; PRODUCT_NAME = "New Expensify AdHoc"; PROVISIONING_PROFILE_SPECIFIER = chat_expensify_appstore; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = expensify_chat_adhoc; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1224,7 +1214,6 @@ CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 368M544MTT; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 368M544MTT; INFOPLIST_FILE = NewExpensify/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1237,7 +1226,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.chat.expensify.chat; PRODUCT_NAME = "New Expensify"; PROVISIONING_PROFILE_SPECIFIER = chat_expensify_appstore; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = chat_expensify_appstore; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -1338,7 +1326,6 @@ CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 368M544MTT; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 368M544MTT; INFOPLIST_FILE = NewExpensify/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1351,7 +1338,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.adhoc; PRODUCT_NAME = "New Expensify AdHoc"; PROVISIONING_PROFILE_SPECIFIER = chat_expensify_appstore; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = expensify_chat_adhoc; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index cb04e9c1ef90..8eed7d03e73f 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3.81 + 1.3.87 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.3.81.10 + 1.3.87.1 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index e597c10142d8..57b623864549 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.3.81 + 1.3.87 CFBundleSignature ???? CFBundleVersion - 1.3.81.10 + 1.3.87.1 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 54d0525fd3c9..cb120bca2b88 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -251,9 +251,9 @@ PODS: - nanopb/encode (= 2.30908.0) - nanopb/decode (2.30908.0) - nanopb/encode (2.30908.0) - - Onfido (27.4.0) - - onfido-react-native-sdk (7.4.0): - - Onfido (= 27.4.0) + - Onfido (28.3.0) + - onfido-react-native-sdk (8.3.0): + - Onfido (~> 28.3.0) - React - OpenSSL-Universal (1.1.1100) - Plaid (4.1.0) @@ -819,7 +819,7 @@ PODS: - SDWebImage/Core (~> 5.10) - SocketRocket (0.6.1) - Turf (2.6.1) - - VisionCamera (2.15.4): + - VisionCamera (2.16.2): - React - React-callinvoker - React-Core @@ -1204,8 +1204,8 @@ SPEC CHECKSUMS: MapboxMaps: af50ec61a7eb3b032c3f7962c6bd671d93d2a209 MapboxMobileEvents: de50b3a4de180dd129c326e09cd12c8adaaa46d6 nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 - Onfido: e36f284b865adcf99d9c905590a64ac09d4a576b - onfido-react-native-sdk: 4ecde1a97435dcff9f00a878e3f8d1eb14fabbdc + Onfido: c7d010d9793790d44a07799d9be25aa8e3814ee7 + onfido-react-native-sdk: b346a620af5669f9fecb6dc3052314a35a94ad9f OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c Plaid: 7d340abeadb46c7aa1a91f896c5b22395a31fcf2 PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef @@ -1287,7 +1287,7 @@ SPEC CHECKSUMS: SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 469ce2c3d22e5e8e4818d5a3b254699a5c89efa4 - VisionCamera: d3ec8883417a6a4a0e3a6ba37d81d22db7611601 + VisionCamera: 95f969b8950b411285579d633a1014782fe0e634 Yoga: 3efc43e0d48686ce2e8c60f99d4e6bd349aff981 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a diff --git a/ios/tmp.xcconfig b/ios/tmp.xcconfig index 2f2502669450..8b137891791f 100644 --- a/ios/tmp.xcconfig +++ b/ios/tmp.xcconfig @@ -1,9 +1 @@ -NEW_EXPENSIFY_URL=https:/$()/new.expensify.com/ -SECURE_EXPENSIFY_URL=https:/$()/secure.expensify.com/ -EXPENSIFY_URL=https:/$()/www.expensify.com/ -EXPENSIFY_PARTNER_NAME=chat-expensify-com -EXPENSIFY_PARTNER_PASSWORD=e21965746fd75f82bb66 -PUSHER_APP_KEY=268df511a204fbb60884 -USE_WEB_PROXY=false -ENVIRONMENT=production -SEND_CRASH_REPORTS=true + From c7a0c606782349385f45fee20cf463b28141787a Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Thu, 19 Oct 2023 04:06:21 -0400 Subject: [PATCH 106/254] cleanup changes --- src/languages/es.ts | 120 +++++++++--------- .../report/ContextMenu/ContextMenuActions.js | 4 +- 2 files changed, 61 insertions(+), 63 deletions(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 5b03ff63438d..1e3ea723288c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2,81 +2,81 @@ import CONST from '../CONST'; import * as ReportActionsUtils from '../libs/ReportActionsUtils'; import type { AddressLineParams, - CharacterLimitParams, - MaxParticipantsReachedParams, - ZipCodeExampleFormatParams, - LoggedInAsParams, - NewFaceEnterMagicCodeParams, - WelcomeEnterMagicCodeParams, AlreadySignedInParams, - GoBackMessageParams, - LocalTimeParams, - EditActionParams, - DeleteActionParams, - DeleteConfirmationParams, - BeginningOfChatHistoryDomainRoomPartOneParams, + AmountEachParams, BeginningOfChatHistoryAdminRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartTwo, - WelcomeToRoomParams, - ReportArchiveReasonsClosedParams, - ReportArchiveReasonsMergedParams, - ReportArchiveReasonsRemovedFromPolicyParams, - ReportArchiveReasonsPolicyDeletedParams, - RequestCountParams, - SettleExpensifyCardParams, - RequestAmountParams, - SplitAmountParams, + BeginningOfChatHistoryDomainRoomPartOneParams, + CharacterLimitParams, + ConfirmThatParams, + DateShouldBeAfterParams, + DateShouldBeBeforeParams, + DeleteActionParams, + DeleteConfirmationParams, DidSplitAmountMessageParams, - AmountEachParams, + EditActionParams, + EnglishTranslation, + EnterMagicCodeParams, + FormattedMaxLengthParams, + GoBackMessageParams, + GoToRoomParams, + IncorrectZipFormatParams, + InstantSummaryParams, + LocalTimeParams, + LoggedInAsParams, + ManagerApprovedParams, + MaxParticipantsReachedParams, + NewFaceEnterMagicCodeParams, + NoLongerHaveAccessParams, + NotAllowedExtensionParams, + NotYouParams, + OOOEventSummaryFullDayParams, + OOOEventSummaryPartialDayParams, + OurEmailProviderParams, + PaidElsewhereWithAmountParams, + PaidWithExpensifyWithAmountParams, + ParentNavigationSummaryParams, PayerOwesAmountParams, PayerOwesParams, PayerPaidAmountParams, PayerPaidParams, PayerSettledParams, - WaitingOnBankAccountParams, + RemovedTheRequestParams, + RenamedRoomActionParams, + ReportArchiveReasonsClosedParams, + ReportArchiveReasonsMergedParams, + ReportArchiveReasonsPolicyDeletedParams, + ReportArchiveReasonsRemovedFromPolicyParams, + RequestAmountParams, + RequestCountParams, + RequestedAmountMessageParams, + ResolutionConstraintsParams, + RoomNameReservedErrorParams, + RoomRenamedToParams, + SetTheDistanceParams, + SetTheRequestParams, + SettleExpensifyCardParams, SettledAfterAddedBankAccountParams, - PaidElsewhereWithAmountParams, - PaidWithExpensifyWithAmountParams, + SizeExceededParams, + SplitAmountParams, + StepCounterParams, + TagSelectionParams, ThreadRequestReportNameParams, ThreadSentMoneyReportNameParams, - SizeExceededParams, - ResolutionConstraintsParams, - NotAllowedExtensionParams, - EnterMagicCodeParams, - TransferParams, - InstantSummaryParams, - NotYouParams, - DateShouldBeBeforeParams, - DateShouldBeAfterParams, - IncorrectZipFormatParams, - WeSentYouMagicSignInLinkParams, ToValidateLoginParams, - NoLongerHaveAccessParams, - OurEmailProviderParams, - ConfirmThatParams, + TransferParams, UntilTimeParams, - StepCounterParams, - UserIsAlreadyMemberParams, - GoToRoomParams, - WelcomeNoteParams, - RoomNameReservedErrorParams, - RenamedRoomActionParams, - RoomRenamedToParams, - OOOEventSummaryFullDayParams, - OOOEventSummaryPartialDayParams, - ParentNavigationSummaryParams, - ManagerApprovedParams, - SetTheRequestParams, - SetTheDistanceParams, - UpdatedTheRequestParams, UpdatedTheDistanceParams, - RemovedTheRequestParams, - FormattedMaxLengthParams, - RequestedAmountMessageParams, - TagSelectionParams, - EnglishTranslation, + UpdatedTheRequestParams, + UserIsAlreadyMemberParams, + WaitingOnBankAccountParams, WalletProgramParams, + WeSentYouMagicSignInLinkParams, + WelcomeEnterMagicCodeParams, + WelcomeNoteParams, + WelcomeToRoomParams, + ZipCodeExampleFormatParams, } from './types'; /* eslint-disable max-len */ @@ -417,8 +417,8 @@ export default { deleteConfirmation: ({action}: DeleteConfirmationParams) => `¿Estás seguro de que quieres eliminar este ${ReportActionsUtils.isMoneyRequestAction(action) ? 'pedido' : 'comentario'}`, onlyVisible: 'Visible sólo para', replyInThread: 'Responder en el hilo', - subscribeToThread: 'NEED TO TRANSLATE', - unsubscribeFromThread: 'NEED TO TRANSLATE', + subscribeToThread: 'Suscríbete al hilo', + unsubscribeFromThread: 'Darse de baja del hilo', flagAsOffensive: 'Marcar como ofensivo', }, emojiReactions: { diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 8006cd3e3f29..999f8e9fa26b 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -134,7 +134,7 @@ export default [ if (closePopover) { hideContextMenu(false, () => { ReportActionComposeFocusManager.focus(); - Report.navigateToAndOpenChildReport(lodashGet(reportAction, 'childReportID', ''), reportAction, reportID); + Report.navigateToAndOpenChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID); }); return; } @@ -146,7 +146,6 @@ export default [ { isAnonymousAction: false, textTranslateKey: 'reportActionContextMenu.subscribeToThread', - // textTranslateKey: lodashGet(reportAction, 'childReportNotificationPreference', '0'), icon: Expensicons.Bell, successTextTranslateKey: '', successIcon: null, @@ -179,7 +178,6 @@ export default [ { isAnonymousAction: false, textTranslateKey: 'reportActionContextMenu.unsubscribeFromThread', - // textTranslateKey: lodashGet(reportAction, 'childReportNotificationPreference', '0'), icon: Expensicons.BellSlash, successTextTranslateKey: '', successIcon: null, From 8a609fd5b22dd83baf79b48f65e481d02ac25598 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Thu, 19 Oct 2023 04:07:45 -0400 Subject: [PATCH 107/254] revert accidental change --- src/languages/es.ts | 116 ++++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 1e3ea723288c..0ecff93d2758 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2,81 +2,81 @@ import CONST from '../CONST'; import * as ReportActionsUtils from '../libs/ReportActionsUtils'; import type { AddressLineParams, + CharacterLimitParams, + MaxParticipantsReachedParams, + ZipCodeExampleFormatParams, + LoggedInAsParams, + NewFaceEnterMagicCodeParams, + WelcomeEnterMagicCodeParams, AlreadySignedInParams, - AmountEachParams, + GoBackMessageParams, + LocalTimeParams, + EditActionParams, + DeleteActionParams, + DeleteConfirmationParams, + BeginningOfChatHistoryDomainRoomPartOneParams, BeginningOfChatHistoryAdminRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartTwo, - BeginningOfChatHistoryDomainRoomPartOneParams, - CharacterLimitParams, - ConfirmThatParams, - DateShouldBeAfterParams, - DateShouldBeBeforeParams, - DeleteActionParams, - DeleteConfirmationParams, + WelcomeToRoomParams, + ReportArchiveReasonsClosedParams, + ReportArchiveReasonsMergedParams, + ReportArchiveReasonsRemovedFromPolicyParams, + ReportArchiveReasonsPolicyDeletedParams, + RequestCountParams, + SettleExpensifyCardParams, + RequestAmountParams, + SplitAmountParams, DidSplitAmountMessageParams, - EditActionParams, - EnglishTranslation, - EnterMagicCodeParams, - FormattedMaxLengthParams, - GoBackMessageParams, - GoToRoomParams, - IncorrectZipFormatParams, - InstantSummaryParams, - LocalTimeParams, - LoggedInAsParams, - ManagerApprovedParams, - MaxParticipantsReachedParams, - NewFaceEnterMagicCodeParams, - NoLongerHaveAccessParams, - NotAllowedExtensionParams, - NotYouParams, - OOOEventSummaryFullDayParams, - OOOEventSummaryPartialDayParams, - OurEmailProviderParams, - PaidElsewhereWithAmountParams, - PaidWithExpensifyWithAmountParams, - ParentNavigationSummaryParams, + AmountEachParams, PayerOwesAmountParams, PayerOwesParams, PayerPaidAmountParams, PayerPaidParams, PayerSettledParams, - RemovedTheRequestParams, - RenamedRoomActionParams, - ReportArchiveReasonsClosedParams, - ReportArchiveReasonsMergedParams, - ReportArchiveReasonsPolicyDeletedParams, - ReportArchiveReasonsRemovedFromPolicyParams, - RequestAmountParams, - RequestCountParams, - RequestedAmountMessageParams, - ResolutionConstraintsParams, - RoomNameReservedErrorParams, - RoomRenamedToParams, - SetTheDistanceParams, - SetTheRequestParams, - SettleExpensifyCardParams, + WaitingOnBankAccountParams, SettledAfterAddedBankAccountParams, - SizeExceededParams, - SplitAmountParams, - StepCounterParams, - TagSelectionParams, + PaidElsewhereWithAmountParams, + PaidWithExpensifyWithAmountParams, ThreadRequestReportNameParams, ThreadSentMoneyReportNameParams, - ToValidateLoginParams, + SizeExceededParams, + ResolutionConstraintsParams, + NotAllowedExtensionParams, + EnterMagicCodeParams, TransferParams, + InstantSummaryParams, + NotYouParams, + DateShouldBeBeforeParams, + DateShouldBeAfterParams, + IncorrectZipFormatParams, + WeSentYouMagicSignInLinkParams, + ToValidateLoginParams, + NoLongerHaveAccessParams, + OurEmailProviderParams, + ConfirmThatParams, UntilTimeParams, - UpdatedTheDistanceParams, - UpdatedTheRequestParams, + StepCounterParams, UserIsAlreadyMemberParams, - WaitingOnBankAccountParams, - WalletProgramParams, - WeSentYouMagicSignInLinkParams, - WelcomeEnterMagicCodeParams, + GoToRoomParams, WelcomeNoteParams, - WelcomeToRoomParams, - ZipCodeExampleFormatParams, + RoomNameReservedErrorParams, + RenamedRoomActionParams, + RoomRenamedToParams, + OOOEventSummaryFullDayParams, + OOOEventSummaryPartialDayParams, + ParentNavigationSummaryParams, + ManagerApprovedParams, + SetTheRequestParams, + SetTheDistanceParams, + UpdatedTheRequestParams, + UpdatedTheDistanceParams, + RemovedTheRequestParams, + FormattedMaxLengthParams, + RequestedAmountMessageParams, + TagSelectionParams, + EnglishTranslation, + WalletProgramParams, } from './types'; /* eslint-disable max-len */ From a691d7eb3b7f121964201387bccce67f3ce594d4 Mon Sep 17 00:00:00 2001 From: Kacper Falat Date: Thu, 28 Sep 2023 11:51:21 +0200 Subject: [PATCH 108/254] Migrated Welcome.js to TypeScript. Signed-off-by: Kacper Falat --- src/libs/ReportUtils.js | 2 +- src/libs/actions/Policy.js | 2 +- src/libs/actions/{Welcome.js => Welcome.ts} | 64 ++++++++++++--------- 3 files changed, 39 insertions(+), 29 deletions(-) rename src/libs/actions/{Welcome.js => Welcome.ts} (73%) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index eb512b7927a9..b279c8cfc6f7 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -363,7 +363,7 @@ function isUserCreatedPolicyRoom(report) { /** * Whether the provided report is a Policy Expense chat. * @param {Object} report - * @param {String} report.chatType + * @param {String} [report.chatType] * @returns {Boolean} */ function isPolicyExpenseChat(report) { diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 89324dd35485..4696ab2f0649 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -162,7 +162,7 @@ function deleteWorkspace(policyID, reports, policyName) { /** * Is the user an admin of a free policy (aka workspace)? * - * @param {Array} policies + * @param {Record} policies * @returns {Boolean} */ function isAdminOfFreePolicy(policies) { diff --git a/src/libs/actions/Welcome.js b/src/libs/actions/Welcome.ts similarity index 73% rename from src/libs/actions/Welcome.js rename to src/libs/actions/Welcome.ts index 8e1832edb9a7..0c6f69451d1e 100644 --- a/src/libs/actions/Welcome.js +++ b/src/libs/actions/Welcome.ts @@ -1,6 +1,4 @@ import Onyx from 'react-native-onyx'; -import _ from 'underscore'; -import lodashGet from 'lodash/get'; import Navigation from '../Navigation/Navigation'; import * as ReportUtils from '../ReportUtils'; import ROUTES from '../../ROUTES'; @@ -8,15 +6,28 @@ import * as Policy from './Policy'; import ONYXKEYS from '../../ONYXKEYS'; import SCREENS from '../../SCREENS'; import CONST from '../../CONST'; +import Report from '../../types/onyx/Report'; +import OnyxPolicy from "../../types/onyx/Policy"; -let resolveIsReadyPromise; +let resolveIsReadyPromise: (value?: (PromiseLike)) => void; let isReadyPromise = new Promise((resolve) => { resolveIsReadyPromise = resolve; }); -let isFirstTimeNewExpensifyUser; +let isFirstTimeNewExpensifyUser: boolean | undefined; let isLoadingReportData = true; -let currentUserAccountID; +let currentUserAccountID: number | undefined; + +type Route = { + name: string; + params?: {path: string, exitTo?: string, openOnAdminRoom?: boolean}; +}; + +type ShowParams = { + routes: Route[]; + showCreateMenu?: () => void; + showPopoverMenu?: () => boolean; +}; /** * Check that a few requests have completed so that the welcome action can proceed: @@ -26,11 +37,13 @@ let currentUserAccountID; * - Whether we have loaded all reports the server knows about */ function checkOnReady() { - if (!_.isBoolean(isFirstTimeNewExpensifyUser) || isLoadingReportData) { + if (typeof isFirstTimeNewExpensifyUser !== "boolean" || isLoadingReportData) { return; } - resolveIsReadyPromise(); + if(resolveIsReadyPromise) { + resolveIsReadyPromise(); + } } Onyx.connect({ @@ -39,7 +52,7 @@ Onyx.connect({ callback: (val) => { // If isFirstTimeNewExpensifyUser was true do not update it to false. We update it to false inside the Welcome.show logic // More context here https://github.com/Expensify/App/pull/16962#discussion_r1167351359 - if (!isFirstTimeNewExpensifyUser) { + if (val !== null) { isFirstTimeNewExpensifyUser = val; } checkOnReady(); @@ -50,12 +63,15 @@ Onyx.connect({ key: ONYXKEYS.IS_LOADING_REPORT_DATA, initWithStoredValues: false, callback: (val) => { - isLoadingReportData = val; + if(val) + { + isLoadingReportData = val; + } checkOnReady(); }, }); -const allReports = {}; +const allReports: Record = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, initWithStoredValues: false, @@ -68,7 +84,7 @@ Onyx.connect({ }, }); -const allPolicies = {}; +const allPolicies: Record = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY, callback: (val, key) => { @@ -98,13 +114,8 @@ Onyx.connect({ /** * Shows a welcome action on first login - * - * @param {Object} params - * @param {Object} params.routes - * @param {Function} [params.showCreateMenu] - * @param {Function} [params.showPopoverMenu] */ -function show({routes, showCreateMenu = () => {}, showPopoverMenu = () => {}}) { +function show({routes, showCreateMenu = () => {}, showPopoverMenu = () => false}: ShowParams) { isReadyPromise.then(() => { if (!isFirstTimeNewExpensifyUser) { return; @@ -112,20 +123,19 @@ function show({routes, showCreateMenu = () => {}, showPopoverMenu = () => {}}) { // If we are rendering the SidebarScreen at the same time as a workspace route that means we've already created a workspace via workspace/new and should not open the global // create menu right now. We should also stay on the workspace page if that is our destination. - const topRoute = _.last(routes); - const isWorkspaceRoute = topRoute.name === 'Settings' && topRoute.params.path.includes('workspace'); - const transitionRoute = _.find(routes, (route) => route.name === SCREENS.TRANSITION_BETWEEN_APPS); - const exitingToWorkspaceRoute = lodashGet(transitionRoute, 'params.exitTo', '') === 'workspace/new'; - const openOnAdminRoom = lodashGet(topRoute, 'params.openOnAdminRoom', false); - const isDisplayingWorkspaceRoute = isWorkspaceRoute || exitingToWorkspaceRoute; + const topRoute = routes[routes.length - 1] + const isWorkspaceRoute = topRoute.name === 'Settings' && topRoute.params?.path.includes('workspace'); + const transitionRoute = routes.find((route) => route.name === SCREENS.TRANSITION_BETWEEN_APPS); + const exitingToWorkspaceRoute = transitionRoute?.params?.exitTo === 'workspace/new'; + const openOnAdminRoom = topRoute.params?.openOnAdminRoom ?? false; + const isDisplayingWorkspaceRoute = isWorkspaceRoute ?? exitingToWorkspaceRoute; // If we already opened the workspace settings or want the admin room to stay open, do not // navigate away to the workspace chat report const shouldNavigateToWorkspaceChat = !isDisplayingWorkspaceRoute && !openOnAdminRoom; - const workspaceChatReport = _.find( - allReports, - (report) => ReportUtils.isPolicyExpenseChat(report) && report.ownerAccountID === currentUserAccountID && report.statusNum !== CONST.REPORT.STATUS.CLOSED, + const workspaceChatReport = Object(allReports).values().find((report: Report) => + ReportUtils.isPolicyExpenseChat(report) && report.ownerAccountID === currentUserAccountID && report.statusNum !== CONST.REPORT.STATUS.CLOSED ); if (workspaceChatReport || openOnAdminRoom) { @@ -138,7 +148,7 @@ function show({routes, showCreateMenu = () => {}, showPopoverMenu = () => {}}) { // If showPopoverMenu exists and returns true then it opened the Popover Menu successfully, and we can update isFirstTimeNewExpensifyUser // so the Welcome logic doesn't run again - if (showPopoverMenu()) { + if (showPopoverMenu && showPopoverMenu()) { isFirstTimeNewExpensifyUser = false; } From 51272573c6b7619615dc61b6d0598e2d4c4fdf5d Mon Sep 17 00:00:00 2001 From: Kacper Falat Date: Fri, 29 Sep 2023 09:27:02 +0200 Subject: [PATCH 109/254] Requested changes implemented. Signed-off-by: Kacper Falat --- src/libs/actions/Welcome.ts | 39 ++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index 0c6f69451d1e..7a8d64b0e37e 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -1,4 +1,4 @@ -import Onyx from 'react-native-onyx'; +import Onyx, {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Navigation from '../Navigation/Navigation'; import * as ReportUtils from '../ReportUtils'; import ROUTES from '../../ROUTES'; @@ -9,7 +9,7 @@ import CONST from '../../CONST'; import Report from '../../types/onyx/Report'; import OnyxPolicy from "../../types/onyx/Policy"; -let resolveIsReadyPromise: (value?: (PromiseLike)) => void; +let resolveIsReadyPromise: (value?: (PromiseLike)) => void | undefined; let isReadyPromise = new Promise((resolve) => { resolveIsReadyPromise = resolve; }); @@ -37,23 +37,21 @@ type ShowParams = { * - Whether we have loaded all reports the server knows about */ function checkOnReady() { - if (typeof isFirstTimeNewExpensifyUser !== "boolean" || isLoadingReportData) { + if (isFirstTimeNewExpensifyUser === undefined || isLoadingReportData) { return; } - if(resolveIsReadyPromise) { - resolveIsReadyPromise(); - } + resolveIsReadyPromise?.(); } Onyx.connect({ key: ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER, initWithStoredValues: false, - callback: (val) => { + callback: (val: OnyxEntry) => { // If isFirstTimeNewExpensifyUser was true do not update it to false. We update it to false inside the Welcome.show logic // More context here https://github.com/Expensify/App/pull/16962#discussion_r1167351359 - if (val !== null) { - isFirstTimeNewExpensifyUser = val; + if (!isFirstTimeNewExpensifyUser) { + isFirstTimeNewExpensifyUser = val ?? undefined; } checkOnReady(); }, @@ -71,7 +69,7 @@ Onyx.connect({ }, }); -const allReports: Record = {}; +const allReports: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, initWithStoredValues: false, @@ -84,7 +82,7 @@ Onyx.connect({ }, }); -const allPolicies: Record = {}; +const allPolicies: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY, callback: (val, key) => { @@ -134,21 +132,26 @@ function show({routes, showCreateMenu = () => {}, showPopoverMenu = () => false} // navigate away to the workspace chat report const shouldNavigateToWorkspaceChat = !isDisplayingWorkspaceRoute && !openOnAdminRoom; - const workspaceChatReport = Object(allReports).values().find((report: Report) => - ReportUtils.isPolicyExpenseChat(report) && report.ownerAccountID === currentUserAccountID && report.statusNum !== CONST.REPORT.STATUS.CLOSED - ); + const workspaceChatReport = Object.values(allReports ?? {}).find((report) => { + if (report) { + return ReportUtils.isPolicyExpenseChat(report) && report.ownerAccountID === currentUserAccountID && report.statusNum !== CONST.REPORT.STATUS.CLOSED + } + return false; + }); - if (workspaceChatReport || openOnAdminRoom) { + if (workspaceChatReport ?? openOnAdminRoom) { // This key is only updated when we call ReconnectApp, setting it to false now allows the user to navigate normally instead of always redirecting to the workspace chat Onyx.set(ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER, false); } if (shouldNavigateToWorkspaceChat && workspaceChatReport) { - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(workspaceChatReport.reportID)); + if (workspaceChatReport.reportID != null) { + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(workspaceChatReport.reportID)); + } // If showPopoverMenu exists and returns true then it opened the Popover Menu successfully, and we can update isFirstTimeNewExpensifyUser // so the Welcome logic doesn't run again - if (showPopoverMenu && showPopoverMenu()) { + if (showPopoverMenu?.()) { isFirstTimeNewExpensifyUser = false; } @@ -157,7 +160,7 @@ function show({routes, showCreateMenu = () => {}, showPopoverMenu = () => false} // If user is not already an admin of a free policy and we are not navigating them to their workspace or creating a new workspace via workspace/new then // we will show the create menu. - if (!Policy.isAdminOfFreePolicy(allPolicies) && !isDisplayingWorkspaceRoute) { + if (allPolicies && !Policy.isAdminOfFreePolicy(allPolicies) && !isDisplayingWorkspaceRoute) { showCreateMenu(); } From 220991b5d2f67cb96dcc96cff435b0cbe35a065a Mon Sep 17 00:00:00 2001 From: Kacper Falat Date: Fri, 29 Sep 2023 09:27:53 +0200 Subject: [PATCH 110/254] Requested changes implemented + prettier. Signed-off-by: Kacper Falat --- src/libs/actions/Welcome.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index 7a8d64b0e37e..2c211ef6c117 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -7,9 +7,9 @@ import ONYXKEYS from '../../ONYXKEYS'; import SCREENS from '../../SCREENS'; import CONST from '../../CONST'; import Report from '../../types/onyx/Report'; -import OnyxPolicy from "../../types/onyx/Policy"; +import OnyxPolicy from '../../types/onyx/Policy'; -let resolveIsReadyPromise: (value?: (PromiseLike)) => void | undefined; +let resolveIsReadyPromise: (value?: PromiseLike) => void | undefined; let isReadyPromise = new Promise((resolve) => { resolveIsReadyPromise = resolve; }); @@ -20,7 +20,7 @@ let currentUserAccountID: number | undefined; type Route = { name: string; - params?: {path: string, exitTo?: string, openOnAdminRoom?: boolean}; + params?: {path: string; exitTo?: string; openOnAdminRoom?: boolean}; }; type ShowParams = { @@ -61,8 +61,7 @@ Onyx.connect({ key: ONYXKEYS.IS_LOADING_REPORT_DATA, initWithStoredValues: false, callback: (val) => { - if(val) - { + if (val) { isLoadingReportData = val; } checkOnReady(); @@ -121,7 +120,7 @@ function show({routes, showCreateMenu = () => {}, showPopoverMenu = () => false} // If we are rendering the SidebarScreen at the same time as a workspace route that means we've already created a workspace via workspace/new and should not open the global // create menu right now. We should also stay on the workspace page if that is our destination. - const topRoute = routes[routes.length - 1] + const topRoute = routes[routes.length - 1]; const isWorkspaceRoute = topRoute.name === 'Settings' && topRoute.params?.path.includes('workspace'); const transitionRoute = routes.find((route) => route.name === SCREENS.TRANSITION_BETWEEN_APPS); const exitingToWorkspaceRoute = transitionRoute?.params?.exitTo === 'workspace/new'; @@ -134,7 +133,7 @@ function show({routes, showCreateMenu = () => {}, showPopoverMenu = () => false} const workspaceChatReport = Object.values(allReports ?? {}).find((report) => { if (report) { - return ReportUtils.isPolicyExpenseChat(report) && report.ownerAccountID === currentUserAccountID && report.statusNum !== CONST.REPORT.STATUS.CLOSED + return ReportUtils.isPolicyExpenseChat(report) && report.ownerAccountID === currentUserAccountID && report.statusNum !== CONST.REPORT.STATUS.CLOSED; } return false; }); From c5a2f2b6e56e8c8a89e4874e1b67b480135f7483 Mon Sep 17 00:00:00 2001 From: Kacper Falat Date: Fri, 29 Sep 2023 10:50:20 +0200 Subject: [PATCH 111/254] Solved comments. Signed-off-by: Kacper Falat --- src/libs/actions/Welcome.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index 2c211ef6c117..2fc5a0553c5e 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -1,4 +1,4 @@ -import Onyx, {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import Onyx, {OnyxCollection} from 'react-native-onyx'; import Navigation from '../Navigation/Navigation'; import * as ReportUtils from '../ReportUtils'; import ROUTES from '../../ROUTES'; @@ -9,8 +9,8 @@ import CONST from '../../CONST'; import Report from '../../types/onyx/Report'; import OnyxPolicy from '../../types/onyx/Policy'; -let resolveIsReadyPromise: (value?: PromiseLike) => void | undefined; -let isReadyPromise = new Promise((resolve) => { +let resolveIsReadyPromise: (value?: Promise) => void | undefined; +let isReadyPromise = new Promise((resolve) => { resolveIsReadyPromise = resolve; }); @@ -47,7 +47,7 @@ function checkOnReady() { Onyx.connect({ key: ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER, initWithStoredValues: false, - callback: (val: OnyxEntry) => { + callback: (val) => { // If isFirstTimeNewExpensifyUser was true do not update it to false. We update it to false inside the Welcome.show logic // More context here https://github.com/Expensify/App/pull/16962#discussion_r1167351359 if (!isFirstTimeNewExpensifyUser) { @@ -176,7 +176,7 @@ function resetReadyCheck() { isLoadingReportData = true; } -function serverDataIsReadyPromise() { +function serverDataIsReadyPromise(): Promise { return isReadyPromise; } From 2258f1f5af167705841070e72b11401ebf3ff2bf Mon Sep 17 00:00:00 2001 From: Kacper Falat Date: Fri, 29 Sep 2023 11:40:13 +0200 Subject: [PATCH 112/254] Renamed val -> value. Signed-off-by: Kacper Falat --- src/libs/actions/Welcome.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index 2fc5a0553c5e..e4ab9caa39bc 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -47,11 +47,11 @@ function checkOnReady() { Onyx.connect({ key: ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER, initWithStoredValues: false, - callback: (val) => { + callback: (value) => { // If isFirstTimeNewExpensifyUser was true do not update it to false. We update it to false inside the Welcome.show logic // More context here https://github.com/Expensify/App/pull/16962#discussion_r1167351359 if (!isFirstTimeNewExpensifyUser) { - isFirstTimeNewExpensifyUser = val ?? undefined; + isFirstTimeNewExpensifyUser = value ?? undefined; } checkOnReady(); }, @@ -60,9 +60,9 @@ Onyx.connect({ Onyx.connect({ key: ONYXKEYS.IS_LOADING_REPORT_DATA, initWithStoredValues: false, - callback: (val) => { - if (val) { - isLoadingReportData = val; + callback: (value) => { + if (value) { + isLoadingReportData = value; } checkOnReady(); }, From bdd184110b6ae91f1937c8e24c7d8111f56d8d40 Mon Sep 17 00:00:00 2001 From: Kacper Falat Date: Tue, 3 Oct 2023 09:56:34 +0200 Subject: [PATCH 113/254] Resolved comment. Signed-off-by: Kacper Falat --- src/libs/actions/Welcome.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index e4ab9caa39bc..b0dc1738c193 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -144,7 +144,7 @@ function show({routes, showCreateMenu = () => {}, showPopoverMenu = () => false} } if (shouldNavigateToWorkspaceChat && workspaceChatReport) { - if (workspaceChatReport.reportID != null) { + if (workspaceChatReport.reportID !== null) { Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(workspaceChatReport.reportID)); } From f97333182f7c011d21ef7044ad1aa8d81c173e1a Mon Sep 17 00:00:00 2001 From: Kacper Falat Date: Thu, 5 Oct 2023 10:05:11 +0200 Subject: [PATCH 114/254] Graceful handling of last array element. Signed-off-by: Kacper Falat --- src/libs/actions/Welcome.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index b0dc1738c193..cbfac7c917da 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -120,11 +120,11 @@ function show({routes, showCreateMenu = () => {}, showPopoverMenu = () => false} // If we are rendering the SidebarScreen at the same time as a workspace route that means we've already created a workspace via workspace/new and should not open the global // create menu right now. We should also stay on the workspace page if that is our destination. - const topRoute = routes[routes.length - 1]; - const isWorkspaceRoute = topRoute.name === 'Settings' && topRoute.params?.path.includes('workspace'); + const topRoute = routes.length > 0 ? routes[routes.length - 1] : undefined; + const isWorkspaceRoute = topRoute !== undefined && topRoute.name === 'Settings' && topRoute.params?.path.includes('workspace'); const transitionRoute = routes.find((route) => route.name === SCREENS.TRANSITION_BETWEEN_APPS); const exitingToWorkspaceRoute = transitionRoute?.params?.exitTo === 'workspace/new'; - const openOnAdminRoom = topRoute.params?.openOnAdminRoom ?? false; + const openOnAdminRoom = topRoute === undefined ? undefined : topRoute.params?.openOnAdminRoom ?? false; const isDisplayingWorkspaceRoute = isWorkspaceRoute ?? exitingToWorkspaceRoute; // If we already opened the workspace settings or want the admin room to stay open, do not From 69b495a9daf2f3bcc4aba586de2c96db1f6954ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Fa=C5=82at?= Date: Mon, 9 Oct 2023 09:11:37 +0200 Subject: [PATCH 115/254] Requested change on Welcome.ts Co-authored-by: Hayata Suenaga Signed-off-by: Kacper Falat --- src/libs/actions/Welcome.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index cbfac7c917da..5494f5437a76 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -124,7 +124,7 @@ function show({routes, showCreateMenu = () => {}, showPopoverMenu = () => false} const isWorkspaceRoute = topRoute !== undefined && topRoute.name === 'Settings' && topRoute.params?.path.includes('workspace'); const transitionRoute = routes.find((route) => route.name === SCREENS.TRANSITION_BETWEEN_APPS); const exitingToWorkspaceRoute = transitionRoute?.params?.exitTo === 'workspace/new'; - const openOnAdminRoom = topRoute === undefined ? undefined : topRoute.params?.openOnAdminRoom ?? false; + const openOnAdminRoom = topRoute?.params?.openOnAdminRoom ?? false; const isDisplayingWorkspaceRoute = isWorkspaceRoute ?? exitingToWorkspaceRoute; // If we already opened the workspace settings or want the admin room to stay open, do not From e651ba360ce9c32793cbf11bb83a6276aad08abf Mon Sep 17 00:00:00 2001 From: Kacper Falat Date: Thu, 19 Oct 2023 10:47:38 +0200 Subject: [PATCH 116/254] Rebased and resigned old commit tree. --- src/libs/actions/Policy.js | 2 +- src/libs/actions/Welcome.ts | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 4696ab2f0649..02dd45ce7969 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -162,7 +162,7 @@ function deleteWorkspace(policyID, reports, policyName) { /** * Is the user an admin of a free policy (aka workspace)? * - * @param {Record} policies + * @param {Record} [policies] * @returns {Boolean} */ function isAdminOfFreePolicy(policies) { diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index 5494f5437a76..29e0088ed689 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -50,9 +50,9 @@ Onyx.connect({ callback: (value) => { // If isFirstTimeNewExpensifyUser was true do not update it to false. We update it to false inside the Welcome.show logic // More context here https://github.com/Expensify/App/pull/16962#discussion_r1167351359 - if (!isFirstTimeNewExpensifyUser) { - isFirstTimeNewExpensifyUser = value ?? undefined; - } + + isFirstTimeNewExpensifyUser = value ?? undefined; + checkOnReady(); }, }); @@ -61,9 +61,7 @@ Onyx.connect({ key: ONYXKEYS.IS_LOADING_REPORT_DATA, initWithStoredValues: false, callback: (value) => { - if (value) { - isLoadingReportData = value; - } + isLoadingReportData = value ?? false; checkOnReady(); }, }); @@ -159,7 +157,7 @@ function show({routes, showCreateMenu = () => {}, showPopoverMenu = () => false} // If user is not already an admin of a free policy and we are not navigating them to their workspace or creating a new workspace via workspace/new then // we will show the create menu. - if (allPolicies && !Policy.isAdminOfFreePolicy(allPolicies) && !isDisplayingWorkspaceRoute) { + if (!Policy.isAdminOfFreePolicy(allPolicies ?? undefined) && !isDisplayingWorkspaceRoute) { showCreateMenu(); } From 304931aed8e83b2b4ce69176c96eaab5f6364b9a Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Thu, 19 Oct 2023 14:51:46 +0200 Subject: [PATCH 117/254] [TS migration] Migrate 'RadioButtons.js' component --- .../{RadioButtons.js => RadioButtons.tsx} | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) rename src/components/{RadioButtons.js => RadioButtons.tsx} (61%) diff --git a/src/components/RadioButtons.js b/src/components/RadioButtons.tsx similarity index 61% rename from src/components/RadioButtons.js rename to src/components/RadioButtons.tsx index 455f4dad1674..f570bf6fad3e 100644 --- a/src/components/RadioButtons.js +++ b/src/components/RadioButtons.tsx @@ -1,33 +1,31 @@ import React, {useState} from 'react'; import {View} from 'react-native'; -import PropTypes from 'prop-types'; -import _ from 'underscore'; import RadioButtonWithLabel from './RadioButtonWithLabel'; import styles from '../styles/styles'; -const propTypes = { +type Choice = { + label: string; + value: string; +}; + +type RadioButtonsProps = { /** List of choices to display via radio buttons */ - items: PropTypes.arrayOf( - PropTypes.shape({ - label: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, - }), - ).isRequired, + items: Choice[]; /** Callback to fire when selecting a radio button */ - onPress: PropTypes.func.isRequired, + onPress: (value: string) => void; }; -function RadioButtons(props) { +function RadioButtons(props: RadioButtonsProps) { const [checkedValue, setCheckedValue] = useState(''); return ( - {_.map(props.items, (item, index) => ( + {props.items.map((item) => ( { setCheckedValue(item.value); return props.onPress(item.value); @@ -39,7 +37,6 @@ function RadioButtons(props) { ); } -RadioButtons.propTypes = propTypes; RadioButtons.displayName = 'RadioButtons'; export default RadioButtons; From 84e6c26ba8f86a2b5952e13629d5a4c6114ee07d Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Thu, 19 Oct 2023 16:11:53 +0200 Subject: [PATCH 118/254] Destructure props object --- src/components/RadioButtons.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/RadioButtons.tsx b/src/components/RadioButtons.tsx index f570bf6fad3e..cf9830d4b967 100644 --- a/src/components/RadioButtons.tsx +++ b/src/components/RadioButtons.tsx @@ -16,19 +16,19 @@ type RadioButtonsProps = { onPress: (value: string) => void; }; -function RadioButtons(props: RadioButtonsProps) { +function RadioButtons({items, onPress}: RadioButtonsProps) { const [checkedValue, setCheckedValue] = useState(''); return ( - {props.items.map((item) => ( + {items.map((item) => ( { setCheckedValue(item.value); - return props.onPress(item.value); + return onPress(item.value); }} label={item.label} /> From 5ad7f00172de2160a2934a6f478da21fac25bc0c Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Thu, 19 Oct 2023 16:48:32 +0200 Subject: [PATCH 119/254] Remove unused KeyboardDismissingFlatList component --- .../KeyboardDismissingFlatList/index.js | 51 ------------------- .../index.native.js | 16 ------ 2 files changed, 67 deletions(-) delete mode 100644 src/components/KeyboardDismissingFlatList/index.js delete mode 100644 src/components/KeyboardDismissingFlatList/index.native.js diff --git a/src/components/KeyboardDismissingFlatList/index.js b/src/components/KeyboardDismissingFlatList/index.js deleted file mode 100644 index 0ca8504d96ab..000000000000 --- a/src/components/KeyboardDismissingFlatList/index.js +++ /dev/null @@ -1,51 +0,0 @@ -import React, {useRef, useEffect, useCallback} from 'react'; -import {FlatList, Keyboard} from 'react-native'; -import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; - -function KeyboardDismissingFlatList(props) { - const isScreenTouched = useRef(false); - - useEffect(() => { - if (!DeviceCapabilities.canUseTouchScreen()) { - return; - } - - const touchStart = () => { - isScreenTouched.current = true; - }; - - const touchEnd = () => { - isScreenTouched.current = false; - }; - - // We're setting `isScreenTouched` in this listener only for web platforms with touchscreen (mWeb) where - // we want to dismiss the keyboard only when the list is scrolled by the user and not when it's scrolled programmatically. - document.addEventListener('touchstart', touchStart); - document.addEventListener('touchend', touchEnd); - - return () => { - document.removeEventListener('touchstart', touchStart); - document.removeEventListener('touchend', touchEnd); - }; - }, []); - - const onScroll = useCallback(() => { - // Only dismiss the keyboard whenever the user scrolls the screen - if (!isScreenTouched.current) { - return; - } - Keyboard.dismiss(); - }, []); - - return ( - - ); -} - -KeyboardDismissingFlatList.displayName = 'KeyboardDismissingFlatList'; - -export default KeyboardDismissingFlatList; diff --git a/src/components/KeyboardDismissingFlatList/index.native.js b/src/components/KeyboardDismissingFlatList/index.native.js deleted file mode 100644 index 97297528ac77..000000000000 --- a/src/components/KeyboardDismissingFlatList/index.native.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import {FlatList, Keyboard} from 'react-native'; - -function KeyboardDismissingFlatList(props) { - return ( - Keyboard.dismiss()} - /> - ); -} - -KeyboardDismissingFlatList.displayName = 'KeyboardDismissingFlatList'; - -export default KeyboardDismissingFlatList; From b4c0c1de45cf18ea21893673a3dc6e38ca0066c6 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 19 Oct 2023 08:11:50 -1000 Subject: [PATCH 120/254] Remove hidden reports like threads from the global unread count --- src/libs/UnreadIndicatorUpdater/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/UnreadIndicatorUpdater/index.js b/src/libs/UnreadIndicatorUpdater/index.js index 09fa82612314..1ec7bd3a159c 100644 --- a/src/libs/UnreadIndicatorUpdater/index.js +++ b/src/libs/UnreadIndicatorUpdater/index.js @@ -3,12 +3,13 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '../../ONYXKEYS'; import updateUnread from './updateUnread/index'; import * as ReportUtils from '../ReportUtils'; +import CONST from '../../CONST'; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, waitForCollectionCallback: true, callback: (reportsFromOnyx) => { - const unreadReports = _.filter(reportsFromOnyx, ReportUtils.isUnread); + const unreadReports = _.filter(reportsFromOnyx, (report) => ReportUtils.isUnread(report) || report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); updateUnread(_.size(unreadReports)); }, }); From acd395eca58924bcacfc144848ef1900b60dabda Mon Sep 17 00:00:00 2001 From: Hardik Choudhary Date: Fri, 20 Oct 2023 00:47:39 +0530 Subject: [PATCH 121/254] Suggested changes applied --- src/components/Composer/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Composer/index.js b/src/components/Composer/index.js index 97a592a546ab..f845134acc51 100755 --- a/src/components/Composer/index.js +++ b/src/components/Composer/index.js @@ -445,6 +445,7 @@ function Composer({ StyleSheet.flatten([style, {outline: 'none'}]), StyleUtils.getComposeTextAreaPadding(numberOfLines, isComposerFullSize), + Browser.isMobileSafari() || Browser.isSafari() ? styles.rtlTextRenderForSafari : {}, ], [style, maxLines, numberOfLines, isComposerFullSize], ); @@ -457,7 +458,7 @@ function Composer({ placeholderTextColor={themeColors.placeholderText} ref={(el) => (textInput.current = el)} selection={selection} - style={(Browser.isMobileSafari() || Browser.isSafari()) ? [inputStyleMemo, styles.rtlTextRenderForSafari] : [inputStyleMemo]} + style={inputStyleMemo} value={value} forwardedRef={forwardedRef} defaultValue={defaultValue} From 71cb32ba907148e412bd9841d2f111cf69908091 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 19 Oct 2023 12:21:41 -0700 Subject: [PATCH 122/254] Extra merge changes that got left out --- src/components/MessagesRow.js | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/components/MessagesRow.js b/src/components/MessagesRow.js index 37366ce5a442..6f4aa13a36f8 100644 --- a/src/components/MessagesRow.js +++ b/src/components/MessagesRow.js @@ -25,15 +25,19 @@ const propTypes = { /** Additional style object for the container */ containerStyles: stylePropTypes, + + /** Whether we can dismiss the error message */ + canDismissError: PropTypes.bool, }; const defaultProps = { messages: {}, onClose: () => {}, containerStyles: [], + canDismissError: true, }; -function MessagesRow({messages, type, onClose, containerStyles}) { +function MessagesRow({messages, type, onClose, containerStyles, canDismissError}) { const {translate} = useLocalize(); if (_.isEmpty(messages)) { return null; @@ -46,16 +50,18 @@ function MessagesRow({messages, type, onClose, containerStyles}) { messages={messages} type={type} /> - - - - - + {canDismissError && ( + + + + + + )} ); } From 8084b257448ef16347a88503ac8987f8735d0cb3 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 19 Oct 2023 10:36:37 -1000 Subject: [PATCH 123/254] Switch to && --- src/libs/UnreadIndicatorUpdater/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/UnreadIndicatorUpdater/index.js b/src/libs/UnreadIndicatorUpdater/index.js index 1ec7bd3a159c..9acf1a17bbc6 100644 --- a/src/libs/UnreadIndicatorUpdater/index.js +++ b/src/libs/UnreadIndicatorUpdater/index.js @@ -9,7 +9,7 @@ Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, waitForCollectionCallback: true, callback: (reportsFromOnyx) => { - const unreadReports = _.filter(reportsFromOnyx, (report) => ReportUtils.isUnread(report) || report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); + const unreadReports = _.filter(reportsFromOnyx, (report) => ReportUtils.isUnread(report) && report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); updateUnread(_.size(unreadReports)); }, }); From 79fdee91602d5c5005125863f5a8e3ebaf24ac14 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Thu, 19 Oct 2023 17:10:36 -0400 Subject: [PATCH 124/254] Add checkout to setup for OSBotify --- .github/actions/composite/setupGitForOSBotifyApp/action.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/actions/composite/setupGitForOSBotifyApp/action.yml b/.github/actions/composite/setupGitForOSBotifyApp/action.yml index bd5b5139bc6b..d90adf609cd7 100644 --- a/.github/actions/composite/setupGitForOSBotifyApp/action.yml +++ b/.github/actions/composite/setupGitForOSBotifyApp/action.yml @@ -24,6 +24,12 @@ outputs: runs: using: composite steps: + - name: Checkout + uses: actions/checkout@v3 + with: + sparse-checkout: | + .github + - name: Decrypt OSBotify GPG key run: cd .github/workflows && gpg --quiet --batch --yes --decrypt --passphrase=${{ inputs.GPG_PASSPHRASE }} --output OSBotify-private-key.asc OSBotify-private-key.asc.gpg shell: bash From 411b053119fd0f2f215d1bdfa60395125c603606 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Thu, 19 Oct 2023 17:20:52 -0400 Subject: [PATCH 125/254] add step to check if gpg key is present --- .../actions/composite/setupGitForOSBotifyApp/action.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/actions/composite/setupGitForOSBotifyApp/action.yml b/.github/actions/composite/setupGitForOSBotifyApp/action.yml index d90adf609cd7..8d48bf011696 100644 --- a/.github/actions/composite/setupGitForOSBotifyApp/action.yml +++ b/.github/actions/composite/setupGitForOSBotifyApp/action.yml @@ -24,6 +24,14 @@ outputs: runs: using: composite steps: + - name: Check if gpg encrypted private key is present + shell: bash + run: | + if [ -f .github/workflows/OSBotify-private-key.asc.gpg ]; then + echo "::set-output name=key_exists::true" + else + echo "::set-output name=key_exists::false" + fi - name: Checkout uses: actions/checkout@v3 with: From f11a4630253a669c987beabc4b16c7a59951df4b Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Thu, 19 Oct 2023 17:25:06 -0400 Subject: [PATCH 126/254] add conditional to checkout --- .github/actions/composite/setupGitForOSBotifyApp/action.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/actions/composite/setupGitForOSBotifyApp/action.yml b/.github/actions/composite/setupGitForOSBotifyApp/action.yml index 8d48bf011696..dea091d9c1bb 100644 --- a/.github/actions/composite/setupGitForOSBotifyApp/action.yml +++ b/.github/actions/composite/setupGitForOSBotifyApp/action.yml @@ -25,15 +25,18 @@ runs: using: composite steps: - name: Check if gpg encrypted private key is present + id: key_check shell: bash run: | - if [ -f .github/workflows/OSBotify-private-key.asc.gpg ]; then + if [[ -f .github/workflows/OSBotify-private-key.asc.gpg ]]; then echo "::set-output name=key_exists::true" else echo "::set-output name=key_exists::false" fi + - name: Checkout uses: actions/checkout@v3 + if: steps.key_check.outputs.key_exists != 'true' with: sparse-checkout: | .github From 50456d6ab60dd241c6bcc100090ed2a34d97acb0 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Thu, 19 Oct 2023 17:25:19 -0400 Subject: [PATCH 127/254] simplify check --- .github/actions/composite/setupGitForOSBotifyApp/action.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/actions/composite/setupGitForOSBotifyApp/action.yml b/.github/actions/composite/setupGitForOSBotifyApp/action.yml index dea091d9c1bb..9961893e03e5 100644 --- a/.github/actions/composite/setupGitForOSBotifyApp/action.yml +++ b/.github/actions/composite/setupGitForOSBotifyApp/action.yml @@ -30,8 +30,6 @@ runs: run: | if [[ -f .github/workflows/OSBotify-private-key.asc.gpg ]]; then echo "::set-output name=key_exists::true" - else - echo "::set-output name=key_exists::false" fi - name: Checkout From db19896eff8e7589909a1b7b3cd9955c3cc77eb5 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Thu, 19 Oct 2023 18:57:10 -0400 Subject: [PATCH 128/254] remove extra parameters --- src/libs/actions/Report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 7d8bce044f94..cdc07df2ea13 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1354,7 +1354,7 @@ function updateNotificationPreference(reportID, previousValue, newValue, navigat value: {[parentReportActionID]: {childReportNotificationPreference: previousValue}}, }); } - API.write('UpdateReportNotificationPreference', {reportID, notificationPreference: newValue, parentReportID, parentReportActionID}, {optimisticData, failureData}); + API.write('UpdateReportNotificationPreference', {reportID, notificationPreference: newValue}, {optimisticData, failureData}); if (navigate) { Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(reportID)); } From 986529036ba06832f10ae7f3dc387872e6519ea5 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Thu, 19 Oct 2023 18:58:34 -0400 Subject: [PATCH 129/254] modify spanish translation --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 0ecff93d2758..287790fe24c7 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -417,7 +417,7 @@ export default { deleteConfirmation: ({action}: DeleteConfirmationParams) => `¿Estás seguro de que quieres eliminar este ${ReportActionsUtils.isMoneyRequestAction(action) ? 'pedido' : 'comentario'}`, onlyVisible: 'Visible sólo para', replyInThread: 'Responder en el hilo', - subscribeToThread: 'Suscríbete al hilo', + subscribeToThread: 'Suscribirse al hilo', unsubscribeFromThread: 'Darse de baja del hilo', flagAsOffensive: 'Marcar como ofensivo', }, From 233d703fc60a1da3b61dad69df71f331e53ee5d5 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 20 Oct 2023 08:09:24 +0200 Subject: [PATCH 130/254] Bring back test --- tests/unit/SidebarTest.js | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/unit/SidebarTest.js b/tests/unit/SidebarTest.js index 1ad10a3bfaf0..1b5daa323da5 100644 --- a/tests/unit/SidebarTest.js +++ b/tests/unit/SidebarTest.js @@ -81,5 +81,51 @@ describe('Sidebar', () => { }) ); }); + it('renders the policy deleted archive reason as the preview message of the chat', () => { + const report = { + ...LHNTestUtils.getFakeReport(['email1@test.com', 'email2@test.com'], 3, true), + policyName: 'Vikings Policy', + chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, + statusNum: CONST.REPORT.STATUS.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + }; + const action = { + ...LHNTestUtils.getFakeReportAction('email1@test.com', 3, true), + actionName: 'CLOSED', + originalMessage: { + policyName: 'Vikings Policy', + reason: 'policyDeleted', + }, + }; + + // Given the user is in all betas + const betas = [CONST.BETAS.DEFAULT_ROOMS, CONST.BETAS.POLICY_ROOMS]; + LHNTestUtils.getDefaultRenderedSidebarLinks('0'); + return ( + waitForBatchedUpdates() + // When Onyx is updated with the data and the sidebar re-renders + .then(() => + Onyx.multiSet({ + [ONYXKEYS.BETAS]: betas, + [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, + [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, + [`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`]: report, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`]: {[action.reportActionId]: action}, + }), + ) + .then(() => { + const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); + const displayNames = screen.queryAllByLabelText(hintText); + expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Report (archived)'); + + const hintMessagePreviewText = Localize.translateLocal('accessibilityHints.lastChatMessagePreview'); + const messagePreviewTexts = screen.queryAllByLabelText(hintMessagePreviewText); + expect(lodashGet(messagePreviewTexts, [0, 'props', 'children'])).toBe( + 'This workspace chat is no longer active because Vikings Policy is no longer an active workspace.', + ); + }) + ); + }); }); }); From f13776329f8aebd877d073d719305935b14e9cdc Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 20 Oct 2023 08:18:15 +0200 Subject: [PATCH 131/254] Add comments to onyx types --- src/types/onyx/PersonalDetails.ts | 1 + src/types/onyx/Report.ts | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/types/onyx/PersonalDetails.ts b/src/types/onyx/PersonalDetails.ts index 4b3e97ef22fc..0cd264802128 100644 --- a/src/types/onyx/PersonalDetails.ts +++ b/src/types/onyx/PersonalDetails.ts @@ -43,6 +43,7 @@ type PersonalDetails = { /** Timezone of the current user from their personal details */ timezone?: Timezone; + /** Status of the current user from their personal details */ status?: string; }; diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 4a84069c9afa..494661b2fb89 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -72,12 +72,25 @@ type Report = { participantAccountIDs?: number[]; total?: number; currency?: string; - iouReportAmount?: number; + + /** Whether the report is waiting on a bank account */ isWaitingOnBankAccount?: boolean; + + /** Whether the last message was deleted */ isLastMessageDeletedParentAction?: boolean; + + /** The ID of the IOU report */ iouReportID?: string; + + /** Total amount of money owed for IOU report */ + iouReportAmount?: number; + + /** Pending fields for the report */ pendingFields?: Record; + + /** The ID of the preexisting report (it is possible that we optimistically created a Report for which a report already exists) */ preexistingReportID?: string; + /** If the report contains nonreimbursable expenses, send the nonreimbursable total */ nonReimbursableTotal?: number; }; From c51b66730ac14dec9fe6a86cba3b89c7d699acd3 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Fri, 20 Oct 2023 11:54:02 +0530 Subject: [PATCH 132/254] dsable exhaustive-deps for scrollToIndex --- src/components/SelectionList/BaseSelectionList.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index a2f3661f0c59..585871b4cc83 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -175,7 +175,9 @@ function BaseSelectionList({ listRef.current.scrollToLocation({sectionIndex: adjustedSectionIndex, itemIndex, animated, viewOffset: variables.contentHeaderHeight}); }, - [flattenedSections.allOptions, sections], + + // eslint-disable-next-line react-hooks/exhaustive-deps + [flattenedSections.allOptions], ); /** From 60799d9b5c91a0ccefde0c747b1fde8649e64850 Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Fri, 20 Oct 2023 21:28:08 +0530 Subject: [PATCH 133/254] add const values --- src/CONST.ts | 4 ++++ src/components/PlaidLink/index.native.js | 3 ++- src/pages/ReimbursementAccount/ReimbursementAccountPage.js | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index e2f3fea08215..f50c435a9221 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -157,6 +157,10 @@ const CONST = { ERROR: { TOO_MANY_ATTEMPTS: 'Too many attempts', }, + EVENTS_NAME: { + OPEN: 'OPEN', + EXIT: 'EXIT', + }, }, ERROR: { MISSING_ROUTING_NUMBER: '402 Missing routingNumber', diff --git a/src/components/PlaidLink/index.native.js b/src/components/PlaidLink/index.native.js index 71ed551af235..53c513258ecf 100644 --- a/src/components/PlaidLink/index.native.js +++ b/src/components/PlaidLink/index.native.js @@ -2,6 +2,7 @@ import {useEffect} from 'react'; import {openLink, useDeepLinkRedirector, usePlaidEmitter, dismissLink} from 'react-native-plaid-link-sdk'; import Log from '../../libs/Log'; import {plaidLinkPropTypes, plaidLinkDefaultProps} from './plaidLinkPropTypes'; +import CONST from '../../CONST'; function PlaidLink(props) { useDeepLinkRedirector(); @@ -10,7 +11,7 @@ function PlaidLink(props) { props.onEvent(event.eventName, event.metadata); }); useEffect(() => { - props.onEvent('OPEN', {}); + props.onEvent(CONST.BANK_ACCOUNT.PLAID.EVENTS_NAME.OPEN, {}); openLink({ tokenConfig: { token: props.token, diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index e37058c2a21d..db2e062573ce 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -399,7 +399,7 @@ class ReimbursementAccountPage extends React.Component { } const isLoading = (this.props.isLoadingReportData || this.props.account.isLoading || this.props.reimbursementAccount.isLoading) && - (!this.props.plaidCurrentEvent || this.props.plaidCurrentEvent === 'EXIT'); + (!this.props.plaidCurrentEvent || this.props.plaidCurrentEvent === CONST.BANK_ACCOUNT.PLAID.EVENTS_NAME.EXIT); // Prevent the full-page blocking offline view from being displayed for these steps if the device goes offline. const shouldShowOfflineLoader = !( From 6aeeacf01c6468625aac7ab65be407e1e6bafcc9 Mon Sep 17 00:00:00 2001 From: mkhutornyi Date: Fri, 20 Oct 2023 19:45:32 +0100 Subject: [PATCH 134/254] fix clip icon briefly showing on attachment preview --- src/components/AttachmentModal.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 61b138747950..6793c00eb747 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -1,4 +1,4 @@ -import React, {useState, useCallback, useRef, useMemo} from 'react'; +import React, {useState, useCallback, useRef, useMemo, useEffect} from 'react'; import PropTypes from 'prop-types'; import {View, Animated, Keyboard} from 'react-native'; import Str from 'expensify-common/lib/str'; @@ -137,6 +137,10 @@ function AttachmentModal(props) { const {translate} = useLocalize(); const {isOffline} = useNetwork(); + useEffect(() => { + setFile(props.originalFileName ? {name: props.originalFileName} : undefined); + }, [props.originalFileName]); + const onCarouselAttachmentChange = props.onCarouselAttachmentChange; /** From e93d6dde643a722b4765589fbe3a8181ea76216e Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Fri, 20 Oct 2023 19:22:58 -0400 Subject: [PATCH 135/254] unsubscribe on new comment --- src/libs/ReportUtils.js | 11 +++++++++++ src/libs/actions/Report.js | 3 ++- .../report/ContextMenu/ContextMenuActions.js | 17 +++++++++++------ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index efd4557aea8b..8ea5c20b324f 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -777,6 +777,16 @@ function getReport(reportID) { return lodashGet(allReports, `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {}) || {}; } +/** + * Returns whether or not the author of the action is this user + * + * @param {Object} reportAction + * @returns {Boolean} + */ +function isActionCreator(reportAction) { + return reportAction.actorAccountID === currentUserAccountID +} + /** * Can only delete if the author is this user and the action is an ADDCOMMENT action or an IOU action in an unsettled report, or if the user is a * policy admin @@ -3969,6 +3979,7 @@ export { canEditReportAction, canFlagReportAction, shouldShowFlagComment, + isActionCreator, canDeleteReportAction, canLeaveRoom, sortReportsByLastRead, diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 93b2d72f9d96..2e714154c8aa 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1398,7 +1398,8 @@ function toggleSubscribeToChildReport(childReportID = '0', parentReportAction = const participantLogins = PersonalDetailsUtils.getLoginsByAccountIDs(newChat.participantAccountIDs); openReport(newChat.reportID, participantLogins, newChat, parentReportAction.reportActionID); - updateNotificationPreference(newChat.reportID, prevNotificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false, parentReportID, parentReportAction.reportActionID); + const notificationPreference = prevNotificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; + updateNotificationPreference(newChat.reportID, prevNotificationPreference, notificationPreference, false, parentReportID, parentReportAction.reportActionID); } } diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 999f8e9fa26b..cb26af15181d 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -150,11 +150,12 @@ export default [ successTextTranslateKey: '', successIcon: null, shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID) => { - const childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', ''); - const subscribed = childReportNotificationPreference && childReportNotificationPreference !== 'hidden'; - if (type !== CONTEXT_MENU_TYPES.REPORT_ACTION) { - return false; + let childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', ''); + if (!childReportNotificationPreference) { + const isActionCreator = ReportUtils.isActionCreator(reportAction); + childReportNotificationPreference = isActionCreator ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; } + const subscribed = childReportNotificationPreference !== 'hidden'; const isCommentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && !ReportUtils.isThreadFirstChat(reportAction, reportID); const isReportPreviewAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW; const isIOUAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && !ReportActionsUtils.isSplitBillAction(reportAction); @@ -182,8 +183,12 @@ export default [ successTextTranslateKey: '', successIcon: null, shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID) => { - const childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', ''); - const subscribed = childReportNotificationPreference && childReportNotificationPreference !== 'hidden'; + let childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', ''); + if (!childReportNotificationPreference) { + const isActionCreator = ReportUtils.isActionCreator(reportAction); + childReportNotificationPreference = isActionCreator ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; + } + const subscribed = childReportNotificationPreference !== 'hidden'; if (type !== CONTEXT_MENU_TYPES.REPORT_ACTION) { return false; } From 6caf051a8d0e1fb734c6fd1887f36afba9fe7fd6 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Fri, 20 Oct 2023 19:54:04 -0400 Subject: [PATCH 136/254] prettier --- src/libs/ReportUtils.js | 2 +- src/libs/actions/Report.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 8ea5c20b324f..7f231381c85d 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -784,7 +784,7 @@ function getReport(reportID) { * @returns {Boolean} */ function isActionCreator(reportAction) { - return reportAction.actorAccountID === currentUserAccountID + return reportAction.actorAccountID === currentUserAccountID; } /** diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 2e714154c8aa..38889a474231 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1398,7 +1398,8 @@ function toggleSubscribeToChildReport(childReportID = '0', parentReportAction = const participantLogins = PersonalDetailsUtils.getLoginsByAccountIDs(newChat.participantAccountIDs); openReport(newChat.reportID, participantLogins, newChat, parentReportAction.reportActionID); - const notificationPreference = prevNotificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; + const notificationPreference = + prevNotificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; updateNotificationPreference(newChat.reportID, prevNotificationPreference, notificationPreference, false, parentReportID, parentReportAction.reportActionID); } } From c4182b36cb17a87789777f2296ae48f45040ec25 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Fri, 20 Oct 2023 20:52:20 -0400 Subject: [PATCH 137/254] change subscribe based on user created --- .../home/report/ContextMenu/ContextMenuActions.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index cb26af15181d..c4fac6d21283 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -162,7 +162,11 @@ export default [ return !subscribed && (isCommentAction || isReportPreviewAction || isIOUAction); }, onPress: (closePopover, {reportAction, reportID}) => { - const childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', ''); + let childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', ''); + if (!childReportNotificationPreference) { + const isActionCreator = ReportUtils.isActionCreator(reportAction); + childReportNotificationPreference = isActionCreator ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; + } if (closePopover) { hideContextMenu(false, () => { ReportActionComposeFocusManager.focus(); @@ -198,7 +202,11 @@ export default [ return subscribed && (isCommentAction || isReportPreviewAction || isIOUAction); }, onPress: (closePopover, {reportAction, reportID}) => { - const childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', ''); + let childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', ''); + if (!childReportNotificationPreference) { + const isActionCreator = ReportUtils.isActionCreator(reportAction); + childReportNotificationPreference = isActionCreator ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; + } if (closePopover) { hideContextMenu(false, () => { ReportActionComposeFocusManager.focus(); From 1dd55f164afdd774b36031d472b5c5916b5a4ef0 Mon Sep 17 00:00:00 2001 From: Hardik Choudhary Date: Sat, 21 Oct 2023 19:28:18 +0530 Subject: [PATCH 138/254] Separate module convertToLTRForComposer created --- src/libs/convertToLTR/index.android.ts | 10 ++++++ src/libs/convertToLTR/index.ts | 10 +----- .../convertToLTRForComposer/index.android.ts | 8 +++++ src/libs/convertToLTRForComposer/index.ts | 34 +++++++++++++++++++ src/libs/convertToLTRForComposer/types.ts | 3 ++ .../ComposerWithSuggestions.js | 20 +++++------ 6 files changed, 66 insertions(+), 19 deletions(-) create mode 100644 src/libs/convertToLTR/index.android.ts create mode 100644 src/libs/convertToLTRForComposer/index.android.ts create mode 100644 src/libs/convertToLTRForComposer/index.ts create mode 100644 src/libs/convertToLTRForComposer/types.ts diff --git a/src/libs/convertToLTR/index.android.ts b/src/libs/convertToLTR/index.android.ts new file mode 100644 index 000000000000..3a4755b1710f --- /dev/null +++ b/src/libs/convertToLTR/index.android.ts @@ -0,0 +1,10 @@ +import CONST from '../../CONST'; +import ConvertToLTR from './types'; + +/** + * Android only - convert RTL text to a LTR text using Unicode controls. + * https://www.w3.org/International/questions/qa-bidi-unicode-controls + */ +const convertToLTR: ConvertToLTR = (text) => `${CONST.UNICODE.LTR}${text}`; + +export default convertToLTR; diff --git a/src/libs/convertToLTR/index.ts b/src/libs/convertToLTR/index.ts index 5cdd39acac17..58d8be93836e 100644 --- a/src/libs/convertToLTR/index.ts +++ b/src/libs/convertToLTR/index.ts @@ -1,13 +1,5 @@ -import CONST from '../../CONST'; import ConvertToLTR from './types'; -const convertToLTR: ConvertToLTR = (text) => { - // Check if the text already starts with the LTR marker (if so, return as is). - if (text.startsWith(CONST.UNICODE.LTR)) { - return text; - } +const convertToLTR: ConvertToLTR = (text) => text; - // Add the LTR marker to the beginning of the text. - return `${CONST.UNICODE.LTR}${text}`; -}; export default convertToLTR; diff --git a/src/libs/convertToLTRForComposer/index.android.ts b/src/libs/convertToLTRForComposer/index.android.ts new file mode 100644 index 000000000000..09e7f2e5cd87 --- /dev/null +++ b/src/libs/convertToLTRForComposer/index.android.ts @@ -0,0 +1,8 @@ +import ConvertToLTRForComposer from './types'; + +/** + * Android only - Do not convert RTL text to a LTR text for input box using Unicode controls. + * Android does not properly support bidirectional text for mixed content for input box + */ +const convertToLTRForComposer: ConvertToLTRForComposer = (text) => text; +export default convertToLTRForComposer; diff --git a/src/libs/convertToLTRForComposer/index.ts b/src/libs/convertToLTRForComposer/index.ts new file mode 100644 index 000000000000..4b253f3a2ecb --- /dev/null +++ b/src/libs/convertToLTRForComposer/index.ts @@ -0,0 +1,34 @@ +import CONST from '../../CONST'; +import ConvertToLTRForComposer from './types'; + +function hasLTRorRTLCharacters(text: string): boolean { + // Regular expressions to match LTR and RTL character ranges. + // eslint-disable-next-line + const ltrPattern = /[\u0001-\u05FF\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/; + const rtlPattern = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/; + + return ltrPattern.test(text) || rtlPattern.test(text); +} + +// Converts a given text to ensure it starts with the LTR (Left-to-Right) marker. +const convertToLTRForComposer: ConvertToLTRForComposer = (text) => { + // Ensure the text contains LTR or RTL characters to avoid an unwanted special character at the beginning, even after a backspace deletion. + if (!hasLTRorRTLCharacters(text)) { + return ''; + } + + // Check if the text contains only spaces. If it does, we do not concatenate it with CONST.UNICODE.LTR, + // as doing so would alter the normal behavior of the input box. + if (/^\s*$/.test(text)) { + return text; + } + + // Check if the text already starts with the LTR marker (if so, return as is). + if (text.startsWith(CONST.UNICODE.LTR)) { + return text; + } + + // Add the LTR marker to the beginning of the text. + return `${CONST.UNICODE.LTR}${text}`; +}; +export default convertToLTRForComposer; diff --git a/src/libs/convertToLTRForComposer/types.ts b/src/libs/convertToLTRForComposer/types.ts new file mode 100644 index 000000000000..c6edeaaba446 --- /dev/null +++ b/src/libs/convertToLTRForComposer/types.ts @@ -0,0 +1,3 @@ +type ConvertToLTRForComposer = (text: string) => string; + +export default ConvertToLTRForComposer; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js index bdf15e6c81ac..fbed91449ae7 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js @@ -34,9 +34,9 @@ import withKeyboardState from '../../../../components/withKeyboardState'; import {propTypes, defaultProps} from './composerWithSuggestionsProps'; import focusWithDelay from '../../../../libs/focusWithDelay'; import useDebounce from '../../../../hooks/useDebounce'; -import convertToLTR from '../../../../libs/convertToLTR'; import updateMultilineInputRange from '../../../../libs/UpdateMultilineInputRange'; import * as InputFocus from '../../../../libs/actions/InputFocus'; +import convertToLTRForComposer from '../../../../libs/convertToLTRForComposer'; const {RNTextInputReset} = NativeModules; @@ -213,7 +213,6 @@ function ComposerWithSuggestions({ (commentValue, shouldDebounceSaveComment) => { raiseIsScrollLikelyLayoutTriggered(); const {text: newComment, emojis} = EmojiUtils.replaceAndExtractEmojis(commentValue, preferredSkinTone, preferredLocale); - if (!_.isEmpty(emojis)) { const newEmojis = EmojiUtils.getAddedEmojis(emojis, emojisPresentBefore.current); if (!_.isEmpty(newEmojis)) { @@ -225,9 +224,10 @@ function ComposerWithSuggestions({ debouncedUpdateFrequentlyUsedEmojis(); } } + const newCommentConverted = convertToLTRForComposer(newComment); emojisPresentBefore.current = emojis; - setIsCommentEmpty(!!newComment.match(/^(\s)*$/)); - setValue(Platform.OS === 'android' ? newComment : convertToLTR(newComment)); + setIsCommentEmpty(!!newCommentConverted.match(/^(\s)*$/)); + setValue(newCommentConverted); if (commentValue !== newComment) { const remainder = ComposerUtils.getCommonSuffixLength(commentValue, newComment); setSelection({ @@ -237,22 +237,22 @@ function ComposerWithSuggestions({ } // Indicate that draft has been created. - if (commentRef.current.length === 0 && newComment.length !== 0) { + if (commentRef.current.length === 0 && newCommentConverted.length !== 0) { Report.setReportWithDraft(reportID, true); } // The draft has been deleted. - if (newComment.length === 0) { + if (newCommentConverted.length === 0) { Report.setReportWithDraft(reportID, false); } - commentRef.current = newComment; + commentRef.current = newCommentConverted; if (shouldDebounceSaveComment) { - debouncedSaveReportComment(reportID, newComment); + debouncedSaveReportComment(reportID, newCommentConverted); } else { - Report.saveReportComment(reportID, newComment || ''); + Report.saveReportComment(reportID, newCommentConverted || ''); } - if (newComment) { + if (newCommentConverted) { debouncedBroadcastUserIsTyping(reportID); } }, From 389f982fdfed813ac1328e7d07cff0872ab4515d Mon Sep 17 00:00:00 2001 From: Hardik Choudhary Date: Sat, 21 Oct 2023 19:30:15 +0530 Subject: [PATCH 139/254] Platform import removed --- .../home/report/ReportActionCompose/ComposerWithSuggestions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js index fbed91449ae7..a1872bf0b714 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js @@ -1,5 +1,5 @@ import React, {useEffect, useCallback, useState, useRef, useMemo, useImperativeHandle} from 'react'; -import {View, NativeModules, findNodeHandle, Platform} from 'react-native'; +import {View, NativeModules, findNodeHandle} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import lodashGet from 'lodash/get'; From a88dabc930281aed291622932b25c364d942f5d4 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Mon, 23 Oct 2023 10:15:02 +0700 Subject: [PATCH 140/254] fix update the latest change in staging --- src/libs/actions/App.js | 31 +++---------------- .../FloatingActionButtonAndPopover.js | 2 +- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 42efa0353fd5..d4837a0b17a4 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -333,14 +333,7 @@ function endSignOnTransition() { * @param {Boolean} [isThereModalToDismiss] Optional, if there is a modal to dismiss */ -function createWorkspaceAndNavigateToIt( - policyOwnerEmail = '', - makeMeAdmin = false, - policyName = '', - transitionFromOldDot = false, - shouldNavigateToAdminChat = true, - isThereModalToDismiss = true, -) { +function createWorkspaceAndNavigateToIt(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', transitionFromOldDot = false, shouldNavigateToAdminChat = true) { const policyID = Policy.generatePolicyID(); const adminsChatReportID = Policy.createWorkspace(policyOwnerEmail, makeMeAdmin, policyName, policyID); Navigation.isNavigationReady() @@ -351,26 +344,10 @@ function createWorkspaceAndNavigateToIt( } if (shouldNavigateToAdminChat) { - if (isThereModalToDismiss) { - Navigation.dismissModal(adminsChatReportID); - setTimeout(() => { - Navigation.navigate(ROUTES.SETTINGS); - setTimeout(() => { - Navigation.navigate(ROUTES.SETTINGS_WORKSPACES); - setTimeout(() => { - Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); - }, 50); - }, 50); - }, 50); - } else { - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(adminsChatReportID)); - setTimeout(() => { - Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); - }, 50); - } - } else { - Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(adminsChatReportID)); } + + Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); }) .then(endSignOnTransition); } diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index 5932049cd32c..02f1856c7bf5 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -228,7 +228,7 @@ function FloatingActionButtonAndPopover(props) { iconHeight: 40, text: props.translate('workspace.new.newWorkspace'), description: props.translate('workspace.new.getTheExpensifyCardAndMore'), - onSelected: () => interceptAnonymousUser(() => App.createWorkspaceAndNavigateToIt('', false, '', false, !props.isSmallScreenWidth, false)), + onSelected: () => interceptAnonymousUser(() => App.createWorkspaceAndNavigateToIt('', false, '', false, !props.isSmallScreenWidth)), }, ] : []), From 54a01bb3a1153a629d70c78b2a4f4ac5e7e91add Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Mon, 23 Oct 2023 10:52:30 +0700 Subject: [PATCH 141/254] fix on press create new workspace --- src/libs/actions/App.js | 2 -- .../sidebar/SidebarScreen/FloatingActionButtonAndPopover.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index d4837a0b17a4..75520d483f98 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -330,8 +330,6 @@ function endSignOnTransition() { * @param {String} [policyName] Optional, custom policy name we will use for created workspace * @param {Boolean} [transitionFromOldDot] Optional, if the user is transitioning from old dot * @param {Boolean} [shouldNavigateToAdminChat] Optional, navigate to the #admin room after creation - * @param {Boolean} [isThereModalToDismiss] Optional, if there is a modal to dismiss - */ function createWorkspaceAndNavigateToIt(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', transitionFromOldDot = false, shouldNavigateToAdminChat = true) { const policyID = Policy.generatePolicyID(); diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index 02f1856c7bf5..1bbb64d99c2b 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -228,7 +228,7 @@ function FloatingActionButtonAndPopover(props) { iconHeight: 40, text: props.translate('workspace.new.newWorkspace'), description: props.translate('workspace.new.getTheExpensifyCardAndMore'), - onSelected: () => interceptAnonymousUser(() => App.createWorkspaceAndNavigateToIt('', false, '', false, !props.isSmallScreenWidth)), + onSelected: () => interceptAnonymousUser(() => App.createWorkspaceWithPolicyDraftAndNavigateToIt()), }, ] : []), From f921458f3c660a7b0473f7365389e751e26267df Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Mon, 23 Oct 2023 09:39:04 +0200 Subject: [PATCH 142/254] Remove PolicyUtils.ts --- src/libs/PolicyUtils.ts | 217 ---------------------------------------- 1 file changed, 217 deletions(-) delete mode 100644 src/libs/PolicyUtils.ts diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts deleted file mode 100644 index ebf89867ff50..000000000000 --- a/src/libs/PolicyUtils.ts +++ /dev/null @@ -1,217 +0,0 @@ -import {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import Str from 'expensify-common/lib/str'; -import CONST from '../CONST'; -import ONYXKEYS from '../ONYXKEYS'; -import {PersonalDetails, Policy, PolicyMembers, PolicyTags} from '../types/onyx'; - -type MemberEmailsToAccountIDs = Record; -type PersonalDetailsList = Record; -type UnitRate = {rate: number}; - -/** - * Filter out the active policies, which will exclude policies with pending deletion - * These are policies that we can use to create reports with in NewDot. - */ -function getActivePolicies(policies: OnyxCollection): Policy[] | undefined { - return Object.values(policies ?? {}).filter( - (policy): policy is Policy => - policy !== null && policy && (policy.isPolicyExpenseChatEnabled || policy.areChatRoomsEnabled) && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - ); -} - -/** - * Checks if we have any errors stored within the POLICY_MEMBERS. Determines whether we should show a red brick road error or not. - * Data structure: {accountID: {role:'user', errors: []}, accountID2: {role:'admin', errors: [{1231312313: 'Unable to do X'}]}, ...} - */ -function hasPolicyMemberError(policyMembers: OnyxEntry): boolean { - return Object.values(policyMembers ?? {}).some((member) => Object.keys(member?.errors ?? {}).length > 0); -} - -/** - * Check if the policy has any error fields. - */ -function hasPolicyErrorFields(policy: OnyxEntry): boolean { - return Object.keys(policy?.errorFields ?? {}).some((fieldErrors) => Object.keys(fieldErrors ?? {}).length > 0); -} - -/** - * Check if the policy has any errors, and if it doesn't, then check if it has any error fields. - */ -function hasPolicyError(policy: OnyxEntry): boolean { - return Object.keys(policy?.errors ?? {}).length > 0 ? true : hasPolicyErrorFields(policy); -} - -/** - * Checks if we have any errors stored within the policy custom units. - */ -function hasCustomUnitsError(policy: OnyxEntry): boolean { - return Object.keys(policy?.customUnits?.errors ?? {}).length > 0; -} - -function getNumericValue(value: number, toLocaleDigit: (arg: string) => string): number | string { - const numValue = parseFloat(value.toString().replace(toLocaleDigit('.'), '.')); - if (Number.isNaN(numValue)) { - return NaN; - } - return numValue.toFixed(CONST.CUSTOM_UNITS.RATE_DECIMALS); -} - -function getRateDisplayValue(value: number, toLocaleDigit: (arg: string) => string): string { - const numValue = getNumericValue(value, toLocaleDigit); - if (Number.isNaN(numValue)) { - return ''; - } - return numValue.toString().replace('.', toLocaleDigit('.')).substring(0, value.toString().length); -} - -function getUnitRateValue(customUnitRate: UnitRate, toLocaleDigit: (arg: string) => string) { - return getRateDisplayValue((customUnitRate?.rate ?? 0) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, toLocaleDigit); -} - -/** - * Get the brick road indicator status for a policy. The policy has an error status if there is a policy member error, a custom unit error or a field error. - */ -function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry, policyMembersCollection: OnyxCollection): string { - const policyMembers = policyMembersCollection?.[`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policy?.id}`] ?? {}; - if (hasPolicyMemberError(policyMembers) || hasCustomUnitsError(policy) || hasPolicyErrorFields(policy)) { - return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; - } - return ''; -} - -/** - * Check if the policy can be displayed - * If offline, always show the policy pending deletion. - * If online, show the policy pending deletion only if there is an error. - * Note: Using a local ONYXKEYS.NETWORK subscription will cause a delay in - * updating the screen. Passing the offline status from the component. - */ -function shouldShowPolicy(policy: OnyxEntry, isOffline: boolean): boolean { - return ( - !!policy && - policy?.isPolicyExpenseChatEnabled && - policy?.role === CONST.POLICY.ROLE.ADMIN && - (isOffline || policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policy.errors ?? {}).length > 0) - ); -} - -function isExpensifyTeam(email: string): boolean { - const emailDomain = Str.extractEmailDomain(email ?? ''); - return emailDomain === CONST.EXPENSIFY_PARTNER_NAME || emailDomain === CONST.EMAIL.GUIDES_DOMAIN; -} - -function isExpensifyGuideTeam(email: string): boolean { - const emailDomain = Str.extractEmailDomain(email ?? ''); - return emailDomain === CONST.EMAIL.GUIDES_DOMAIN; -} - -/** - * Checks if the current user is an admin of the policy. - */ -const isPolicyAdmin = (policy: OnyxEntry): boolean => policy?.role === CONST.POLICY.ROLE.ADMIN; - -/** - * Create an object mapping member emails to their accountIDs. Filter for members without errors, and get the login email from the personalDetail object using the accountID. - * - * We only return members without errors. Otherwise, the members with errors would immediately be removed before the user has a chance to read the error. - */ -function getMemberAccountIDsForWorkspace(policyMembers: OnyxEntry, personalDetails: OnyxEntry): MemberEmailsToAccountIDs { - const memberEmailsToAccountIDs: MemberEmailsToAccountIDs = {}; - Object.keys(policyMembers ?? {}).forEach((accountID) => { - const member = policyMembers?.[accountID]; - if (Object.keys(member?.errors ?? {})?.length > 0) { - return; - } - const personalDetail = personalDetails?.[accountID]; - if (!personalDetail?.login) { - return; - } - memberEmailsToAccountIDs[personalDetail.login] = Number(accountID); - }); - return memberEmailsToAccountIDs; -} - -/** - * Get login list that we should not show in the workspace invite options - */ -function getIneligibleInvitees(policyMembers: OnyxEntry, personalDetails: OnyxEntry): string[] { - const memberEmailsToExclude: string[] = [...CONST.EXPENSIFY_EMAILS]; - Object.keys(policyMembers ?? {}).forEach((accountID) => { - const policyMember = policyMembers?.[accountID]; - // Policy members that are pending delete or have errors are not valid and we should show them in the invite options (don't exclude them). - if (policyMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policyMember?.errors ?? {}).length > 0) { - return; - } - const memberEmail = personalDetails?.[accountID]?.login; - if (!memberEmail) { - return; - } - memberEmailsToExclude.push(memberEmail); - }); - - return memberEmailsToExclude; -} - -/** - * Gets the tag from policy tags, defaults to the first if no key is provided. - */ -function getTag(policyTags: OnyxEntry, tagKey?: keyof typeof policyTags) { - if (Object.keys(policyTags ?? {})?.length === 0) { - return {}; - } - - const policyTagKey = tagKey ?? Object.keys(policyTags ?? {})[0]; - - return policyTags?.[policyTagKey] ?? {}; -} - -/** - * Gets the first tag name from policy tags. - */ -function getTagListName(policyTags: OnyxEntry) { - if (Object.keys(policyTags ?? {})?.length === 0) { - return ''; - } - - const policyTagKeys = Object.keys(policyTags ?? {})[0] ?? []; - - return policyTags?.[policyTagKeys]?.name ?? ''; -} - -/** - * Gets the tags of a policy for a specific key. Defaults to the first tag if no key is provided. - */ -function getTagList(policyTags: OnyxCollection, tagKey: string) { - if (Object.keys(policyTags ?? {})?.length === 0) { - return {}; - } - - const policyTagKey = tagKey ?? Object.keys(policyTags ?? {})[0]; - - return policyTags?.[policyTagKey]?.tags ?? {}; -} - -function isPendingDeletePolicy(policy: OnyxEntry): boolean { - return policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; -} - -export { - getActivePolicies, - hasPolicyMemberError, - hasPolicyError, - hasPolicyErrorFields, - hasCustomUnitsError, - getNumericValue, - getUnitRateValue, - getPolicyBrickRoadIndicatorStatus, - shouldShowPolicy, - isExpensifyTeam, - isExpensifyGuideTeam, - isPolicyAdmin, - getMemberAccountIDsForWorkspace, - getIneligibleInvitees, - getTag, - getTagListName, - getTagList, - isPendingDeletePolicy, -}; From db434434c6a45eacfea9b21324326b056d653a6a Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Mon, 23 Oct 2023 09:40:04 +0200 Subject: [PATCH 143/254] Rename PolicyUtils --- src/libs/{PolicyUtils.js => PolicyUtils.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/libs/{PolicyUtils.js => PolicyUtils.ts} (100%) diff --git a/src/libs/PolicyUtils.js b/src/libs/PolicyUtils.ts similarity index 100% rename from src/libs/PolicyUtils.js rename to src/libs/PolicyUtils.ts From 560b8cac16b9c5e44e00318d91c5eea7e845999c Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Mon, 23 Oct 2023 09:40:37 +0200 Subject: [PATCH 144/254] Migrate PolicyUtils to TS --- src/libs/PolicyUtils.ts | 192 +++++++++++++--------------------------- 1 file changed, 63 insertions(+), 129 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 6bbae72f1d80..ebf89867ff50 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -1,69 +1,54 @@ -import _ from 'underscore'; -import lodashGet from 'lodash/get'; +import {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Str from 'expensify-common/lib/str'; import CONST from '../CONST'; import ONYXKEYS from '../ONYXKEYS'; +import {PersonalDetails, Policy, PolicyMembers, PolicyTags} from '../types/onyx'; + +type MemberEmailsToAccountIDs = Record; +type PersonalDetailsList = Record; +type UnitRate = {rate: number}; /** * Filter out the active policies, which will exclude policies with pending deletion * These are policies that we can use to create reports with in NewDot. - * @param {Object} policies - * @returns {Array} */ -function getActivePolicies(policies) { - return _.filter(policies, (policy) => policy && (policy.isPolicyExpenseChatEnabled || policy.areChatRoomsEnabled) && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); +function getActivePolicies(policies: OnyxCollection): Policy[] | undefined { + return Object.values(policies ?? {}).filter( + (policy): policy is Policy => + policy !== null && policy && (policy.isPolicyExpenseChatEnabled || policy.areChatRoomsEnabled) && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + ); } /** * Checks if we have any errors stored within the POLICY_MEMBERS. Determines whether we should show a red brick road error or not. * Data structure: {accountID: {role:'user', errors: []}, accountID2: {role:'admin', errors: [{1231312313: 'Unable to do X'}]}, ...} - * - * @param {Object} policyMembers - * @returns {Boolean} */ -function hasPolicyMemberError(policyMembers) { - return _.some(policyMembers, (member) => !_.isEmpty(member.errors)); +function hasPolicyMemberError(policyMembers: OnyxEntry): boolean { + return Object.values(policyMembers ?? {}).some((member) => Object.keys(member?.errors ?? {}).length > 0); } /** * Check if the policy has any error fields. - * - * @param {Object} policy - * @param {Object} policy.errorFields - * @return {Boolean} */ -function hasPolicyErrorFields(policy) { - return _.some(lodashGet(policy, 'errorFields', {}), (fieldErrors) => !_.isEmpty(fieldErrors)); +function hasPolicyErrorFields(policy: OnyxEntry): boolean { + return Object.keys(policy?.errorFields ?? {}).some((fieldErrors) => Object.keys(fieldErrors ?? {}).length > 0); } /** * Check if the policy has any errors, and if it doesn't, then check if it has any error fields. - * - * @param {Object} policy - * @param {Object} policy.errors - * @param {Object} policy.errorFields - * @return {Boolean} */ -function hasPolicyError(policy) { - return !_.isEmpty(lodashGet(policy, 'errors', {})) ? true : hasPolicyErrorFields(policy); +function hasPolicyError(policy: OnyxEntry): boolean { + return Object.keys(policy?.errors ?? {}).length > 0 ? true : hasPolicyErrorFields(policy); } /** * Checks if we have any errors stored within the policy custom units. - * - * @param {Object} policy - * @returns {Boolean} */ -function hasCustomUnitsError(policy) { - return !_.isEmpty(_.pick(lodashGet(policy, 'customUnits', {}), 'errors')); +function hasCustomUnitsError(policy: OnyxEntry): boolean { + return Object.keys(policy?.customUnits?.errors ?? {}).length > 0; } -/** - * @param {Number} value - * @param {Function} toLocaleDigit - * @returns {Number} - */ -function getNumericValue(value, toLocaleDigit) { +function getNumericValue(value: number, toLocaleDigit: (arg: string) => string): number | string { const numValue = parseFloat(value.toString().replace(toLocaleDigit('.'), '.')); if (Number.isNaN(numValue)) { return NaN; @@ -71,39 +56,23 @@ function getNumericValue(value, toLocaleDigit) { return numValue.toFixed(CONST.CUSTOM_UNITS.RATE_DECIMALS); } -/** - * @param {Number} value - * @param {Function} toLocaleDigit - * @returns {String} - */ -function getRateDisplayValue(value, toLocaleDigit) { +function getRateDisplayValue(value: number, toLocaleDigit: (arg: string) => string): string { const numValue = getNumericValue(value, toLocaleDigit); if (Number.isNaN(numValue)) { return ''; } - return numValue.toString().replace('.', toLocaleDigit('.')).substring(0, value.length); + return numValue.toString().replace('.', toLocaleDigit('.')).substring(0, value.toString().length); } -/** - * @param {Object} customUnitRate - * @param {Number} customUnitRate.rate - * @param {Function} toLocaleDigit - * @returns {String} - */ -function getUnitRateValue(customUnitRate, toLocaleDigit) { - return getRateDisplayValue(lodashGet(customUnitRate, 'rate', 0) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, toLocaleDigit); +function getUnitRateValue(customUnitRate: UnitRate, toLocaleDigit: (arg: string) => string) { + return getRateDisplayValue((customUnitRate?.rate ?? 0) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, toLocaleDigit); } /** * Get the brick road indicator status for a policy. The policy has an error status if there is a policy member error, a custom unit error or a field error. - * - * @param {Object} policy - * @param {String} policy.id - * @param {Object} policyMembersCollection - * @returns {String} */ -function getPolicyBrickRoadIndicatorStatus(policy, policyMembersCollection) { - const policyMembers = lodashGet(policyMembersCollection, `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policy.id}`, {}); +function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry, policyMembersCollection: OnyxCollection): string { + const policyMembers = policyMembersCollection?.[`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policy?.id}`] ?? {}; if (hasPolicyMemberError(policyMembers) || hasCustomUnitsError(policy) || hasPolicyErrorFields(policy)) { return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; } @@ -116,62 +85,45 @@ function getPolicyBrickRoadIndicatorStatus(policy, policyMembersCollection) { * If online, show the policy pending deletion only if there is an error. * Note: Using a local ONYXKEYS.NETWORK subscription will cause a delay in * updating the screen. Passing the offline status from the component. - * @param {Object} policy - * @param {Boolean} isOffline - * @returns {Boolean} */ -function shouldShowPolicy(policy, isOffline) { +function shouldShowPolicy(policy: OnyxEntry, isOffline: boolean): boolean { return ( - policy && - policy.isPolicyExpenseChatEnabled && - policy.role === CONST.POLICY.ROLE.ADMIN && - (isOffline || policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !_.isEmpty(policy.errors)) + !!policy && + policy?.isPolicyExpenseChatEnabled && + policy?.role === CONST.POLICY.ROLE.ADMIN && + (isOffline || policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policy.errors ?? {}).length > 0) ); } -/** - * @param {string} email - * @returns {boolean} - */ -function isExpensifyTeam(email) { - const emailDomain = Str.extractEmailDomain(email); +function isExpensifyTeam(email: string): boolean { + const emailDomain = Str.extractEmailDomain(email ?? ''); return emailDomain === CONST.EXPENSIFY_PARTNER_NAME || emailDomain === CONST.EMAIL.GUIDES_DOMAIN; } -/** - * @param {string} email - * @returns {boolean} - */ -function isExpensifyGuideTeam(email) { - const emailDomain = Str.extractEmailDomain(email); +function isExpensifyGuideTeam(email: string): boolean { + const emailDomain = Str.extractEmailDomain(email ?? ''); return emailDomain === CONST.EMAIL.GUIDES_DOMAIN; } /** * Checks if the current user is an admin of the policy. - * - * @param {Object} policy - * @returns {Boolean} */ -const isPolicyAdmin = (policy) => lodashGet(policy, 'role') === CONST.POLICY.ROLE.ADMIN; +const isPolicyAdmin = (policy: OnyxEntry): boolean => policy?.role === CONST.POLICY.ROLE.ADMIN; /** - * @param {Object} policyMembers - * @param {Object} personalDetails - * @returns {Object} - * * Create an object mapping member emails to their accountIDs. Filter for members without errors, and get the login email from the personalDetail object using the accountID. * * We only return members without errors. Otherwise, the members with errors would immediately be removed before the user has a chance to read the error. */ -function getMemberAccountIDsForWorkspace(policyMembers, personalDetails) { - const memberEmailsToAccountIDs = {}; - _.each(policyMembers, (member, accountID) => { - if (!_.isEmpty(member.errors)) { +function getMemberAccountIDsForWorkspace(policyMembers: OnyxEntry, personalDetails: OnyxEntry): MemberEmailsToAccountIDs { + const memberEmailsToAccountIDs: MemberEmailsToAccountIDs = {}; + Object.keys(policyMembers ?? {}).forEach((accountID) => { + const member = policyMembers?.[accountID]; + if (Object.keys(member?.errors ?? {})?.length > 0) { return; } - const personalDetail = personalDetails[accountID]; - if (!personalDetail || !personalDetail.login) { + const personalDetail = personalDetails?.[accountID]; + if (!personalDetail?.login) { return; } memberEmailsToAccountIDs[personalDetail.login] = Number(accountID); @@ -181,19 +133,16 @@ function getMemberAccountIDsForWorkspace(policyMembers, personalDetails) { /** * Get login list that we should not show in the workspace invite options - * - * @param {Object} policyMembers - * @param {Object} personalDetails - * @returns {Array} */ -function getIneligibleInvitees(policyMembers, personalDetails) { - const memberEmailsToExclude = [...CONST.EXPENSIFY_EMAILS]; - _.each(policyMembers, (policyMember, accountID) => { +function getIneligibleInvitees(policyMembers: OnyxEntry, personalDetails: OnyxEntry): string[] { + const memberEmailsToExclude: string[] = [...CONST.EXPENSIFY_EMAILS]; + Object.keys(policyMembers ?? {}).forEach((accountID) => { + const policyMember = policyMembers?.[accountID]; // Policy members that are pending delete or have errors are not valid and we should show them in the invite options (don't exclude them). - if (policyMember.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !_.isEmpty(policyMember.errors)) { + if (policyMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policyMember?.errors ?? {}).length > 0) { return; } - const memberEmail = lodashGet(personalDetails, `[${accountID}].login`); + const memberEmail = personalDetails?.[accountID]?.login; if (!memberEmail) { return; } @@ -205,60 +154,45 @@ function getIneligibleInvitees(policyMembers, personalDetails) { /** * Gets the tag from policy tags, defaults to the first if no key is provided. - * - * @param {Object} policyTags - * @param {String} [tagKey] - * @returns {Object} */ -function getTag(policyTags, tagKey) { - if (_.isEmpty(policyTags)) { +function getTag(policyTags: OnyxEntry, tagKey?: keyof typeof policyTags) { + if (Object.keys(policyTags ?? {})?.length === 0) { return {}; } - const policyTagKey = tagKey || _.first(_.keys(policyTags)); + const policyTagKey = tagKey ?? Object.keys(policyTags ?? {})[0]; - return lodashGet(policyTags, policyTagKey, {}); + return policyTags?.[policyTagKey] ?? {}; } /** * Gets the first tag name from policy tags. - * - * @param {Object} policyTags - * @returns {String} */ -function getTagListName(policyTags) { - if (_.isEmpty(policyTags)) { +function getTagListName(policyTags: OnyxEntry) { + if (Object.keys(policyTags ?? {})?.length === 0) { return ''; } - const policyTagKeys = _.keys(policyTags) || []; + const policyTagKeys = Object.keys(policyTags ?? {})[0] ?? []; - return lodashGet(policyTags, [_.first(policyTagKeys), 'name'], ''); + return policyTags?.[policyTagKeys]?.name ?? ''; } /** * Gets the tags of a policy for a specific key. Defaults to the first tag if no key is provided. - * - * @param {Object} policyTags - * @param {String} [tagKey] - * @returns {String} */ -function getTagList(policyTags, tagKey) { - if (_.isEmpty(policyTags)) { +function getTagList(policyTags: OnyxCollection, tagKey: string) { + if (Object.keys(policyTags ?? {})?.length === 0) { return {}; } - const policyTagKey = tagKey || _.first(_.keys(policyTags)); + const policyTagKey = tagKey ?? Object.keys(policyTags ?? {})[0]; - return lodashGet(policyTags, [policyTagKey, 'tags'], {}); + return policyTags?.[policyTagKey]?.tags ?? {}; } -/** - * @param {Object} policy - * @returns {Boolean} - */ -function isPendingDeletePolicy(policy) { - return policy.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; +function isPendingDeletePolicy(policy: OnyxEntry): boolean { + return policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; } export { From c133db8e73151501fd724383c523cd8c013993f7 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Wed, 18 Oct 2023 16:00:17 +0200 Subject: [PATCH 145/254] Use FormProvider in NewContactMethodPage --- .../settings/Profile/Contacts/NewContactMethodPage.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/settings/Profile/Contacts/NewContactMethodPage.js b/src/pages/settings/Profile/Contacts/NewContactMethodPage.js index 480c425a9094..e6f55b7cd6cb 100644 --- a/src/pages/settings/Profile/Contacts/NewContactMethodPage.js +++ b/src/pages/settings/Profile/Contacts/NewContactMethodPage.js @@ -18,8 +18,9 @@ import styles from '../../../../styles/styles'; import * as User from '../../../../libs/actions/User'; import * as LoginUtils from '../../../../libs/LoginUtils'; import * as ErrorUtils from '../../../../libs/ErrorUtils'; -import Form from '../../../../components/Form'; import CONST from '../../../../CONST'; +import FormProvider from '../../../../components/Form/FormProvider'; +import InputWrapper from '../../../../components/Form/InputWrapper'; const propTypes = { /* Onyx Props */ @@ -104,7 +105,7 @@ function NewContactMethodPage(props) { title={props.translate('contacts.newContactMethod')} onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS)} /> -
{props.translate('common.pleaseEnterEmailOrPhoneNumber')} - -
+ ); } From 425ecb5b4ecd4f6506f734982e5f67eb28994f85 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Thu, 19 Oct 2023 11:36:49 +0200 Subject: [PATCH 146/254] Use FormProvider in StatusSetPage --- .../settings/Profile/CustomStatus/StatusSetPage.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/pages/settings/Profile/CustomStatus/StatusSetPage.js b/src/pages/settings/Profile/CustomStatus/StatusSetPage.js index 189ec3e5f2f3..ac04c3ee5d4f 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusSetPage.js +++ b/src/pages/settings/Profile/CustomStatus/StatusSetPage.js @@ -3,7 +3,6 @@ import {View} from 'react-native'; import PropTypes from 'prop-types'; import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; -import Form from '../../../../components/Form'; import HeaderWithBackButton from '../../../../components/HeaderWithBackButton'; import ROUTES from '../../../../ROUTES'; import ScreenWrapper from '../../../../components/ScreenWrapper'; @@ -17,6 +16,8 @@ import ONYXKEYS from '../../../../ONYXKEYS'; import * as User from '../../../../libs/actions/User'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes} from '../../../../components/withCurrentUserPersonalDetails'; import TextInput from '../../../../components/TextInput'; +import FormProvider from '../../../../components/Form/FormProvider'; +import InputWrapper from '../../../../components/Form/InputWrapper'; const propTypes = { /** The draft status of the user */ @@ -52,7 +53,7 @@ function StatusSetPage({draftStatus, currentUserPersonalDetails}) { title={translate('statusPage.status')} onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_STATUS)} /> -
- - -
+ ); } From b9138d78d2bd82c7ac92a8fdea4e993bea733ce1 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Thu, 19 Oct 2023 19:03:08 +0200 Subject: [PATCH 147/254] fix: flashlist container styles --- src/pages/settings/Wallet/PaymentMethodList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Wallet/PaymentMethodList.js b/src/pages/settings/Wallet/PaymentMethodList.js index 921d9c9d3a64..c30d8e66f2f1 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.js +++ b/src/pages/settings/Wallet/PaymentMethodList.js @@ -346,7 +346,7 @@ function PaymentMethodList({ ListFooterComponent={shouldShowAddBankAccount ? renderListFooterComponent : null} onContentSizeChange={onListContentSizeChange} scrollEnabled={shouldEnableScroll} - style={style} + contentContainerStyle={style} /> {shouldShowAddPaymentMethodButton && ( From 7a9e84628e7800c4135a990ac932331f81da3f35 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Thu, 19 Oct 2023 19:08:04 +0200 Subject: [PATCH 148/254] fix: move flashlist container styles to a wrapper --- .../settings/Wallet/PaymentMethodList.js | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/pages/settings/Wallet/PaymentMethodList.js b/src/pages/settings/Wallet/PaymentMethodList.js index c30d8e66f2f1..ce5c5a37b56a 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.js +++ b/src/pages/settings/Wallet/PaymentMethodList.js @@ -1,5 +1,6 @@ import _ from 'underscore'; import React, {useCallback, useMemo} from 'react'; +import {View} from 'react-native'; import PropTypes from 'prop-types'; import {FlashList} from '@shopify/flash-list'; import lodashGet from 'lodash/get'; @@ -336,18 +337,19 @@ function PaymentMethodList({ return ( <> - + + + {shouldShowAddPaymentMethodButton && ( {(isOffline) => ( From 7ce37a6ea8f7203b146f51b98be73ad4eef096ba Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Mon, 23 Oct 2023 13:21:57 +0200 Subject: [PATCH 149/254] chore: add minHeight to the flashlist wrapper View to prevent measurement fail at initial render --- src/pages/settings/Wallet/PaymentMethodList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Wallet/PaymentMethodList.js b/src/pages/settings/Wallet/PaymentMethodList.js index ce5c5a37b56a..b46197725604 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.js +++ b/src/pages/settings/Wallet/PaymentMethodList.js @@ -337,7 +337,7 @@ function PaymentMethodList({ return ( <> - + Date: Mon, 23 Oct 2023 21:25:31 +0700 Subject: [PATCH 150/254] Add default value to selected participants --- .../MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js index 9e2f1a3a1f45..768f66cc2c7f 100644 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js @@ -54,7 +54,7 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route}) { const isScanRequest = MoneyRequestUtils.isScanRequest(selectedTab); const isSplitRequest = iou.id === CONST.IOU.MONEY_REQUEST_TYPE.SPLIT; const [headerTitle, setHeaderTitle] = useState(); - const [selectedParticipants, setSelectedParticipants] = useState([]); + const [selectedParticipants, setSelectedParticipants] = useState(iou.participants); useEffect(() => { if (isDistanceRequest) { From ec7860f932378b9773afcb3ed81a99a2511d26e9 Mon Sep 17 00:00:00 2001 From: Hardik Choudhary Date: Mon, 23 Oct 2023 20:02:37 +0530 Subject: [PATCH 151/254] rule name specified --- src/libs/convertToLTRForComposer/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/convertToLTRForComposer/index.ts b/src/libs/convertToLTRForComposer/index.ts index 4b253f3a2ecb..eb14bfa8c11a 100644 --- a/src/libs/convertToLTRForComposer/index.ts +++ b/src/libs/convertToLTRForComposer/index.ts @@ -3,7 +3,7 @@ import ConvertToLTRForComposer from './types'; function hasLTRorRTLCharacters(text: string): boolean { // Regular expressions to match LTR and RTL character ranges. - // eslint-disable-next-line + // eslint-disable-next-line no-control-regex const ltrPattern = /[\u0001-\u05FF\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/; const rtlPattern = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/; From cd3261a101db98b1f86966d82d21b940dc45ec4c Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Mon, 23 Oct 2023 22:03:31 +0700 Subject: [PATCH 152/254] fix remove createWorkspaceAndNavigateToIt --- .../Navigation/AppNavigator/AuthScreens.js | 2 +- src/libs/actions/App.js | 39 +++---------------- src/libs/actions/Policy.js | 4 +- src/pages/workspace/WorkspaceInitialPage.js | 2 +- src/pages/workspace/WorkspaceNewRoomPage.js | 2 +- 5 files changed, 11 insertions(+), 38 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index dd7175dbc6f6..8b07034400a3 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -184,7 +184,7 @@ class AuthScreens extends React.Component { App.reconnectApp(this.props.lastUpdateIDAppliedToClient); } - App.setUpPoliciesAndNavigate(this.props.session, !this.props.isSmallScreenWidth); + App.setUpPoliciesAndNavigate(this.props.session); App.redirectThirdPartyDesktopSignIn(); // Check if we should be running any demos immediately after signing in. diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 75520d483f98..678b5f9f4594 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -322,44 +322,17 @@ function endSignOnTransition() { return resolveSignOnTransitionToFinishPromise(); } -/** - * Create a new workspace and navigate to it - * - * @param {String} [policyOwnerEmail] Optional, the email of the account to make the owner of the policy - * @param {Boolean} [makeMeAdmin] Optional, leave the calling account as an admin on the policy - * @param {String} [policyName] Optional, custom policy name we will use for created workspace - * @param {Boolean} [transitionFromOldDot] Optional, if the user is transitioning from old dot - * @param {Boolean} [shouldNavigateToAdminChat] Optional, navigate to the #admin room after creation - */ -function createWorkspaceAndNavigateToIt(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', transitionFromOldDot = false, shouldNavigateToAdminChat = true) { - const policyID = Policy.generatePolicyID(); - const adminsChatReportID = Policy.createWorkspace(policyOwnerEmail, makeMeAdmin, policyName, policyID); - Navigation.isNavigationReady() - .then(() => { - if (transitionFromOldDot) { - // We must call goBack() to remove the /transition route from history - Navigation.goBack(ROUTES.HOME); - } - - if (shouldNavigateToAdminChat) { - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(adminsChatReportID)); - } - - Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); - }) - .then(endSignOnTransition); -} - /** * Create a new draft workspace and navigate to it * * @param {String} [policyOwnerEmail] Optional, the email of the account to make the owner of the policy * @param {String} [policyName] Optional, custom policy name we will use for created workspace * @param {Boolean} [transitionFromOldDot] Optional, if the user is transitioning from old dot + * @param {Boolean} [makeMeAdmin] Optional, leave the calling account as an admin on the policy */ -function createWorkspaceWithPolicyDraftAndNavigateToIt(policyOwnerEmail = '', policyName = '', transitionFromOldDot = false) { +function createWorkspaceWithPolicyDraftAndNavigateToIt(policyOwnerEmail = '', policyName = '', transitionFromOldDot = false, makeMeAdmin = false) { const policyID = Policy.generatePolicyID(); - Policy.createDraftInitialWorkspace(policyOwnerEmail, policyName, policyID); + Policy.createDraftInitialWorkspace(policyOwnerEmail, policyName, policyID, makeMeAdmin); Navigation.isNavigationReady() .then(() => { @@ -403,9 +376,8 @@ function savePolicyDraftByNewWorkspace(policyID, policyName, policyOwnerEmail = * pass it in as a parameter. withOnyx guarantees that the value has been read * from Onyx because it will not render the AuthScreens until that point. * @param {Object} session - * @param {Boolean} shouldNavigateToAdminChat Should we navigate to admin chat after creating workspace */ -function setUpPoliciesAndNavigate(session, shouldNavigateToAdminChat) { +function setUpPoliciesAndNavigate(session) { const currentUrl = getCurrentUrl(); if (!session || !currentUrl || !currentUrl.includes('exitTo')) { return; @@ -426,7 +398,7 @@ function setUpPoliciesAndNavigate(session, shouldNavigateToAdminChat) { const shouldCreateFreePolicy = !isLoggingInAsNewUser && isTransitioning && exitTo === ROUTES.WORKSPACE_NEW; if (shouldCreateFreePolicy) { - createWorkspaceAndNavigateToIt(policyOwnerEmail, makeMeAdmin, policyName, true, shouldNavigateToAdminChat); + createWorkspaceWithPolicyDraftAndNavigateToIt(policyOwnerEmail, policyName, true, makeMeAdmin); return; } if (!isLoggingInAsNewUser && exitTo) { @@ -555,7 +527,6 @@ export { handleRestrictedEvent, beginDeepLinkRedirect, beginDeepLinkRedirectAfterTransition, - createWorkspaceAndNavigateToIt, getMissingOnyxUpdates, finalReconnectAppAfterActivatingReliableUpdates, savePolicyDraftByNewWorkspace, diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 89324dd35485..a239ca691de3 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -922,8 +922,9 @@ function buildOptimisticCustomUnits() { * @param {String} [policyOwnerEmail] Optional, the email of the account to make the owner of the policy * @param {String} [policyName] Optional, custom policy name we will use for created workspace * @param {String} [policyID] Optional, custom policy id we will use for created workspace + * @param {Boolean} [makeMeAdmin] Optional, leave the calling account as an admin on the policy */ -function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', policyID = generatePolicyID()) { +function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', policyID = generatePolicyID(), makeMeAdmin = false) { const workspaceName = policyName || generateDefaultWorkspaceName(policyOwnerEmail); const {customUnits} = buildOptimisticCustomUnits(); @@ -941,6 +942,7 @@ function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', pol outputCurrency: lodashGet(allPersonalDetails, [sessionAccountID, 'localCurrencyCode'], CONST.CURRENCY.USD), pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, customUnits, + makeMeAdmin, }, }, { diff --git a/src/pages/workspace/WorkspaceInitialPage.js b/src/pages/workspace/WorkspaceInitialPage.js index d275b7f0dd10..edaa1b29bffd 100644 --- a/src/pages/workspace/WorkspaceInitialPage.js +++ b/src/pages/workspace/WorkspaceInitialPage.js @@ -88,7 +88,7 @@ function WorkspaceInitialPage(props) { return; } - App.savePolicyDraftByNewWorkspace(props.policyDraft.id, props.policyDraft.name, '', false); + App.savePolicyDraftByNewWorkspace(props.policyDraft.id, props.policyDraft.name, '', props.policyDraft.makeMeAdmin); // We only care when the component renders the first time // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index da0bf845cc81..8a62a3b876ea 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -165,7 +165,7 @@ function WorkspaceNewRoomPage(props) { shouldShow={!Permissions.canUsePolicyRooms(props.betas) || !workspaceOptions.length} shouldShowBackButton={false} linkKey="workspace.emptyWorkspace.title" - onLinkPress={() => App.createWorkspaceAndNavigateToIt('', false, '', false, false)} + onLinkPress={() => App.createWorkspaceWithPolicyDraftAndNavigateToIt()} > Date: Mon, 23 Oct 2023 11:47:15 -0400 Subject: [PATCH 153/254] Update semver on desktop --- desktop/package-lock.json | 2 +- desktop/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/desktop/package-lock.json b/desktop/package-lock.json index abc1299154ef..6f8be8fdba83 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -10,7 +10,7 @@ "electron-context-menu": "^2.3.0", "electron-log": "^4.4.7", "electron-serve": "^1.0.0", - "electron-updater": "^4.3.4", + "electron-updater": "^4.6.5", "node-machine-id": "^1.1.12" } }, diff --git a/desktop/package.json b/desktop/package.json index 45283a260970..61485f7ba390 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -7,7 +7,7 @@ "electron-context-menu": "^2.3.0", "electron-log": "^4.4.7", "electron-serve": "^1.0.0", - "electron-updater": "^4.3.4", + "electron-updater": "^4.6.5", "node-machine-id": "^1.1.12" }, "author": "Expensify, Inc.", From 8de04a64371b80eafc95ede4e4b84510a756b73e Mon Sep 17 00:00:00 2001 From: Rocio Perez-Cano Date: Mon, 23 Oct 2023 12:04:36 -0400 Subject: [PATCH 154/254] Update to 6.0 (good luck) --- desktop/package-lock.json | 126 +++++++++++++++++++------------------- desktop/package.json | 2 +- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/desktop/package-lock.json b/desktop/package-lock.json index 6f8be8fdba83..0ff280c4b9c6 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -10,15 +10,10 @@ "electron-context-menu": "^2.3.0", "electron-log": "^4.4.7", "electron-serve": "^1.0.0", - "electron-updater": "^4.6.5", + "electron-updater": "^6.1.4", "node-machine-id": "^1.1.12" } }, - "node_modules/@types/semver": { - "version": "7.3.9", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", - "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==" - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -55,11 +50,11 @@ } }, "node_modules/builder-util-runtime": { - "version": "8.9.2", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.9.2.tgz", - "integrity": "sha512-rhuKm5vh7E0aAmT6i8aoSfEjxzdYEFX7zDApK+eNgOhjofnWb74d9SRJv0H/8nsgOkos0TZ4zxW0P8J4N7xQ2A==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.1.tgz", + "integrity": "sha512-2rLv/uQD2x+dJ0J3xtsmI12AlRyk7p45TEbE/6o/fbb633e/S3pPgm+ct+JHsoY7r39dKHnGEFk/AASRFdnXmA==", "dependencies": { - "debug": "^4.3.2", + "debug": "^4.3.4", "sax": "^1.2.4" }, "engines": { @@ -98,9 +93,9 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { "ms": "2.1.2" }, @@ -155,18 +150,18 @@ "integrity": "sha512-tQJBCbXKoKCfkBC143QCqnEtT1s8dNE2V+b/82NF6lxnGO/2Q3a3GSLHtKl3iEDQgdzTf9pH7p418xq2rXbz1Q==" }, "node_modules/electron-updater": { - "version": "4.6.5", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.6.5.tgz", - "integrity": "sha512-kdTly8O9mSZfm9fslc1mnCY+mYOeaYRy7ERa2Fed240u01BKll3aiupzkd07qKw69KvhBSzuHroIW3mF0D8DWA==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.4.tgz", + "integrity": "sha512-yYAJc6RQjjV4WtInZVn+ZcLyXRhbVXoomKEfUUwDqIk5s2wxzLhWaor7lrNgxODyODhipjg4SVPMhJHi5EnsCA==", "dependencies": { - "@types/semver": "^7.3.6", - "builder-util-runtime": "8.9.2", - "fs-extra": "^10.0.0", + "builder-util-runtime": "9.2.1", + "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", "lodash.escaperegexp": "^4.1.2", "lodash.isequal": "^4.5.0", - "semver": "^7.3.5" + "semver": "^7.3.8", + "tiny-typed-emitter": "^2.1.0" } }, "node_modules/emoji-regex": { @@ -206,9 +201,9 @@ } }, "node_modules/fs-extra": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -219,9 +214,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", @@ -333,14 +328,14 @@ } }, "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" }, "node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -410,6 +405,11 @@ "node": ">=8" } }, + "node_modules/tiny-typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz", + "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==" + }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -437,11 +437,6 @@ } }, "dependencies": { - "@types/semver": { - "version": "7.3.9", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", - "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==" - }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -466,11 +461,11 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" }, "builder-util-runtime": { - "version": "8.9.2", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.9.2.tgz", - "integrity": "sha512-rhuKm5vh7E0aAmT6i8aoSfEjxzdYEFX7zDApK+eNgOhjofnWb74d9SRJv0H/8nsgOkos0TZ4zxW0P8J4N7xQ2A==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.1.tgz", + "integrity": "sha512-2rLv/uQD2x+dJ0J3xtsmI12AlRyk7p45TEbE/6o/fbb633e/S3pPgm+ct+JHsoY7r39dKHnGEFk/AASRFdnXmA==", "requires": { - "debug": "^4.3.2", + "debug": "^4.3.4", "sax": "^1.2.4" } }, @@ -497,9 +492,9 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { "ms": "2.1.2" } @@ -540,18 +535,18 @@ "integrity": "sha512-tQJBCbXKoKCfkBC143QCqnEtT1s8dNE2V+b/82NF6lxnGO/2Q3a3GSLHtKl3iEDQgdzTf9pH7p418xq2rXbz1Q==" }, "electron-updater": { - "version": "4.6.5", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.6.5.tgz", - "integrity": "sha512-kdTly8O9mSZfm9fslc1mnCY+mYOeaYRy7ERa2Fed240u01BKll3aiupzkd07qKw69KvhBSzuHroIW3mF0D8DWA==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.4.tgz", + "integrity": "sha512-yYAJc6RQjjV4WtInZVn+ZcLyXRhbVXoomKEfUUwDqIk5s2wxzLhWaor7lrNgxODyODhipjg4SVPMhJHi5EnsCA==", "requires": { - "@types/semver": "^7.3.6", - "builder-util-runtime": "8.9.2", - "fs-extra": "^10.0.0", + "builder-util-runtime": "9.2.1", + "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", "lodash.escaperegexp": "^4.1.2", "lodash.isequal": "^4.5.0", - "semver": "^7.3.5" + "semver": "^7.3.8", + "tiny-typed-emitter": "^2.1.0" } }, "emoji-regex": { @@ -582,9 +577,9 @@ } }, "fs-extra": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -592,9 +587,9 @@ } }, "graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -680,14 +675,14 @@ } }, "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "requires": { "lru-cache": "^6.0.0" } @@ -736,6 +731,11 @@ "ansi-regex": "^5.0.1" } }, + "tiny-typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz", + "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==" + }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", diff --git a/desktop/package.json b/desktop/package.json index 61485f7ba390..bf49d93f1a7b 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -7,7 +7,7 @@ "electron-context-menu": "^2.3.0", "electron-log": "^4.4.7", "electron-serve": "^1.0.0", - "electron-updater": "^4.6.5", + "electron-updater": "^6.1.4", "node-machine-id": "^1.1.12" }, "author": "Expensify, Inc.", From 8d81cb602f90ed8a5dde0352e2e31458ffabf801 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Tue, 24 Oct 2023 03:28:56 +0100 Subject: [PATCH 155/254] move the magic code input to a fixed footer outside the scrollview --- .../TwoFactorAuth/Steps/VerifyStep.js | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.js b/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.js index 560a395e6844..a254b28341f1 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.js +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.js @@ -118,23 +118,23 @@ function VerifyStep({account, session}) { {translate('twoFactorAuth.enterCode')} - + + + - -