From 5ae65ead66ad2f87b2dcd4b1ab55c87a4b3a17ce Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 26 Jul 2023 16:19:52 -0700 Subject: [PATCH 001/376] 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/376] 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/376] 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/376] 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/376] 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/376] 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/376] 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/376] 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/376] 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/376] 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/376] 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/376] 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/376] 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/376] 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/376] 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 68b2444a762ab9bf8a604a6c5d34891225f51c3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Tue, 19 Sep 2023 17:41:02 +0200 Subject: [PATCH 016/376] migrate SelectionScraper --- .../{index.native.js => index.native.ts} | 0 .../SelectionScraper/{index.js => index.ts} | 71 ++++++++++--------- 2 files changed, 37 insertions(+), 34 deletions(-) rename src/libs/SelectionScraper/{index.native.js => index.native.ts} (100%) rename src/libs/SelectionScraper/{index.js => index.ts} (72%) diff --git a/src/libs/SelectionScraper/index.native.js b/src/libs/SelectionScraper/index.native.ts similarity index 100% rename from src/libs/SelectionScraper/index.native.js rename to src/libs/SelectionScraper/index.native.ts diff --git a/src/libs/SelectionScraper/index.js b/src/libs/SelectionScraper/index.ts similarity index 72% rename from src/libs/SelectionScraper/index.js rename to src/libs/SelectionScraper/index.ts index 44b87deba796..79639cd0f28a 100644 --- a/src/libs/SelectionScraper/index.js +++ b/src/libs/SelectionScraper/index.ts @@ -1,7 +1,7 @@ import render from 'dom-serializer'; import {parseDocument} from 'htmlparser2'; -import _ from 'underscore'; import Str from 'expensify-common/lib/str'; +import {DataNode, Element, Node} from 'domhandler'; import CONST from '../../CONST'; const elementsWillBeSkipped = ['html', 'body']; @@ -9,17 +9,17 @@ const tagAttribute = 'data-testid'; /** * Reads html of selection. If browser doesn't support Selection API, returns empty string. - * @returns {String} HTML of selection as String */ -const getHTMLOfSelection = () => { +const getHTMLOfSelection = (): string => { // If browser doesn't support Selection API, return an empty string. - if (!window.getSelection) { + const selection = window.getSelection(); + + if (!selection || !window.getSelection) { return ''; } - const selection = window.getSelection(); if (selection.rangeCount <= 0) { - return window.getSelection().toString(); + return window.getSelection()?.toString() ?? ''; } const div = document.createElement('div'); @@ -64,17 +64,17 @@ const getHTMLOfSelection = () => { // and finally commonAncestorContainer.parentNode.closest('data-testid') is targeted dom. if (range.commonAncestorContainer instanceof HTMLElement) { parent = range.commonAncestorContainer.closest(`[${tagAttribute}]`); - } else { - parent = range.commonAncestorContainer.parentNode.closest(`[${tagAttribute}]`); + } else if (range.commonAncestorContainer.parentNode) { + parent = (range.commonAncestorContainer.parentNode as HTMLElement).closest(`[${tagAttribute}]`); } // Keep traversing up to clone all parents with 'data-testid' attribute. while (parent) { const cloned = parent.cloneNode(); cloned.appendChild(child); - child = cloned; + child = cloned as DocumentFragment; - parent = parent.parentNode.closest(`[${tagAttribute}]`); + parent = (parent.parentNode as HTMLElement).closest(`[${tagAttribute}]`); } div.appendChild(child); @@ -96,58 +96,61 @@ const getHTMLOfSelection = () => { /** * Clears all attributes from dom elements - * @param {Object} dom htmlparser2 dom representation - * @param {Boolean} isChildOfEditorElement - * @returns {Object} htmlparser2 dom representation */ -const replaceNodes = (dom, isChildOfEditorElement) => { - let domName = dom.name; - let domChildren; - const domAttribs = {}; - let data; +const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => { + const domElement = dom as Element; + let domName = domElement.name; + let domChildren: Node[] = []; + const domAttribs = {} as Element['attribs']; + let data = ''; // Encoding HTML chars '< >' in the text, because any HTML will be removed in stripHTML method. - if (dom.type === 'text') { - data = Str.htmlEncode(dom.data); + if (dom.type.toString() === 'text') { + data = Str.htmlEncode((dom as DataNode).data); } // We are skipping elements which has html and body in data-testid, since ExpensiMark can't parse it. Also this data // has no meaning for us. - if (dom.attribs && dom.attribs[tagAttribute]) { - if (!elementsWillBeSkipped.includes(dom.attribs[tagAttribute])) { - domName = dom.attribs[tagAttribute]; + if (domElement.attribs?.[tagAttribute]) { + if (!elementsWillBeSkipped.includes(domElement.attribs[tagAttribute])) { + domName = domElement.attribs[tagAttribute]; } - } else if (dom.name === 'div' && dom.children.length === 1 && isChildOfEditorElement) { + } else if (domElement.name === 'div' && domElement.children.length === 1 && isChildOfEditorElement) { // We are excluding divs that are children of our editor element and have only one child to prevent // additional newlines from being added in the HTML to Markdown conversion process. - return replaceNodes(dom.children[0], isChildOfEditorElement); + return replaceNodes(domElement.children[0], isChildOfEditorElement); } // We need to preserve href attribute in order to copy links. - if (dom.attribs && dom.attribs.href) { - domAttribs.href = dom.attribs.href; + if (domElement.attribs?.href) { + domAttribs.href = domElement.attribs.href; + } + + if (domElement.children) { + domChildren = domElement.children.map((c) => replaceNodes(c, isChildOfEditorElement || !!domElement.attribs?.[tagAttribute])); } - if (dom.children) { - domChildren = _.map(dom.children, (c) => replaceNodes(c, isChildOfEditorElement || !_.isEmpty(dom.attribs && dom.attribs[tagAttribute]))); + if (data) { + return { + ...dom, + data, + } as DataNode; } return { ...dom, - data, name: domName, attribs: domAttribs, children: domChildren, - }; + } as Element; }; /** * Resolves the current selection to values and produces clean HTML. - * @returns {String} resolved selection in the HTML format */ -const getCurrentSelection = () => { +const getCurrentSelection = (): string => { const domRepresentation = parseDocument(getHTMLOfSelection()); - domRepresentation.children = _.map(domRepresentation.children, replaceNodes); + domRepresentation.children = domRepresentation.children.map((item) => replaceNodes(item, false)); // Newline characters need to be removed here because the HTML could contain both newlines and
tags, and when //
tags are converted later to markdown, it creates duplicate newline characters. This means that when the content From e608638391f557facb96497e942266de89461daf Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Wed, 20 Sep 2023 23:32:53 +0200 Subject: [PATCH 017/376] 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 018/376] 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 43adfe9e58fd0c7e78ec5edaef30be0d9ab8d01f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Thu, 21 Sep 2023 15:06:18 +0200 Subject: [PATCH 019/376] review changes --- src/libs/SelectionScraper/index.native.ts | 8 ++++++-- src/libs/SelectionScraper/index.ts | 7 ++++--- src/libs/SelectionScraper/types.ts | 3 +++ 3 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 src/libs/SelectionScraper/types.ts diff --git a/src/libs/SelectionScraper/index.native.ts b/src/libs/SelectionScraper/index.native.ts index 3872ece30b66..7712906f05e6 100644 --- a/src/libs/SelectionScraper/index.native.ts +++ b/src/libs/SelectionScraper/index.native.ts @@ -1,4 +1,8 @@ +import GetCurrentSelection from './types'; + +// This is a no-op function for native devices because they wouldn't be able to support Selection API like a website. +const getCurrentSelection: GetCurrentSelection = () => ''; + export default { - // This is a no-op function for native devices because they wouldn't be able to support Selection API like a website. - getCurrentSelection: () => '', + getCurrentSelection, }; diff --git a/src/libs/SelectionScraper/index.ts b/src/libs/SelectionScraper/index.ts index 79639cd0f28a..6660d5a394cb 100644 --- a/src/libs/SelectionScraper/index.ts +++ b/src/libs/SelectionScraper/index.ts @@ -3,6 +3,7 @@ import {parseDocument} from 'htmlparser2'; import Str from 'expensify-common/lib/str'; import {DataNode, Element, Node} from 'domhandler'; import CONST from '../../CONST'; +import GetCurrentSelection from './types'; const elementsWillBeSkipped = ['html', 'body']; const tagAttribute = 'data-testid'; @@ -44,7 +45,7 @@ const getHTMLOfSelection = (): string => { // If clonedSelection has no text content this data has no meaning to us. if (clonedSelection.textContent) { - let parent; + let parent: globalThis.Element | null = null; let child = clonedSelection; // If selection starts and ends within same text node we use its parentNode. This is because we can't @@ -101,7 +102,7 @@ const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => { const domElement = dom as Element; let domName = domElement.name; let domChildren: Node[] = []; - const domAttribs = {} as Element['attribs']; + const domAttribs: Element['attribs'] = {}; let data = ''; // Encoding HTML chars '< >' in the text, because any HTML will be removed in stripHTML method. @@ -148,7 +149,7 @@ const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => { /** * Resolves the current selection to values and produces clean HTML. */ -const getCurrentSelection = (): string => { +const getCurrentSelection: GetCurrentSelection = () => { const domRepresentation = parseDocument(getHTMLOfSelection()); domRepresentation.children = domRepresentation.children.map((item) => replaceNodes(item, false)); diff --git a/src/libs/SelectionScraper/types.ts b/src/libs/SelectionScraper/types.ts new file mode 100644 index 000000000000..d33338883dd4 --- /dev/null +++ b/src/libs/SelectionScraper/types.ts @@ -0,0 +1,3 @@ +type GetCurrentSelection = () => string; + +export default GetCurrentSelection; From 58f417f8039a4d6faab3d0ee6254582beada2fd3 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Fri, 22 Sep 2023 16:19:47 +0800 Subject: [PATCH 020/376] 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 6841a5407f3303696ebde4c00ea920215ca99109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Fri, 22 Sep 2023 11:40:35 +0200 Subject: [PATCH 021/376] add return comment --- src/libs/SelectionScraper/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/SelectionScraper/index.ts b/src/libs/SelectionScraper/index.ts index 6660d5a394cb..52fe69fcb75e 100644 --- a/src/libs/SelectionScraper/index.ts +++ b/src/libs/SelectionScraper/index.ts @@ -10,6 +10,7 @@ const tagAttribute = 'data-testid'; /** * Reads html of selection. If browser doesn't support Selection API, returns empty string. + * @returns HTML of selection as String */ const getHTMLOfSelection = (): string => { // If browser doesn't support Selection API, return an empty string. From 478559309312a717b3cb7e14f85187123f2f2608 Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Wed, 27 Sep 2023 11:35:25 +0800 Subject: [PATCH 022/376] 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 023/376] 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 024/376] 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 decc2982e22af9f257c2798a73b06ffb2a8e8f04 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Thu, 28 Sep 2023 12:16:41 +0800 Subject: [PATCH 025/376] run webpack dev on https --- config/webpack/webpack.dev.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/config/webpack/webpack.dev.js b/config/webpack/webpack.dev.js index 19999491395e..c8af82d1d855 100644 --- a/config/webpack/webpack.dev.js +++ b/config/webpack/webpack.dev.js @@ -1,8 +1,8 @@ const path = require('path'); const portfinder = require('portfinder'); -const {DefinePlugin} = require('webpack'); -const {merge} = require('webpack-merge'); -const {TimeAnalyticsPlugin} = require('time-analytics-webpack-plugin'); +const { DefinePlugin } = require('webpack'); +const { merge } = require('webpack-merge'); +const { TimeAnalyticsPlugin } = require('time-analytics-webpack-plugin'); const getCommonConfig = require('./webpack.common'); const BASE_PORT = 8082; @@ -13,20 +13,20 @@ const BASE_PORT = 8082; * @returns {Configuration} */ module.exports = (env = {}) => - portfinder.getPortPromise({port: BASE_PORT}).then((port) => { + portfinder.getPortPromise({ port: BASE_PORT }).then((port) => { // Check if the USE_WEB_PROXY variable has been provided // and rewrite any requests to the local proxy server const proxySettings = process.env.USE_WEB_PROXY === 'false' ? {} : { - proxy: { - '/api': 'http://[::1]:9000', - '/staging': 'http://[::1]:9000', - '/chat-attachments': 'http://[::1]:9000', - '/receipts': 'http://[::1]:9000', - }, - }; + proxy: { + '/api': 'http://[::1]:9000', + '/staging': 'http://[::1]:9000', + '/chat-attachments': 'http://[::1]:9000', + '/receipts': 'http://[::1]:9000', + }, + }; const baseConfig = getCommonConfig(env); @@ -44,6 +44,7 @@ module.exports = (env = {}) => ...proxySettings, historyApiFallback: true, port, + https: true, }, plugins: [ new DefinePlugin({ From 0b7a83e8edd8e44971226f86f55c81140b20fc76 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Thu, 28 Sep 2023 18:22:10 +0800 Subject: [PATCH 026/376] 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 b0f3f2371e557b73ce96b51488410ff5300014df Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Fri, 29 Sep 2023 10:38:38 +0800 Subject: [PATCH 027/376] add task to generate certificates --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index d013caa1c402..f7f329363145 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,8 @@ "test:e2e": "node tests/e2e/testRunner.js --development", "gh-actions-unused-styles": "./.github/scripts/findUnusedKeys.sh", "workflow-test": "./workflow_tests/scripts/runWorkflowTests.sh", - "workflow-test:generate": "node workflow_tests/utils/preGenerateTest.js" + "workflow-test:generate": "node workflow_tests/utils/preGenerateTest.js", + "setup-https": "mkcert -install && mkcert -cert-file config/webpack/certificate.pem -key-file config/webpack/key.pem new.expensify.com.dev localhost 127.0.0.1" }, "dependencies": { "@expensify/react-native-web": "0.18.15", From 325213a4d9b48095682c3c6b30d7a7fbee39910f Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Fri, 29 Sep 2023 10:39:11 +0800 Subject: [PATCH 028/376] use generated certs in webpack dev --- config/webpack/webpack.dev.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config/webpack/webpack.dev.js b/config/webpack/webpack.dev.js index c8af82d1d855..5c705257ab2a 100644 --- a/config/webpack/webpack.dev.js +++ b/config/webpack/webpack.dev.js @@ -44,7 +44,10 @@ module.exports = (env = {}) => ...proxySettings, historyApiFallback: true, port, - https: true, + https: { + key: path.join(__dirname, 'key.pem'), + cert: path.join(__dirname, 'certificate.pem') + } }, plugins: [ new DefinePlugin({ From c46d0fa0d3ed1ff08b762416be6d09659e612e91 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Fri, 29 Sep 2023 10:39:59 +0800 Subject: [PATCH 029/376] update gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index d3b4daac04d7..335efdc5586a 100644 --- a/.gitignore +++ b/.gitignore @@ -111,3 +111,6 @@ tsconfig.tsbuildinfo # Yalc .yalc yalc.lock + +# Local https certificate/key +config/webpack/*.pem From 0140258f2a40fe513c40d79abb644f39ed9aff5b Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Fri, 29 Sep 2023 12:07:45 +0800 Subject: [PATCH 030/376] add mkcert instructions --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index fce7cee8dcdd..537f0d0101ad 100644 --- a/README.md +++ b/README.md @@ -34,12 +34,18 @@ These instructions should get you set up ready to work on New Expensify 🙌 1. Install `nvm` then `node` & `npm`: `brew install nvm && nvm install` 2. Install `watchman`: `brew install watchman` 3. Install dependencies: `npm install` +4. Install `mkcert`: `brew install mkcert` followed by `npm run setup-https`. If you're using another OS, follow the instructions [here](https://github.com/FiloSottile/mkcert?tab=readme-ov-file#installation). You can use any IDE or code editing tool for developing on any platform. Use your favorite! ## Recommended `node` setup In order to have more consistent builds, we use a strict `node` and `npm` version as defined in the `package.json` `engines` field and `.nvmrc` file. `npm install` will fail if you do not use the version defined, so it is recommended to install `node` via `nvm` for easy node version management. Automatic `node` version switching can be installed for [`zsh`](https://github.com/nvm-sh/nvm#zsh) or [`bash`](https://github.com/nvm-sh/nvm#bash) using `nvm`. +## Configuring HTTPS +The webpack development server now uses https. If you're using a mac, you can simply run `npm run setup-https`. + +If you're using another operating system, you will need to ensure `mkcert` is installed, and then follow the instructions in the repository to generate certificates valid for `new.expesify.com.dev` and `localhost`. The certificate should be named `certificate.pem` and the key should be named `key.pem`. They should be placed in `config/webpack`. + ## Running the web app 🕸 * To run the **development web app**: `npm run web` * Changes applied to Javascript will be applied automatically via WebPack as configured in `webpack.dev.js` From 34b573291053ef606ff1e346fb825518190d2d34 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Fri, 29 Sep 2023 12:08:02 +0800 Subject: [PATCH 031/376] prettier --- config/webpack/webpack.dev.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/config/webpack/webpack.dev.js b/config/webpack/webpack.dev.js index 5c705257ab2a..ac7696293b76 100644 --- a/config/webpack/webpack.dev.js +++ b/config/webpack/webpack.dev.js @@ -1,8 +1,8 @@ const path = require('path'); const portfinder = require('portfinder'); -const { DefinePlugin } = require('webpack'); -const { merge } = require('webpack-merge'); -const { TimeAnalyticsPlugin } = require('time-analytics-webpack-plugin'); +const {DefinePlugin} = require('webpack'); +const {merge} = require('webpack-merge'); +const {TimeAnalyticsPlugin} = require('time-analytics-webpack-plugin'); const getCommonConfig = require('./webpack.common'); const BASE_PORT = 8082; @@ -13,20 +13,20 @@ const BASE_PORT = 8082; * @returns {Configuration} */ module.exports = (env = {}) => - portfinder.getPortPromise({ port: BASE_PORT }).then((port) => { + portfinder.getPortPromise({port: BASE_PORT}).then((port) => { // Check if the USE_WEB_PROXY variable has been provided // and rewrite any requests to the local proxy server const proxySettings = process.env.USE_WEB_PROXY === 'false' ? {} : { - proxy: { - '/api': 'http://[::1]:9000', - '/staging': 'http://[::1]:9000', - '/chat-attachments': 'http://[::1]:9000', - '/receipts': 'http://[::1]:9000', - }, - }; + proxy: { + '/api': 'http://[::1]:9000', + '/staging': 'http://[::1]:9000', + '/chat-attachments': 'http://[::1]:9000', + '/receipts': 'http://[::1]:9000', + }, + }; const baseConfig = getCommonConfig(env); @@ -46,8 +46,8 @@ module.exports = (env = {}) => port, https: { key: path.join(__dirname, 'key.pem'), - cert: path.join(__dirname, 'certificate.pem') - } + cert: path.join(__dirname, 'certificate.pem'), + }, }, plugins: [ new DefinePlugin({ From 2d80cbd1ea5044a0e2a54334a13a7d51fa69bb68 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Fri, 29 Sep 2023 16:22:42 +0800 Subject: [PATCH 032/376] change host to new.expensify.com.dev --- README.md | 4 ++++ config/webpack/webpack.dev.js | 1 + 2 files changed, 5 insertions(+) diff --git a/README.md b/README.md index 537f0d0101ad..6b5f96de6b72 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,10 @@ These instructions should get you set up ready to work on New Expensify 🙌 2. Install `watchman`: `brew install watchman` 3. Install dependencies: `npm install` 4. Install `mkcert`: `brew install mkcert` followed by `npm run setup-https`. If you're using another OS, follow the instructions [here](https://github.com/FiloSottile/mkcert?tab=readme-ov-file#installation). +5. Create a host entry in your local hosts file, `/etc/hosts` for new.expensify.com.dev pointing to localhost: +``` +127.0.0.1 new.expensify.com.dev +``` You can use any IDE or code editing tool for developing on any platform. Use your favorite! diff --git a/config/webpack/webpack.dev.js b/config/webpack/webpack.dev.js index ac7696293b76..1e7074b16277 100644 --- a/config/webpack/webpack.dev.js +++ b/config/webpack/webpack.dev.js @@ -44,6 +44,7 @@ module.exports = (env = {}) => ...proxySettings, historyApiFallback: true, port, + host: 'new.expensify.com.dev', https: { key: path.join(__dirname, 'key.pem'), cert: path.join(__dirname, 'certificate.pem'), From d7b9c815938dbdf97302e80895da2e6210b92d77 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Fri, 29 Sep 2023 11:39:56 +0200 Subject: [PATCH 033/376] Migrate environment lib to TS --- .../{Environment.js => Environment.ts} | 25 ++++++------------- .../{index.android.js => index.android.ts} | 11 +++++--- .../{index.ios.js => index.ios.ts} | 6 ++--- .../betaChecker/{index.js => index.ts} | 4 +-- .../{index.native.js => index.native.ts} | 11 +++----- .../getEnvironment/{index.js => index.ts} | 7 ++---- 6 files changed, 24 insertions(+), 40 deletions(-) rename src/libs/Environment/{Environment.js => Environment.ts} (69%) rename src/libs/Environment/betaChecker/{index.android.js => index.android.ts} (89%) rename src/libs/Environment/betaChecker/{index.ios.js => index.ios.ts} (67%) rename src/libs/Environment/betaChecker/{index.js => index.ts} (70%) rename src/libs/Environment/getEnvironment/{index.native.js => index.native.ts} (75%) rename src/libs/Environment/getEnvironment/{index.js => index.ts} (53%) diff --git a/src/libs/Environment/Environment.js b/src/libs/Environment/Environment.ts similarity index 69% rename from src/libs/Environment/Environment.js rename to src/libs/Environment/Environment.ts index c039b49d33aa..f14e21eac90d 100644 --- a/src/libs/Environment/Environment.js +++ b/src/libs/Environment/Environment.ts @@ -1,5 +1,4 @@ import Config from 'react-native-config'; -import lodashGet from 'lodash/get'; import CONST from '../../CONST'; import getEnvironment from './getEnvironment'; import CONFIG from '../../CONFIG'; @@ -20,40 +19,32 @@ const OLDDOT_ENVIRONMENT_URLS = { /** * Are we running the app in development? - * - * @return {boolean} */ -function isDevelopment() { - return lodashGet(Config, 'ENVIRONMENT', CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.DEV; +function isDevelopment(): boolean { + return (Config?.ENVIRONMENT ?? CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.DEV; } /** * Are we running an internal test build? - * - * @return {boolean} */ -function isInternalTestBuild() { - return lodashGet(Config, 'ENVIRONMENT', CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.ADHOC && lodashGet(Config, 'PULL_REQUEST_NUMBER', ''); +function isInternalTestBuild(): boolean { + return !!((Config?.ENVIRONMENT ?? CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.ADHOC && (Config?.PULL_REQUEST_NUMBER ?? '')); } /** * Get the URL based on the environment we are in - * - * @returns {Promise} */ -function getEnvironmentURL() { +function getEnvironmentURL(): Promise { return new Promise((resolve) => { - getEnvironment().then((environment) => resolve(ENVIRONMENT_URLS[environment])); + getEnvironment().then((environment) => resolve(ENVIRONMENT_URLS[environment as keyof typeof ENVIRONMENT_URLS])); }); } /** * Get the corresponding oldDot URL based on the environment we are in - * - * @returns {Promise} */ -function getOldDotEnvironmentURL() { - return getEnvironment().then((environment) => OLDDOT_ENVIRONMENT_URLS[environment]); +function getOldDotEnvironmentURL(): Promise { + return getEnvironment().then((environment) => OLDDOT_ENVIRONMENT_URLS[environment as keyof typeof OLDDOT_ENVIRONMENT_URLS]); } export {getEnvironment, isInternalTestBuild, isDevelopment, getEnvironmentURL, getOldDotEnvironmentURL}; diff --git a/src/libs/Environment/betaChecker/index.android.js b/src/libs/Environment/betaChecker/index.android.ts similarity index 89% rename from src/libs/Environment/betaChecker/index.android.js rename to src/libs/Environment/betaChecker/index.android.ts index e74648973c34..8b3afed3b2f3 100644 --- a/src/libs/Environment/betaChecker/index.android.js +++ b/src/libs/Environment/betaChecker/index.android.ts @@ -8,15 +8,18 @@ import * as AppUpdate from '../../actions/AppUpdate'; let isLastSavedBeta = false; Onyx.connect({ key: ONYXKEYS.IS_BETA, - callback: (value) => (isLastSavedBeta = value), + callback: (value) => { + if (!value) { + return; + } + isLastSavedBeta = value; + }, }); /** * Check the GitHub releases to see if the current build is a beta build or production build - * - * @returns {Promise} */ -function isBetaBuild() { +function isBetaBuild(): Promise { return new Promise((resolve) => { fetch(CONST.GITHUB_RELEASE_URL) .then((res) => res.json()) diff --git a/src/libs/Environment/betaChecker/index.ios.js b/src/libs/Environment/betaChecker/index.ios.ts similarity index 67% rename from src/libs/Environment/betaChecker/index.ios.js rename to src/libs/Environment/betaChecker/index.ios.ts index 65b3ea935b04..2d6079e30a1c 100644 --- a/src/libs/Environment/betaChecker/index.ios.js +++ b/src/libs/Environment/betaChecker/index.ios.ts @@ -2,12 +2,10 @@ import {NativeModules} from 'react-native'; /** * Check to see if the build is staging (TestFlight) or production - * - * @returns {Promise} */ -function isBetaBuild() { +function isBetaBuild(): Promise { return new Promise((resolve) => { - NativeModules.EnvironmentChecker.isBeta().then((isBeta) => { + NativeModules.EnvironmentChecker.isBeta().then((isBeta: boolean) => { resolve(isBeta); }); }); diff --git a/src/libs/Environment/betaChecker/index.js b/src/libs/Environment/betaChecker/index.ts similarity index 70% rename from src/libs/Environment/betaChecker/index.js rename to src/libs/Environment/betaChecker/index.ts index 9d0d4af5741b..cda7c297624f 100644 --- a/src/libs/Environment/betaChecker/index.js +++ b/src/libs/Environment/betaChecker/index.ts @@ -1,9 +1,7 @@ /** * There's no beta build in non native - * - * @returns {Promise} */ -function isBetaBuild() { +function isBetaBuild(): Promise { return Promise.resolve(false); } diff --git a/src/libs/Environment/getEnvironment/index.native.js b/src/libs/Environment/getEnvironment/index.native.ts similarity index 75% rename from src/libs/Environment/getEnvironment/index.native.js rename to src/libs/Environment/getEnvironment/index.native.ts index 73014c4beffb..1e3333c6a881 100644 --- a/src/libs/Environment/getEnvironment/index.native.js +++ b/src/libs/Environment/getEnvironment/index.native.ts @@ -1,28 +1,25 @@ -import lodashGet from 'lodash/get'; import Config from 'react-native-config'; import betaChecker from '../betaChecker'; import CONST from '../../../CONST'; -let environment = null; +let environment: string | null = null; /** * Returns a promise that resolves with the current environment string value - * - * @returns {Promise} */ -function getEnvironment() { +function getEnvironment(): Promise { return new Promise((resolve) => { // If we've already set the environment, use the current value if (environment) { return resolve(environment); } - if (lodashGet(Config, 'ENVIRONMENT', CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.DEV) { + if ((Config?.ENVIRONMENT ?? CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.DEV) { environment = CONST.ENVIRONMENT.DEV; return resolve(environment); } - if (lodashGet(Config, 'ENVIRONMENT', CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.ADHOC) { + if ((Config?.ENVIRONMENT ?? CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.ADHOC) { environment = CONST.ENVIRONMENT.ADHOC; return resolve(environment); } diff --git a/src/libs/Environment/getEnvironment/index.js b/src/libs/Environment/getEnvironment/index.ts similarity index 53% rename from src/libs/Environment/getEnvironment/index.js rename to src/libs/Environment/getEnvironment/index.ts index a987678d6a6e..2c27be5fc471 100644 --- a/src/libs/Environment/getEnvironment/index.js +++ b/src/libs/Environment/getEnvironment/index.ts @@ -1,14 +1,11 @@ -import lodashGet from 'lodash/get'; import Config from 'react-native-config'; import CONST from '../../../CONST'; /** * Returns a promise that resolves with the current environment string value - * - * @returns {Promise} */ -function getEnvironment() { - return Promise.resolve(lodashGet(Config, 'ENVIRONMENT', CONST.ENVIRONMENT.DEV)); +function getEnvironment(): Promise { + return Promise.resolve(Config?.ENVIRONMENT ?? CONST.ENVIRONMENT.DEV); } export default getEnvironment; From 8113ebf244d214dc2e9d9d02ebb8d93fb88377a1 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Fri, 29 Sep 2023 12:24:07 +0200 Subject: [PATCH 034/376] 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 035/376] 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 036/376] 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 037/376] [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 038/376] 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 59a32831104a465c7386dfc1d5090af93f61570a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Mon, 2 Oct 2023 16:24:36 +0200 Subject: [PATCH 039/376] review changes --- src/libs/SelectionScraper/index.ts | 36 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/libs/SelectionScraper/index.ts b/src/libs/SelectionScraper/index.ts index 52fe69fcb75e..4b679734b3a0 100644 --- a/src/libs/SelectionScraper/index.ts +++ b/src/libs/SelectionScraper/index.ts @@ -14,9 +14,11 @@ const tagAttribute = 'data-testid'; */ const getHTMLOfSelection = (): string => { // If browser doesn't support Selection API, return an empty string. + if (!window.getSelection) { + return ''; + } const selection = window.getSelection(); - - if (!selection || !window.getSelection) { + if (!selection) { return ''; } @@ -66,8 +68,8 @@ const getHTMLOfSelection = (): string => { // and finally commonAncestorContainer.parentNode.closest('data-testid') is targeted dom. if (range.commonAncestorContainer instanceof HTMLElement) { parent = range.commonAncestorContainer.closest(`[${tagAttribute}]`); - } else if (range.commonAncestorContainer.parentNode) { - parent = (range.commonAncestorContainer.parentNode as HTMLElement).closest(`[${tagAttribute}]`); + } else { + parent = (range.commonAncestorContainer.parentNode as HTMLElement | null)?.closest(`[${tagAttribute}]`) ?? null; } // Keep traversing up to clone all parents with 'data-testid' attribute. @@ -76,7 +78,7 @@ const getHTMLOfSelection = (): string => { cloned.appendChild(child); child = cloned as DocumentFragment; - parent = (parent.parentNode as HTMLElement).closest(`[${tagAttribute}]`); + parent = (parent.parentNode as HTMLElement | null)?.closest(`[${tagAttribute}]`) ?? null; } div.appendChild(child); @@ -100,16 +102,21 @@ const getHTMLOfSelection = (): string => { * Clears all attributes from dom elements */ const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => { + // Encoding HTML chars '< >' in the text, because any HTML will be removed in stripHTML method. + const domDataNode = dom as DataNode; + let data = ''; + if (dom.type.toString() === 'text' && domDataNode.data) { + data = Str.htmlEncode(domDataNode.data); + return { + ...dom, + data, + } as DataNode; + } + const domElement = dom as Element; let domName = domElement.name; let domChildren: Node[] = []; const domAttribs: Element['attribs'] = {}; - let data = ''; - - // Encoding HTML chars '< >' in the text, because any HTML will be removed in stripHTML method. - if (dom.type.toString() === 'text') { - data = Str.htmlEncode((dom as DataNode).data); - } // We are skipping elements which has html and body in data-testid, since ExpensiMark can't parse it. Also this data // has no meaning for us. @@ -132,13 +139,6 @@ const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => { domChildren = domElement.children.map((c) => replaceNodes(c, isChildOfEditorElement || !!domElement.attribs?.[tagAttribute])); } - if (data) { - return { - ...dom, - data, - } as DataNode; - } - return { ...dom, name: domName, From 4fe4ff020c80b21bcca6b00131fd2e553fb55075 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Tue, 3 Oct 2023 00:17:04 +0700 Subject: [PATCH 040/376] 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 041/376] 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 a269b40cf59459fb5cce0edf5aba6c3f89c3d122 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Tue, 3 Oct 2023 07:44:02 -0400 Subject: [PATCH 042/376] update help site to use new https dev --- docs/_includes/CONST.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_includes/CONST.html b/docs/_includes/CONST.html index 4b87f87931d5..af6a14a78331 100644 --- a/docs/_includes/CONST.html +++ b/docs/_includes/CONST.html @@ -1,7 +1,7 @@ {% if jekyll.environment == "production" %} {% assign MAIN_SITE_URL = "https://new.expensify.com" %} {% else %} - {% assign MAIN_SITE_URL = "http://localhost:8082" %} + {% assign MAIN_SITE_URL = "https://new.expensify.com.dev:8082" %} {% endif %} {% capture CONCIERGE_CHAT_URL %}{{MAIN_SITE_URL}}/concierge{% endcapture %} From 94107850a394e68f2edf933013535fcdc6f3581e Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Tue, 3 Oct 2023 07:44:21 -0400 Subject: [PATCH 043/376] add https url to nav --- src/libs/Navigation/linkingConfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index 533dbf51633a..1e695fa9dcee 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -4,7 +4,7 @@ import CONST from '../../CONST'; import NAVIGATORS from '../../NAVIGATORS'; export default { - prefixes: ['new-expensify://', 'https://www.expensify.cash', 'https://staging.expensify.cash', 'http://localhost', CONST.NEW_EXPENSIFY_URL, CONST.STAGING_NEW_EXPENSIFY_URL], + prefixes: ['new-expensify://', 'https://www.expensify.cash', 'https://staging.expensify.cash', 'https://new.expensify.com.dev', CONST.NEW_EXPENSIFY_URL, CONST.STAGING_NEW_EXPENSIFY_URL], config: { initialRouteName: SCREENS.HOME, screens: { From e8ebe3bc9ffa24480e5417c7c817b47dcf94ee3a Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Tue, 3 Oct 2023 07:44:34 -0400 Subject: [PATCH 044/376] change additional refs to https dev --- contributingGuides/APPLE_GOOGLE_SIGNIN.md | 4 ++-- desktop/main.js | 4 ++-- desktop/start.js | 2 +- src/CONST.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contributingGuides/APPLE_GOOGLE_SIGNIN.md b/contributingGuides/APPLE_GOOGLE_SIGNIN.md index 9032a99dfbbd..3a1feac073db 100644 --- a/contributingGuides/APPLE_GOOGLE_SIGNIN.md +++ b/contributingGuides/APPLE_GOOGLE_SIGNIN.md @@ -161,10 +161,10 @@ function beginAppleSignIn(idToken) { You can use any SSH tunneling service that allows you to configure custom subdomains so that we have a consistent address to use. We'll use ngrok in these examples, but ngrok requires a paid account for this. If you need a free option, try serveo.net. -After you've set ngrok up to be able to run on your machine (requires configuring a key with the command line tool, instructions provided by the ngrok website after you create an account), test hosting the web app on a custom subdomain. This example assumes the development web app is running at `localhost:8082`: +After you've set ngrok up to be able to run on your machine (requires configuring a key with the command line tool, instructions provided by the ngrok website after you create an account), test hosting the web app on a custom subdomain. This example assumes the development web app is running at `new.expensify.com.dev:8082`: ``` -ngrok http 8082 --host-header="localhost:8082" --subdomain=mysubdomain +ngrok http 8082 --host-header="new.expensify.com.dev:8082" --subdomain=mysubdomain ``` The `--host-header` flag is there to avoid webpack errors with header validation. In addition, add `allowedHosts: 'all'` to the dev server config in `webpack.dev.js`: diff --git a/desktop/main.js b/desktop/main.js index 5e184d529afd..4b43ff128f65 100644 --- a/desktop/main.js +++ b/desktop/main.js @@ -85,7 +85,7 @@ _.assign(console, log.functions); // until it detects that it has been upgraded to the correct version. const EXPECTED_UPDATE_VERSION_FLAG = '--expected-update-version'; -const APP_DOMAIN = __DEV__ ? `http://localhost:${port}` : 'app://-'; +const APP_DOMAIN = __DEV__ ? `https://new.expensify.com.dev:${port}` : 'app://-'; let expectedUpdateVersion; for (let i = 0; i < process.argv.length; i++) { @@ -221,7 +221,7 @@ const mainWindow = () => { let deeplinkUrl; let browserWindow; - const loadURL = __DEV__ ? (win) => win.loadURL(`http://localhost:${port}`) : serve({directory: `${__dirname}/www`}); + const loadURL = __DEV__ ? (win) => win.loadURL(`https://new.expensify.com.dev:${port}`) : serve({directory: `${__dirname}/www`}); // Prod and staging set the icon in the electron-builder config, so only update it here for dev if (__DEV__) { diff --git a/desktop/start.js b/desktop/start.js index d9ec59b71c83..a9bb5a2d588d 100644 --- a/desktop/start.js +++ b/desktop/start.js @@ -32,7 +32,7 @@ portfinder env, }, { - command: `wait-port localhost:${port} && npx electronmon ./desktop/dev.js`, + command: `wait-port new.expensify.com.dev:${port} && npx electronmon ./desktop/dev.js`, name: 'Electron', prefixColor: 'cyan.dim', env, diff --git a/src/CONST.ts b/src/CONST.ts index dbe47c6ed1a7..c8e6fcc65e20 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -448,7 +448,7 @@ const CONST = { ONFIDO_TERMS_OF_SERVICE_URL: 'https://onfido.com/terms-of-service/', // Use Environment.getEnvironmentURL to get the complete URL with port number - DEV_NEW_EXPENSIFY_URL: 'http://localhost:', + DEV_NEW_EXPENSIFY_URL: 'https://new.expensify.com.dev:', SIGN_IN_FORM_WIDTH: 300, From bbcdbd146d234e201c05f74eaf40842b5a8c3811 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 3 Oct 2023 19:17:51 +0200 Subject: [PATCH 045/376] 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 89173ee79f3c10f48de8d5449e45b843bf2019aa Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 3 Oct 2023 20:36:35 +0200 Subject: [PATCH 046/376] Create types for environment urls keys --- src/libs/Environment/Environment.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libs/Environment/Environment.ts b/src/libs/Environment/Environment.ts index f14e21eac90d..b201fd5be439 100644 --- a/src/libs/Environment/Environment.ts +++ b/src/libs/Environment/Environment.ts @@ -17,6 +17,9 @@ const OLDDOT_ENVIRONMENT_URLS = { [CONST.ENVIRONMENT.ADHOC]: CONST.STAGING_EXPENSIFY_URL, }; +type EnvironmentUrlsKeys = keyof typeof ENVIRONMENT_URLS; +type OldDotEnvironmentUrlsKeys = keyof typeof OLDDOT_ENVIRONMENT_URLS; + /** * Are we running the app in development? */ @@ -36,7 +39,7 @@ function isInternalTestBuild(): boolean { */ function getEnvironmentURL(): Promise { return new Promise((resolve) => { - getEnvironment().then((environment) => resolve(ENVIRONMENT_URLS[environment as keyof typeof ENVIRONMENT_URLS])); + getEnvironment().then((environment) => resolve(ENVIRONMENT_URLS[environment as EnvironmentUrlsKeys])); }); } @@ -44,7 +47,7 @@ function getEnvironmentURL(): Promise { * Get the corresponding oldDot URL based on the environment we are in */ function getOldDotEnvironmentURL(): Promise { - return getEnvironment().then((environment) => OLDDOT_ENVIRONMENT_URLS[environment as keyof typeof OLDDOT_ENVIRONMENT_URLS]); + return getEnvironment().then((environment) => OLDDOT_ENVIRONMENT_URLS[environment as OldDotEnvironmentUrlsKeys]); } export {getEnvironment, isInternalTestBuild, isDevelopment, getEnvironmentURL, getOldDotEnvironmentURL}; From 0be46040ab60b2cd4b3ed58ff106e0dba98d3a4f Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Tue, 3 Oct 2023 21:52:50 -0400 Subject: [PATCH 047/376] fix unit test --- tests/unit/ReportUtilsTest.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/ReportUtilsTest.js b/tests/unit/ReportUtilsTest.js index 24397a04a0e9..9ddf9b93f8d0 100644 --- a/tests/unit/ReportUtilsTest.js +++ b/tests/unit/ReportUtilsTest.js @@ -503,8 +503,7 @@ describe('ReportUtils', () => { expect(ReportUtils.getReportIDFromLink('new-expensify://r/75431276')).toBe('75431276'); expect(ReportUtils.getReportIDFromLink('https://www.expensify.cash/r/75431276')).toBe('75431276'); expect(ReportUtils.getReportIDFromLink('https://staging.new.expensify.com/r/75431276')).toBe('75431276'); - expect(ReportUtils.getReportIDFromLink('http://localhost/r/75431276')).toBe('75431276'); - expect(ReportUtils.getReportIDFromLink('http://localhost:8080/r/75431276')).toBe('75431276'); + expect(ReportUtils.getReportIDFromLink('https://new.expensify.com.dev/r/75431276')).toBe('75431276'); expect(ReportUtils.getReportIDFromLink('https://staging.expensify.cash/r/75431276')).toBe('75431276'); expect(ReportUtils.getReportIDFromLink('https://new.expensify.com/r/75431276')).toBe('75431276'); }); From 9b266fae39474f474ebd21dd08bc38fb342999a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Wed, 4 Oct 2023 10:47:09 +0200 Subject: [PATCH 048/376] fix replaceNodes function --- src/libs/SelectionScraper/index.ts | 59 +++++++++++++++--------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/src/libs/SelectionScraper/index.ts b/src/libs/SelectionScraper/index.ts index 4b679734b3a0..d865cd75e850 100644 --- a/src/libs/SelectionScraper/index.ts +++ b/src/libs/SelectionScraper/index.ts @@ -100,51 +100,50 @@ const getHTMLOfSelection = (): string => { /** * Clears all attributes from dom elements + * @param dom - dom htmlparser2 dom representation */ const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => { - // Encoding HTML chars '< >' in the text, because any HTML will be removed in stripHTML method. - const domDataNode = dom as DataNode; - let data = ''; - if (dom.type.toString() === 'text' && domDataNode.data) { - data = Str.htmlEncode(domDataNode.data); - return { - ...dom, - data, - } as DataNode; - } - - const domElement = dom as Element; - let domName = domElement.name; + let domName; let domChildren: Node[] = []; const domAttribs: Element['attribs'] = {}; + let data = ''; - // We are skipping elements which has html and body in data-testid, since ExpensiMark can't parse it. Also this data - // has no meaning for us. - if (domElement.attribs?.[tagAttribute]) { - if (!elementsWillBeSkipped.includes(domElement.attribs[tagAttribute])) { - domName = domElement.attribs[tagAttribute]; - } - } else if (domElement.name === 'div' && domElement.children.length === 1 && isChildOfEditorElement) { - // We are excluding divs that are children of our editor element and have only one child to prevent - // additional newlines from being added in the HTML to Markdown conversion process. - return replaceNodes(domElement.children[0], isChildOfEditorElement); + // Encoding HTML chars '< >' in the text, because any HTML will be removed in stripHTML method. + if (dom.type.toString() === 'text' && dom instanceof DataNode) { + data = Str.htmlEncode(dom.data); } - // We need to preserve href attribute in order to copy links. - if (domElement.attribs?.href) { - domAttribs.href = domElement.attribs.href; - } + if (dom instanceof Element) { + domName = dom.name; + // We are skipping elements which has html and body in data-testid, since ExpensiMark can't parse it. Also this data + // has no meaning for us. + if (dom.attribs?.[tagAttribute]) { + if (!elementsWillBeSkipped.includes(dom.attribs[tagAttribute])) { + domName = dom.attribs[tagAttribute]; + } + } else if (dom.name === 'div' && dom.children.length === 1 && isChildOfEditorElement) { + // We are excluding divs that are children of our editor element and have only one child to prevent + // additional newlines from being added in the HTML to Markdown conversion process. + return replaceNodes(dom.children[0], isChildOfEditorElement); + } - if (domElement.children) { - domChildren = domElement.children.map((c) => replaceNodes(c, isChildOfEditorElement || !!domElement.attribs?.[tagAttribute])); + // We need to preserve href attribute in order to copy links. + if (dom.attribs?.href) { + domAttribs.href = dom.attribs.href; + } + + if (dom.children) { + domChildren = dom.children.map((c) => replaceNodes(c, isChildOfEditorElement || !!dom.attribs?.[tagAttribute])); + } } return { ...dom, + data, name: domName, attribs: domAttribs, children: domChildren, - } as Element; + } as unknown as Node; }; /** From 772975082c0725c5ac88f20065adb437944ca3b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Wed, 4 Oct 2023 16:21:49 +0200 Subject: [PATCH 049/376] change replaceNodes return object type --- src/libs/SelectionScraper/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/SelectionScraper/index.ts b/src/libs/SelectionScraper/index.ts index d865cd75e850..0368087b5f38 100644 --- a/src/libs/SelectionScraper/index.ts +++ b/src/libs/SelectionScraper/index.ts @@ -143,7 +143,7 @@ const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => { name: domName, attribs: domAttribs, children: domChildren, - } as unknown as Node; + } as Element & DataNode; }; /** From 392e320554727161916e4547eb24afd7567a5641 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Fri, 6 Oct 2023 22:32:14 +0700 Subject: [PATCH 050/376] 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 051/376] 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 f9717fd85f6239df5df5c5b4d446293eb5be0899 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sat, 7 Oct 2023 11:43:03 +0530 Subject: [PATCH 052/376] fixed offline jumping behaviour --- src/components/AddressSearch/index.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index fe220d442674..84e009fdc081 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -294,12 +294,14 @@ function AddressSearch(props) { ) } listLoaderComponent={ - - - + props.network.isOffline ? null : ( + + + + ) } renderHeaderComponent={() => !props.value && From 3ebd5f97bdd31d5689ac8a9f75a577c70d1808a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Mon, 9 Oct 2023 11:40:09 +0200 Subject: [PATCH 053/376] fix replaceNodes condition --- src/libs/SelectionScraper/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/SelectionScraper/index.ts b/src/libs/SelectionScraper/index.ts index 0368087b5f38..abe9a235fca8 100644 --- a/src/libs/SelectionScraper/index.ts +++ b/src/libs/SelectionScraper/index.ts @@ -111,9 +111,7 @@ const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => { // Encoding HTML chars '< >' in the text, because any HTML will be removed in stripHTML method. if (dom.type.toString() === 'text' && dom instanceof DataNode) { data = Str.htmlEncode(dom.data); - } - - if (dom instanceof Element) { + } else if (dom instanceof Element) { domName = dom.name; // We are skipping elements which has html and body in data-testid, since ExpensiMark can't parse it. Also this data // has no meaning for us. @@ -135,6 +133,8 @@ const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => { if (dom.children) { domChildren = dom.children.map((c) => replaceNodes(c, isChildOfEditorElement || !!dom.attribs?.[tagAttribute])); } + } else { + throw new Error(`Unknown dom type: ${dom.type}`); } return { From 16608759411ace3d75f90f7f19ccfb5cca1282bc Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 9 Oct 2023 22:00:50 +0530 Subject: [PATCH 054/376] fix package upstream --- package-lock.json | 14 +++++++------- package.json | 2 +- src/components/AddressSearch/index.js | 14 ++++++-------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index ddebbe8a3832..9539536b7891 100644 --- a/package-lock.json +++ b/package-lock.json @@ -85,7 +85,7 @@ "react-native-fast-image": "^8.6.3", "react-native-fs": "^2.20.0", "react-native-gesture-handler": "2.12.0", - "react-native-google-places-autocomplete": "2.5.5", + "react-native-google-places-autocomplete": "2.5.6", "react-native-haptic-feedback": "^1.13.0", "react-native-image-pan-zoom": "^2.1.12", "react-native-image-picker": "^5.1.0", @@ -44589,9 +44589,9 @@ } }, "node_modules/react-native-google-places-autocomplete": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/react-native-google-places-autocomplete/-/react-native-google-places-autocomplete-2.5.5.tgz", - "integrity": "sha512-ypqaHYRifcY9q28HkZYExzHMF4Eul+mf3y4dlIlvBj3SgLI2FyrD1mdQoF8A7xwhOWctYs6PsVj3Mg71IVJTTw==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/react-native-google-places-autocomplete/-/react-native-google-places-autocomplete-2.5.6.tgz", + "integrity": "sha512-Dy7mFKyEoiNeWPLd7HUkrI/SzJYe7GST55FGtiXzXDoPs05LYHIOCPrT9qFE51COh5X8kgDKm+f7D5aMY/aMbg==", "dependencies": { "lodash.debounce": "^4.0.8", "prop-types": "^15.7.2", @@ -85211,9 +85211,9 @@ } }, "react-native-google-places-autocomplete": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/react-native-google-places-autocomplete/-/react-native-google-places-autocomplete-2.5.5.tgz", - "integrity": "sha512-ypqaHYRifcY9q28HkZYExzHMF4Eul+mf3y4dlIlvBj3SgLI2FyrD1mdQoF8A7xwhOWctYs6PsVj3Mg71IVJTTw==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/react-native-google-places-autocomplete/-/react-native-google-places-autocomplete-2.5.6.tgz", + "integrity": "sha512-Dy7mFKyEoiNeWPLd7HUkrI/SzJYe7GST55FGtiXzXDoPs05LYHIOCPrT9qFE51COh5X8kgDKm+f7D5aMY/aMbg==", "requires": { "lodash.debounce": "^4.0.8", "prop-types": "^15.7.2", diff --git a/package.json b/package.json index 9a3b9ed3af86..2b97739bc050 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "react-native-fast-image": "^8.6.3", "react-native-fs": "^2.20.0", "react-native-gesture-handler": "2.12.0", - "react-native-google-places-autocomplete": "2.5.5", + "react-native-google-places-autocomplete": "2.5.6", "react-native-haptic-feedback": "^1.13.0", "react-native-image-pan-zoom": "^2.1.12", "react-native-image-picker": "^5.1.0", diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 84e009fdc081..fe220d442674 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -294,14 +294,12 @@ function AddressSearch(props) { ) } listLoaderComponent={ - props.network.isOffline ? null : ( - - - - ) + + + } renderHeaderComponent={() => !props.value && From 3c41ef5638447698d902e9285a8505e5254d6b92 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Wed, 11 Oct 2023 11:43:12 +0200 Subject: [PATCH 055/376] 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 e2738d30b5e767e3568e4476fbc6438870c19607 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Wed, 11 Oct 2023 12:06:24 +0200 Subject: [PATCH 056/376] Remove assertion --- src/libs/Environment/Environment.ts | 7 ++----- src/libs/Environment/getEnvironment/index.native.ts | 5 +++-- src/libs/Environment/getEnvironment/index.ts | 5 +++-- src/libs/Environment/getEnvironment/types.ts | 6 ++++++ 4 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 src/libs/Environment/getEnvironment/types.ts diff --git a/src/libs/Environment/Environment.ts b/src/libs/Environment/Environment.ts index b201fd5be439..60b83e0a1dfe 100644 --- a/src/libs/Environment/Environment.ts +++ b/src/libs/Environment/Environment.ts @@ -17,9 +17,6 @@ const OLDDOT_ENVIRONMENT_URLS = { [CONST.ENVIRONMENT.ADHOC]: CONST.STAGING_EXPENSIFY_URL, }; -type EnvironmentUrlsKeys = keyof typeof ENVIRONMENT_URLS; -type OldDotEnvironmentUrlsKeys = keyof typeof OLDDOT_ENVIRONMENT_URLS; - /** * Are we running the app in development? */ @@ -39,7 +36,7 @@ function isInternalTestBuild(): boolean { */ function getEnvironmentURL(): Promise { return new Promise((resolve) => { - getEnvironment().then((environment) => resolve(ENVIRONMENT_URLS[environment as EnvironmentUrlsKeys])); + getEnvironment().then((environment) => resolve(ENVIRONMENT_URLS[environment])); }); } @@ -47,7 +44,7 @@ function getEnvironmentURL(): Promise { * Get the corresponding oldDot URL based on the environment we are in */ function getOldDotEnvironmentURL(): Promise { - return getEnvironment().then((environment) => OLDDOT_ENVIRONMENT_URLS[environment as OldDotEnvironmentUrlsKeys]); + return getEnvironment().then((environment) => OLDDOT_ENVIRONMENT_URLS[environment]); } export {getEnvironment, isInternalTestBuild, isDevelopment, getEnvironmentURL, getOldDotEnvironmentURL}; diff --git a/src/libs/Environment/getEnvironment/index.native.ts b/src/libs/Environment/getEnvironment/index.native.ts index 1e3333c6a881..0e3b943b1bd5 100644 --- a/src/libs/Environment/getEnvironment/index.native.ts +++ b/src/libs/Environment/getEnvironment/index.native.ts @@ -1,13 +1,14 @@ import Config from 'react-native-config'; import betaChecker from '../betaChecker'; import CONST from '../../../CONST'; +import Environment from './types'; -let environment: string | null = null; +let environment: Environment | null = null; /** * Returns a promise that resolves with the current environment string value */ -function getEnvironment(): Promise { +function getEnvironment(): Promise { return new Promise((resolve) => { // If we've already set the environment, use the current value if (environment) { diff --git a/src/libs/Environment/getEnvironment/index.ts b/src/libs/Environment/getEnvironment/index.ts index 2c27be5fc471..9a18a997eec6 100644 --- a/src/libs/Environment/getEnvironment/index.ts +++ b/src/libs/Environment/getEnvironment/index.ts @@ -1,11 +1,12 @@ import Config from 'react-native-config'; import CONST from '../../../CONST'; +import Environment from './types'; /** * Returns a promise that resolves with the current environment string value */ -function getEnvironment(): Promise { - return Promise.resolve(Config?.ENVIRONMENT ?? CONST.ENVIRONMENT.DEV); +function getEnvironment(): Promise { + return Promise.resolve((Config?.ENVIRONMENT as Environment) ?? CONST.ENVIRONMENT.DEV); } export default getEnvironment; diff --git a/src/libs/Environment/getEnvironment/types.ts b/src/libs/Environment/getEnvironment/types.ts new file mode 100644 index 000000000000..7cf17af3e0d8 --- /dev/null +++ b/src/libs/Environment/getEnvironment/types.ts @@ -0,0 +1,6 @@ +import {ValueOf} from 'type-fest'; +import CONST from '../../../CONST'; + +type Environment = ValueOf; + +export default Environment; From bb6cd818ebb3b9963c67da198870e8ca5350d19b Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 11 Oct 2023 16:17:31 +0200 Subject: [PATCH 057/376] 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 598f97f5471915b03bc9e62a94a4ad728a5f8b27 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 11 Oct 2023 17:01:03 +0200 Subject: [PATCH 058/376] ref: moved useArrowKeyFocusManager to TS --- ...sManager.js => useArrowKeyFocusManager.ts} | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) rename src/hooks/{useArrowKeyFocusManager.js => useArrowKeyFocusManager.ts} (77%) diff --git a/src/hooks/useArrowKeyFocusManager.js b/src/hooks/useArrowKeyFocusManager.ts similarity index 77% rename from src/hooks/useArrowKeyFocusManager.js rename to src/hooks/useArrowKeyFocusManager.ts index 58cecb169249..4f06111df09b 100644 --- a/src/hooks/useArrowKeyFocusManager.js +++ b/src/hooks/useArrowKeyFocusManager.ts @@ -2,19 +2,27 @@ import {useState, useEffect, useCallback, useMemo} from 'react'; import useKeyboardShortcut from './useKeyboardShortcut'; import CONST from '../CONST'; +type Config = { + maxIndex: number; + onFocusedIndexChange?: (index: number) => void; + initialFocusedIndex?: number; + disabledIndexes?: readonly number[]; + shouldExcludeTextAreaNodes?: boolean; + isActive?: boolean; +}; + /** * A hook that makes it easy to use the arrow keys to manage focus of items in a list * * Recommendation: To ensure stability, wrap the `onFocusedIndexChange` function with the useCallback hook before using it with this hook. * - * @param {Object} config - * @param {Number} config.maxIndex – typically the number of items in your list - * @param {Function} [config.onFocusedIndexChange] – optional callback to execute when focusedIndex changes - * @param {Number} [config.initialFocusedIndex] – where to start in the list - * @param {Array} [config.disabledIndexes] – An array of indexes to disable + skip over - * @param {Boolean} [config.shouldExcludeTextAreaNodes] – Whether arrow keys should have any effect when a TextArea node is focused - * @param {Boolean} [config.isActive] – Whether the component is ready and should subscribe to KeyboardShortcut - * @returns {Array} + * @param config + * @param config.maxIndex – typically the number of items in your list + * @param [config.onFocusedIndexChange] – optional callback to execute when focusedIndex changes + * @param [config.initialFocusedIndex] – where to start in the list + * @param [config.disabledIndexes] – An array of indexes to disable + skip over + * @param [config.shouldExcludeTextAreaNodes] – Whether arrow keys should have any effect when a TextArea node is focused + * @param [config.isActive] – Whether the component is ready and should subscribe to KeyboardShortcut */ export default function useArrowKeyFocusManager({ maxIndex, @@ -26,7 +34,7 @@ export default function useArrowKeyFocusManager({ disabledIndexes = CONST.EMPTY_ARRAY, shouldExcludeTextAreaNodes = true, isActive, -}) { +}: Config): [number, (index: number) => void] { const [focusedIndex, setFocusedIndex] = useState(initialFocusedIndex); const arrowConfig = useMemo( () => ({ From 800ce65fcb7a954ce6738a111dfa4a62761a4991 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 11 Oct 2023 18:15:03 +0200 Subject: [PATCH 059/376] [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 060/376] 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 cb7efb9b6c9215e96170476572c7203845681683 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Wed, 11 Oct 2023 22:32:34 +0200 Subject: [PATCH 061/376] Add IsBetaBuild type --- src/libs/Environment/betaChecker/index.android.ts | 3 ++- src/libs/Environment/betaChecker/index.ios.ts | 3 ++- src/libs/Environment/betaChecker/index.ts | 4 +++- src/libs/Environment/betaChecker/types.ts | 3 +++ 4 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 src/libs/Environment/betaChecker/types.ts diff --git a/src/libs/Environment/betaChecker/index.android.ts b/src/libs/Environment/betaChecker/index.android.ts index 8b3afed3b2f3..e33b9c7feb6e 100644 --- a/src/libs/Environment/betaChecker/index.android.ts +++ b/src/libs/Environment/betaChecker/index.android.ts @@ -4,6 +4,7 @@ import CONST from '../../../CONST'; import pkg from '../../../../package.json'; import ONYXKEYS from '../../../ONYXKEYS'; import * as AppUpdate from '../../actions/AppUpdate'; +import IsBetaBuild from './types'; let isLastSavedBeta = false; Onyx.connect({ @@ -19,7 +20,7 @@ Onyx.connect({ /** * Check the GitHub releases to see if the current build is a beta build or production build */ -function isBetaBuild(): Promise { +function isBetaBuild(): IsBetaBuild { return new Promise((resolve) => { fetch(CONST.GITHUB_RELEASE_URL) .then((res) => res.json()) diff --git a/src/libs/Environment/betaChecker/index.ios.ts b/src/libs/Environment/betaChecker/index.ios.ts index 2d6079e30a1c..0d901fc4b003 100644 --- a/src/libs/Environment/betaChecker/index.ios.ts +++ b/src/libs/Environment/betaChecker/index.ios.ts @@ -1,9 +1,10 @@ import {NativeModules} from 'react-native'; +import IsBetaBuild from './types'; /** * Check to see if the build is staging (TestFlight) or production */ -function isBetaBuild(): Promise { +function isBetaBuild(): IsBetaBuild { return new Promise((resolve) => { NativeModules.EnvironmentChecker.isBeta().then((isBeta: boolean) => { resolve(isBeta); diff --git a/src/libs/Environment/betaChecker/index.ts b/src/libs/Environment/betaChecker/index.ts index cda7c297624f..541a3120ccce 100644 --- a/src/libs/Environment/betaChecker/index.ts +++ b/src/libs/Environment/betaChecker/index.ts @@ -1,7 +1,9 @@ +import IsBetaBuild from './types'; + /** * There's no beta build in non native */ -function isBetaBuild(): Promise { +function isBetaBuild(): IsBetaBuild { return Promise.resolve(false); } diff --git a/src/libs/Environment/betaChecker/types.ts b/src/libs/Environment/betaChecker/types.ts new file mode 100644 index 000000000000..61ce4bc9cec4 --- /dev/null +++ b/src/libs/Environment/betaChecker/types.ts @@ -0,0 +1,3 @@ +type IsBetaBuild = Promise; + +export default IsBetaBuild; From c3df845adfe24f1377d7d9ca03fdb21b476ea566 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Wed, 11 Oct 2023 23:07:57 -0400 Subject: [PATCH 062/376] 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 063/376] 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 064/376] 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 065/376] 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 066/376] 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 067/376] 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 068/376] 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 eab18e6e62e0ef4944e164b53a1f6706633fe1d1 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 12 Oct 2023 15:36:48 +0200 Subject: [PATCH 069/376] 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 070/376] 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 071/376] 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 072/376] 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 073/376] 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 074/376] 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 1d6bb1d14cff3dd029868a0a7c8ee14ae78c527b Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Thu, 12 Oct 2023 11:18:33 -0400 Subject: [PATCH 075/376] use server instead of https config --- config/webpack/webpack.dev.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/config/webpack/webpack.dev.js b/config/webpack/webpack.dev.js index 1e7074b16277..dcca84ce5e9d 100644 --- a/config/webpack/webpack.dev.js +++ b/config/webpack/webpack.dev.js @@ -45,9 +45,12 @@ module.exports = (env = {}) => historyApiFallback: true, port, host: 'new.expensify.com.dev', - https: { - key: path.join(__dirname, 'key.pem'), - cert: path.join(__dirname, 'certificate.pem'), + server: { + type: 'https', + options: { + key: path.join(__dirname, 'key.pem'), + cert: path.join(__dirname, 'certificate.pem'), + }, }, }, plugins: [ From 0e7d40daf212782d4209d5ec6d2ab455c6515700 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 12 Oct 2023 18:11:47 +0200 Subject: [PATCH 076/376] 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 077/376] 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 078/376] 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 079/376] 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 080/376] 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 081/376] 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 082/376] 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 083/376] 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 084/376] 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 085/376] 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 086/376] 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 087/376] 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 088/376] 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 089/376] 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 c102c52abc79a22646509e3486a80da647645340 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 13 Oct 2023 15:03:43 +0200 Subject: [PATCH 090/376] fix: resolve comment --- src/hooks/useArrowKeyFocusManager.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hooks/useArrowKeyFocusManager.ts b/src/hooks/useArrowKeyFocusManager.ts index 4f06111df09b..5746700b3c64 100644 --- a/src/hooks/useArrowKeyFocusManager.ts +++ b/src/hooks/useArrowKeyFocusManager.ts @@ -11,6 +11,8 @@ type Config = { isActive?: boolean; }; +type UseArrowKeyFocusManager = [number, (index: number) => void]; + /** * A hook that makes it easy to use the arrow keys to manage focus of items in a list * @@ -34,7 +36,7 @@ export default function useArrowKeyFocusManager({ disabledIndexes = CONST.EMPTY_ARRAY, shouldExcludeTextAreaNodes = true, isActive, -}: Config): [number, (index: number) => void] { +}: Config): UseArrowKeyFocusManager { const [focusedIndex, setFocusedIndex] = useState(initialFocusedIndex); const arrowConfig = useMemo( () => ({ From 86e1ef176839afdd8df5c625164b9e9c049266d9 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Fri, 13 Oct 2023 15:16:02 +0200 Subject: [PATCH 091/376] Resolve typecheck errors --- src/libs/ApiUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/ApiUtils.ts b/src/libs/ApiUtils.ts index 87a251ccb086..54c7689dc4fb 100644 --- a/src/libs/ApiUtils.ts +++ b/src/libs/ApiUtils.ts @@ -1,3 +1,4 @@ +import {ValueOf} from 'type-fest'; import Onyx from 'react-native-onyx'; import ONYXKEYS from '../ONYXKEYS'; import CONFIG from '../CONFIG'; @@ -8,7 +9,7 @@ import {Request} from '../types/onyx'; // To avoid rebuilding native apps, native apps use production config for both staging and prod // We use the async environment check because it works on all platforms -let ENV_NAME = CONST.ENVIRONMENT.PRODUCTION; +let ENV_NAME: ValueOf = CONST.ENVIRONMENT.PRODUCTION; let shouldUseStagingServer = false; Environment.getEnvironment().then((envName) => { ENV_NAME = envName; From d13cfb8f19496b08da088c6b5d3cfd217a1ebb9b Mon Sep 17 00:00:00 2001 From: Hardik Choudhary Date: Fri, 13 Oct 2023 21:39:18 +0530 Subject: [PATCH 092/376] 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 093/376] 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 094/376] 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 095/376] 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 e48b32e09b52d18d944c5d77a86cc12c2dc658f2 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 16 Oct 2023 12:32:43 +0200 Subject: [PATCH 096/376] fix: resolved comment --- src/hooks/useArrowKeyFocusManager.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hooks/useArrowKeyFocusManager.ts b/src/hooks/useArrowKeyFocusManager.ts index 5746700b3c64..b2d1af1d659d 100644 --- a/src/hooks/useArrowKeyFocusManager.ts +++ b/src/hooks/useArrowKeyFocusManager.ts @@ -18,7 +18,6 @@ type UseArrowKeyFocusManager = [number, (index: number) => void]; * * Recommendation: To ensure stability, wrap the `onFocusedIndexChange` function with the useCallback hook before using it with this hook. * - * @param config * @param config.maxIndex – typically the number of items in your list * @param [config.onFocusedIndexChange] – optional callback to execute when focusedIndex changes * @param [config.initialFocusedIndex] – where to start in the list From 68a0ee71c8795e6a536d98c2874b29a15c692705 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 16 Oct 2023 14:26:08 +0200 Subject: [PATCH 097/376] 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 098/376] 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 099/376] 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 100/376] 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 101/376] 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 102/376] 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 103/376] 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 104/376] 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 e733f372f4d63779b349439d821fd843317d743a Mon Sep 17 00:00:00 2001 From: Hayata Suenaga Date: Tue, 17 Oct 2023 20:59:42 +0900 Subject: [PATCH 105/376] docs: add instructions for moving certificates --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/README.md b/README.md index 6b5f96de6b72..17c4f83e2113 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ #### Table of Contents * [Local Development](#local-development) +* [Testing on browsers on simulators and emulators](#testing-on-browsers-on-simulators-and-emulators) * [Running The Tests](#running-the-tests) * [Debugging](#debugging) * [App Structure and Conventions](#app-structure-and-conventions) @@ -113,6 +114,47 @@ variables referenced here get updated since your local `.env` file is ignored. ---- +# Testing on browsers on simulators and emulators + +The development server is reached through the HTTPS protocol, and any client that access the development server needs a certificate. + +You create this certificate by following the instructions in [`Configuring HTTPS`](#configuring-https) of this readme. When accessing the website served from the development server on browsers on iOS simulator or Android emulator, these virtual devices need to have the same certificate installed. Follow the steps below to install to do so. + +#### Pre-requisite for Android flow +1. Open any emulator using Android Studio +2. Use `adb push "$(mkcert -CAROOT)/rootCA.pem" /storage/emulated/0/Download/` to push certificate to install in Download folder. +3. Install the certificate as CA certificate from the settings. +4. Close the emulator. + +Note - If you want to run app on `https://127.0.0.1:8082`, then just install the certificate and use `adb reverse tcp:8082 tcp:8082` on every startup. + +#### Android Flow +1. Run `npm run setupNewDotWebForEmulators android` +2. Select the emulator you want to run if prompted. (If single emulator is available, then it will open automatically) +3. Let the script execute till the message `🎉 Done!`. +4. Check the emulator is closed. + +Note - If you want to run app on `https://new.expensify.com.dev:8082`, then just do the Android flow and use `npm run startAndroidEmulator` to start the Android Emulator every time (It will configure the emulator). + + +Possible Scenario: +1. It may run the second flow on a new device, then to check first flow just run the flow again and select the same device. +2. It may fail to root with error `adbd cannot run as root in production builds`, then it will point to https://stackoverflow.com/a/45668555 in the console. + +#### iOS Flow +1. Run `npm run setupNewDotWebForEmulators ios` +2. Select the emulator you want to run if prompted. (If single emulator is available, then it will open automatically) +3. Let the script execute till the message `🎉 Done!`. +4. Check the emulator is closed. + +#### All Flow +1. Run `npm run setupNewDotWebForEmulators all` or `npm run setupNewDotWebForEmulators` +2. Check if the iOS flow runs first and then Android flow runs. +3. Let the script execute till the message `🎉 Done!`. +4. Check the emulator is closed. + +---- + # Running the tests ## Unit tests Unit tests are valuable when you want to test one component. They should be short, fast, and ideally only test one thing. From 7b277a636b6e04c76926d276100a0344e00ddaef Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 17 Oct 2023 14:05:24 +0200 Subject: [PATCH 106/376] [TS migration] Migrate 'RenderHTML.js' component to TypeScript --- src/components/{RenderHTML.js => RenderHTML.tsx} | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) rename src/components/{RenderHTML.js => RenderHTML.tsx} (75%) diff --git a/src/components/RenderHTML.js b/src/components/RenderHTML.tsx similarity index 75% rename from src/components/RenderHTML.js rename to src/components/RenderHTML.tsx index d2d4f0b58e71..14423d5e9d26 100644 --- a/src/components/RenderHTML.js +++ b/src/components/RenderHTML.tsx @@ -1,29 +1,26 @@ import React from 'react'; -import PropTypes from 'prop-types'; import {RenderHTMLSource} from 'react-native-render-html'; import useWindowDimensions from '../hooks/useWindowDimensions'; -const propTypes = { +type RenderHTMLProps = { /** HTML string to render */ - html: PropTypes.string.isRequired, + html: string; }; // We are using the explicit composite architecture for performance gains. // Configuration for RenderHTML is handled in a top-level component providing // context to RenderHTMLSource components. See https://git.io/JRcZb // The provider is available at src/components/HTMLEngineProvider/ -function RenderHTML(props) { +function RenderHTML({html}: RenderHTMLProps) { const {windowWidth} = useWindowDimensions(); return ( ); } RenderHTML.displayName = 'RenderHTML'; -RenderHTML.propTypes = propTypes; -RenderHTML.defaultProps = {}; export default RenderHTML; From 7f6c9f0dafda8a76784d4e1681a197382be83c79 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 17 Oct 2023 14:08:59 +0200 Subject: [PATCH 107/376] [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 108/376] [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 abad73ff87fd544d75c4b4def30722d9e454766b Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 17 Oct 2023 15:05:58 +0200 Subject: [PATCH 109/376] fix TS check --- src/hooks/useWindowDimensions/index.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/hooks/useWindowDimensions/index.js b/src/hooks/useWindowDimensions/index.js index 1a1f7eed5a67..404bbd161a8c 100644 --- a/src/hooks/useWindowDimensions/index.js +++ b/src/hooks/useWindowDimensions/index.js @@ -2,9 +2,19 @@ import {Dimensions, useWindowDimensions} from 'react-native'; import variables from '../../styles/variables'; +/** + * @typedef {Object} WindowDimensions + * @property {number} windowWidth + * @property {number} windowHeight + * @property {boolean} isExtraSmallScreenHeight + * @property {boolean} isSmallScreenWidth + * @property {boolean} isMediumScreenWidth + * @property {boolean} isLargeScreenWidth + */ + /** * A convenience wrapper around React Native's useWindowDimensions hook that also provides booleans for our breakpoints. - * @returns {Object} + * @returns {WindowDimensions} */ export default function () { const {width: windowWidth, height: windowHeight} = useWindowDimensions(); From fefdb2b3bcf3076daf99e446e6837356d697a4a6 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Tue, 17 Oct 2023 15:08:19 +0200 Subject: [PATCH 110/376] 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 111/376] 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 112/376] 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 113/376] 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 114/376] 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 115/376] 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 116/376] 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 117/376] 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 118/376] 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 119/376] 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 120/376] 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 121/376] 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 122/376] 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 123/376] 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 124/376] 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 125/376] 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 126/376] 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 127/376] 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 128/376] 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 129/376] 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 130/376] 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 131/376] 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 6aeab2f7b1c996634fc7c20bc24b661dee8bd0f4 Mon Sep 17 00:00:00 2001 From: Hayata Suenaga Date: Wed, 18 Oct 2023 21:53:59 +0900 Subject: [PATCH 132/376] docs: update readme --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 17c4f83e2113..d96bafd85061 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ You create this certificate by following the instructions in [`Configuring HTTPS #### Pre-requisite for Android flow 1. Open any emulator using Android Studio 2. Use `adb push "$(mkcert -CAROOT)/rootCA.pem" /storage/emulated/0/Download/` to push certificate to install in Download folder. -3. Install the certificate as CA certificate from the settings. +3. Install the certificate as CA certificate from the settings. On the Android emulator, this option can be found in Settings > Security > Encryption & Credentials > Install a certificate > CA certificate. 4. Close the emulator. Note - If you want to run app on `https://127.0.0.1:8082`, then just install the certificate and use `adb reverse tcp:8082 tcp:8082` on every startup. @@ -132,26 +132,22 @@ Note - If you want to run app on `https://127.0.0.1:8082`, then just install the 1. Run `npm run setupNewDotWebForEmulators android` 2. Select the emulator you want to run if prompted. (If single emulator is available, then it will open automatically) 3. Let the script execute till the message `🎉 Done!`. -4. Check the emulator is closed. Note - If you want to run app on `https://new.expensify.com.dev:8082`, then just do the Android flow and use `npm run startAndroidEmulator` to start the Android Emulator every time (It will configure the emulator). Possible Scenario: -1. It may run the second flow on a new device, then to check first flow just run the flow again and select the same device. -2. It may fail to root with error `adbd cannot run as root in production builds`, then it will point to https://stackoverflow.com/a/45668555 in the console. +The flow may fail to root with error `adbd cannot run as root in production builds`. In this case, please refer to https://stackoverflow.com/a/45668555. Or use `https://127.0.0.1:8082` for less hassle. #### iOS Flow 1. Run `npm run setupNewDotWebForEmulators ios` 2. Select the emulator you want to run if prompted. (If single emulator is available, then it will open automatically) 3. Let the script execute till the message `🎉 Done!`. -4. Check the emulator is closed. #### All Flow 1. Run `npm run setupNewDotWebForEmulators all` or `npm run setupNewDotWebForEmulators` 2. Check if the iOS flow runs first and then Android flow runs. 3. Let the script execute till the message `🎉 Done!`. -4. Check the emulator is closed. ---- From a2a2fda4523474244b7b8dbfe351eed459973e23 Mon Sep 17 00:00:00 2001 From: Hayata Suenaga Date: Wed, 18 Oct 2023 07:10:35 -0700 Subject: [PATCH 133/376] fix typo Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d96bafd85061..b217690f37ed 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ variables referenced here get updated since your local `.env` file is ignored. ---- -# Testing on browsers on simulators and emulators +# Testing on browsers in simulators and emulators The development server is reached through the HTTPS protocol, and any client that access the development server needs a certificate. From 512d08434510ce8f6a4978e94796528b330d84bf Mon Sep 17 00:00:00 2001 From: Hayata Suenaga Date: Wed, 18 Oct 2023 07:11:19 -0700 Subject: [PATCH 134/376] fix typo Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b217690f37ed..48c4cb03baf0 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ variables referenced here get updated since your local `.env` file is ignored. The development server is reached through the HTTPS protocol, and any client that access the development server needs a certificate. -You create this certificate by following the instructions in [`Configuring HTTPS`](#configuring-https) of this readme. When accessing the website served from the development server on browsers on iOS simulator or Android emulator, these virtual devices need to have the same certificate installed. Follow the steps below to install to do so. +You create this certificate by following the instructions in [`Configuring HTTPS`](#configuring-https) of this readme. When accessing the website served from the development server on browsers in iOS simulator or Android emulator, these virtual devices need to have the same certificate installed. Follow the steps below to install them. #### Pre-requisite for Android flow 1. Open any emulator using Android Studio From 3d0b045c01d35b7759d2dcb72ea96a791c07fa6c Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Wed, 18 Oct 2023 10:53:51 -0600 Subject: [PATCH 135/376] Rename shouldDisableWriteActions to canUserPerformWriteAction --- src/libs/ReportUtils.js | 7 ++++--- src/libs/SidebarUtils.js | 2 +- src/pages/home/report/ReportActionsList.js | 2 +- src/pages/home/report/ReportFooter.js | 2 +- src/pages/iou/steps/NewRequestAmountPage.js | 2 +- src/pages/tasks/TaskShareDestinationSelectorModal.js | 2 +- tests/unit/OptionsListUtilsTest.js | 4 ++-- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 4e351d2dc5e3..7a376c59e6a3 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -3386,7 +3386,7 @@ function parseReportRouteParams(route) { parsingRoute = parsingRoute.slice(1); } - if (!parsingRoute.startsWith(Url.addTrailingForwardSlash('r'))) { + if (!parsingRoute.startsWith(Url.addTrailingForwardSlash(ROUTES.REPORT))) { return {reportID: '', isSubReportPageRoute: false}; } @@ -3662,9 +3662,10 @@ function getAddWorkspaceRoomOrChatReportErrors(report) { * @param {Object} report * @returns {Boolean} */ -function shouldDisableWriteActions(report) { +function canUserPerformWriteAction(report) { const reportErrors = getAddWorkspaceRoomOrChatReportErrors(report); return isArchivedRoom(report) || !_.isEmpty(reportErrors) || !isAllowedToComment(report) || isAnonymousUser; + return !isArchivedRoom(report) && _.isEmpty(reportErrors) && isAllowedToComment(report) && !isAnonymousUser; } /** @@ -4057,7 +4058,7 @@ export { getRootParentReport, getReportPreviewMessage, getModifiedExpenseMessage, - shouldDisableWriteActions, + canUserPerformWriteAction, getOriginalReportID, canAccessReport, getAddWorkspaceRoomOrChatReportErrors, diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js index dd6db33902fb..9018ed1ddd80 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.js @@ -305,7 +305,7 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, result.parentReportID = report.parentReportID || null; result.isWaitingOnBankAccount = report.isWaitingOnBankAccount; result.notificationPreference = report.notificationPreference || null; - result.isAllowedToComment = !ReportUtils.shouldDisableWriteActions(report); + result.isAllowedToComment = !ReportUtils.canUserPerformWriteAction(report); const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat; const subtitle = ReportUtils.getChatRoomSubtitle(report); diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index c673c06470f8..e986c7b53528 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -334,7 +334,7 @@ function ReportActionsList({ // Native mobile does not render updates flatlist the changes even though component did update called. // To notify there something changes we can use extraData prop to flatlist const extraData = [isSmallScreenWidth ? currentUnreadMarker : undefined, ReportUtils.isArchivedRoom(report)]; - const hideComposer = ReportUtils.shouldDisableWriteActions(report); + const hideComposer = ReportUtils.canUserPerformWriteAction(report); const shouldShowReportRecipientLocalTime = ReportUtils.canShowReportRecipientLocalTime(personalDetailsList, report, currentUserPersonalDetails.accountID) && !isComposerFullSize; const renderFooter = useCallback(() => { diff --git a/src/pages/home/report/ReportFooter.js b/src/pages/home/report/ReportFooter.js index 2237e6448504..c96824770f07 100644 --- a/src/pages/home/report/ReportFooter.js +++ b/src/pages/home/report/ReportFooter.js @@ -70,7 +70,7 @@ function ReportFooter(props) { const isAnonymousUser = Session.isAnonymousUser(); const isSmallSizeLayout = props.windowWidth - (props.isSmallScreenWidth ? 0 : variables.sideBarWidth) < variables.anonymousReportFooterBreakpoint; - const hideComposer = ReportUtils.shouldDisableWriteActions(props.report); + const hideComposer = ReportUtils.canUserPerformWriteAction(props.report); return ( <> diff --git a/src/pages/iou/steps/NewRequestAmountPage.js b/src/pages/iou/steps/NewRequestAmountPage.js index ae319f5a73bb..dda52352e015 100644 --- a/src/pages/iou/steps/NewRequestAmountPage.js +++ b/src/pages/iou/steps/NewRequestAmountPage.js @@ -85,7 +85,7 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) { // Check and dismiss modal useEffect(() => { - if (!ReportUtils.shouldDisableWriteActions(report)) { + if (!ReportUtils.canUserPerformWriteAction(report)) { return; } Navigation.dismissModal(reportID); diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.js b/src/pages/tasks/TaskShareDestinationSelectorModal.js index 2fc8f0eab014..f1cd009d197d 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.js +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.js @@ -52,7 +52,7 @@ function TaskShareDestinationSelectorModal(props) { const reports = {}; _.keys(props.reports).forEach((reportKey) => { if ( - ReportUtils.shouldDisableWriteActions(props.reports[reportKey]) || + ReportUtils.canUserPerformWriteAction(props.reports[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(props.reports[reportKey]) || ReportUtils.isCanceledTaskReport(props.reports[reportKey]) ) { diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index eda743f85aa2..0032f6a1ad42 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -590,7 +590,7 @@ describe('OptionsListUtils', () => { // Filter current REPORTS as we do in the component, before getting share destination options const filteredReports = {}; _.keys(REPORTS).forEach((reportKey) => { - if (ReportUtils.shouldDisableWriteActions(REPORTS[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(REPORTS[reportKey])) { + if (ReportUtils.canUserPerformWriteAction(REPORTS[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(REPORTS[reportKey])) { return; } filteredReports[reportKey] = REPORTS[reportKey]; @@ -617,7 +617,7 @@ describe('OptionsListUtils', () => { // Filter current REPORTS_WITH_WORKSPACE_ROOMS as we do in the component, before getting share destination options const filteredReportsWithWorkspaceRooms = {}; _.keys(REPORTS_WITH_WORKSPACE_ROOMS).forEach((reportKey) => { - if (ReportUtils.shouldDisableWriteActions(REPORTS_WITH_WORKSPACE_ROOMS[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(REPORTS_WITH_WORKSPACE_ROOMS[reportKey])) { + if (ReportUtils.canUserPerformWriteAction(REPORTS_WITH_WORKSPACE_ROOMS[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(REPORTS_WITH_WORKSPACE_ROOMS[reportKey])) { return; } filteredReportsWithWorkspaceRooms[reportKey] = REPORTS_WITH_WORKSPACE_ROOMS[reportKey]; From 2ca79c84e1a50d16f53a01e49087e602101d3b86 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 18 Oct 2023 10:17:33 -0700 Subject: [PATCH 136/376] 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 137/376] 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 138/376] 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 139/376] 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 140/376] 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 141/376] 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 142/376] 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 143/376] 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 144/376] 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 145/376] 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 146/376] 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 147/376] 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 148/376] 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 149/376] 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 150/376] 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 b4d942fc7d23c314ec7e6efab99c19073cce560f Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 19 Oct 2023 10:46:56 +0200 Subject: [PATCH 151/376] WIP --- src/pages/settings/Wallet/AddDebitCardPage.js | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/pages/settings/Wallet/AddDebitCardPage.js b/src/pages/settings/Wallet/AddDebitCardPage.js index e75c3b2c517e..23532346a990 100644 --- a/src/pages/settings/Wallet/AddDebitCardPage.js +++ b/src/pages/settings/Wallet/AddDebitCardPage.js @@ -22,6 +22,8 @@ import ROUTES from '../../../ROUTES'; import usePrevious from '../../../hooks/usePrevious'; import NotFoundPage from '../../ErrorPage/NotFoundPage'; import Permissions from '../../../libs/Permissions'; +import FormProvider from "../../../components/Form/FormProvider"; +import InputWrapper from "../../../components/Form/InputWrapper"; const propTypes = { /* Onyx Props */ @@ -114,7 +116,7 @@ function DebitCardPage(props) { title={translate('addDebitCardPage.addADebitCard')} onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_WALLET)} /> -
- (nameOnCardRef.current = ref)} spellCheck={false} /> - - - - - - + - ( @@ -195,7 +204,7 @@ function DebitCardPage(props) { )} style={[styles.mt4]} /> - + ); } From e651ba360ce9c32793cbf11bb83a6276aad08abf Mon Sep 17 00:00:00 2001 From: Kacper Falat Date: Thu, 19 Oct 2023 10:47:38 +0200 Subject: [PATCH 152/376] 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 153/376] [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 a348d1ba55eb224a53538f5afc9cd9c7b215a97b Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 19 Oct 2023 15:26:34 +0200 Subject: [PATCH 154/376] Fix checkbox proptypes --- src/components/CheckboxWithLabel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CheckboxWithLabel.js b/src/components/CheckboxWithLabel.js index 63c067c93234..777c46f861aa 100644 --- a/src/components/CheckboxWithLabel.js +++ b/src/components/CheckboxWithLabel.js @@ -54,7 +54,7 @@ const propTypes = { defaultValue: PropTypes.bool, /** React ref being forwarded to the Checkbox input */ - forwardedRef: PropTypes.func, + forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(React.Component)})]), /** The ID used to uniquely identify the input in a Form */ /* eslint-disable-next-line react/no-unused-prop-types */ From 38d91848c62674d56b4137fdce9c1f482b4e46bd Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 19 Oct 2023 15:27:14 +0200 Subject: [PATCH 155/376] Fix address search prop type issue --- src/components/AddressSearch/index.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 3e676b811c16..3fac925cdb0a 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -1,7 +1,7 @@ import _ from 'underscore'; -import React, {useEffect, useMemo, useRef, useState} from 'react'; +import React, {memo, useEffect, useMemo, useRef, useState} from 'react'; import PropTypes from 'prop-types'; -import {Keyboard, LogBox, ScrollView, View, Text, ActivityIndicator} from 'react-native'; +import {ActivityIndicator, Keyboard, LogBox, ScrollView, Text, View} from 'react-native'; import {GooglePlacesAutocomplete} from 'react-native-google-places-autocomplete'; import lodashGet from 'lodash/get'; import compose from '../../libs/compose'; @@ -137,6 +137,16 @@ const defaultProps = { resultTypes: 'address', }; +function listLoader() { + return memo(() => ( + + )); +} + + // Do not convert to class component! It's been tried before and presents more challenges than it's worth. // Relevant thread: https://expensify.slack.com/archives/C03TQ48KC/p1634088400387400 // Reference: https://github.com/FaridSafi/react-native-google-places-autocomplete/issues/609#issuecomment-886133839 @@ -374,14 +384,7 @@ function AddressSearch(props) { {props.translate('common.noResultsFound')} ) } - listLoaderComponent={ - - - - } + listLoaderComponent={listLoader} renderHeaderComponent={renderHeaderComponent} onPress={(data, details) => { saveLocationDetails(data, details); From 00e12fc524860cc05fa5c34929a3ccec21e5efb5 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 19 Oct 2023 15:27:36 +0200 Subject: [PATCH 156/376] Add checkbox default value --- src/pages/settings/Wallet/AddDebitCardPage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/settings/Wallet/AddDebitCardPage.js b/src/pages/settings/Wallet/AddDebitCardPage.js index 23532346a990..ad284da16827 100644 --- a/src/pages/settings/Wallet/AddDebitCardPage.js +++ b/src/pages/settings/Wallet/AddDebitCardPage.js @@ -196,6 +196,7 @@ function DebitCardPage(props) { InputComponent={CheckboxWithLabel} accessibilityLabel={`${translate('common.iAcceptThe')} ${translate('common.expensifyTermsOfService')}`} inputID="acceptTerms" + defaultValue={false} LabelComponent={() => ( {`${translate('common.iAcceptThe')}`} From 6b401e0e1d6631889f93e1fc1a16f19a068ba5e0 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 19 Oct 2023 08:11:06 -0600 Subject: [PATCH 157/376] Fix lint --- src/libs/ReportUtils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 7a376c59e6a3..3b72e6829c39 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -3664,7 +3664,6 @@ function getAddWorkspaceRoomOrChatReportErrors(report) { */ function canUserPerformWriteAction(report) { const reportErrors = getAddWorkspaceRoomOrChatReportErrors(report); - return isArchivedRoom(report) || !_.isEmpty(reportErrors) || !isAllowedToComment(report) || isAnonymousUser; return !isArchivedRoom(report) && _.isEmpty(reportErrors) && isAllowedToComment(report) && !isAnonymousUser; } From 84e6c26ba8f86a2b5952e13629d5a4c6114ee07d Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Thu, 19 Oct 2023 16:11:53 +0200 Subject: [PATCH 158/376] 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 b05c8bab57672e9a00aeb1b86b1efcdc9d1bf228 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 19 Oct 2023 16:25:41 +0200 Subject: [PATCH 159/376] Prettier --- src/pages/settings/Wallet/AddDebitCardPage.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/Wallet/AddDebitCardPage.js b/src/pages/settings/Wallet/AddDebitCardPage.js index ad284da16827..b0196e8e620e 100644 --- a/src/pages/settings/Wallet/AddDebitCardPage.js +++ b/src/pages/settings/Wallet/AddDebitCardPage.js @@ -22,8 +22,8 @@ import ROUTES from '../../../ROUTES'; import usePrevious from '../../../hooks/usePrevious'; import NotFoundPage from '../../ErrorPage/NotFoundPage'; import Permissions from '../../../libs/Permissions'; -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 = { /* Onyx Props */ @@ -190,7 +190,10 @@ function DebitCardPage(props) { containerStyles={[styles.mt4]} /> - + Date: Thu, 19 Oct 2023 16:25:56 +0200 Subject: [PATCH 160/376] Spread props --- src/components/AddressSearch/index.js | 146 +++++++++++++++----------- 1 file changed, 86 insertions(+), 60 deletions(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 3fac925cdb0a..db8d6b6bae42 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -1,5 +1,5 @@ import _ from 'underscore'; -import React, {memo, useEffect, useMemo, useRef, useState} from 'react'; +import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import PropTypes from 'prop-types'; import {ActivityIndicator, Keyboard, LogBox, ScrollView, Text, View} from 'react-native'; import {GooglePlacesAutocomplete} from 'react-native-google-places-autocomplete'; @@ -137,37 +137,49 @@ const defaultProps = { resultTypes: 'address', }; -function listLoader() { - return memo(() => ( - - )); -} - - // Do not convert to class component! It's been tried before and presents more challenges than it's worth. // Relevant thread: https://expensify.slack.com/archives/C03TQ48KC/p1634088400387400 // Reference: https://github.com/FaridSafi/react-native-google-places-autocomplete/issues/609#issuecomment-886133839 -function AddressSearch(props) { +function AddressSearch({ + canUseCurrentLocation, + containerStyles, + defaultValue, + errorText, + hint, + innerRef, + inputID, + isLimitedToUSA, + label, + maxInputLength, + network, + onBlur, + onInputChange, + onPress, + predefinedPlaces, + preferredLocale, + renamedInputKeys, + resultTypes, + shouldSaveDraft, + translate, + value, +}) { const [displayListViewBorder, setDisplayListViewBorder] = useState(false); const [isTyping, setIsTyping] = useState(false); const [isFocused, setIsFocused] = useState(false); - const [searchValue, setSearchValue] = useState(props.value || props.defaultValue || ''); + const [searchValue, setSearchValue] = useState(value || defaultValue || ''); const [locationErrorCode, setLocationErrorCode] = useState(null); const [isFetchingCurrentLocation, setIsFetchingCurrentLocation] = useState(false); const shouldTriggerGeolocationCallbacks = useRef(true); const containerRef = useRef(); const query = useMemo( () => ({ - language: props.preferredLocale, - types: props.resultTypes, - components: props.isLimitedToUSA ? 'country:us' : undefined, + language: preferredLocale, + types: resultTypes, + components: isLimitedToUSA ? 'country:us' : undefined, }), - [props.preferredLocale, props.resultTypes, props.isLimitedToUSA], + [preferredLocale, resultTypes, isLimitedToUSA], ); - const shouldShowCurrentLocationButton = props.canUseCurrentLocation && searchValue.trim().length === 0 && isFocused; + const shouldShowCurrentLocationButton = canUseCurrentLocation && searchValue.trim().length === 0 && isFocused; const saveLocationDetails = (autocompleteData, details) => { const addressComponents = details.address_components; @@ -176,7 +188,7 @@ function AddressSearch(props) { // to this component which don't match the usual properties coming from auto-complete. In that case, only a limited // amount of data massaging needs to happen for what the parent expects to get from this function. if (_.size(details)) { - props.onPress({ + onPress({ address: lodashGet(details, 'description', ''), lat: lodashGet(details, 'geometry.location.lat', 0), lng: lodashGet(details, 'geometry.location.lng', 0), @@ -262,7 +274,7 @@ function AddressSearch(props) { // Not all pages define the Address Line 2 field, so in that case we append any additional address details // (e.g. Apt #) to Address Line 1 - if (subpremise && typeof props.renamedInputKeys.street2 === 'undefined') { + if (subpremise && typeof renamedInputKeys.street2 === 'undefined') { values.street += `, ${subpremise}`; } @@ -271,19 +283,19 @@ function AddressSearch(props) { values.country = country; } - if (props.inputID) { - _.each(values, (value, key) => { - const inputKey = lodashGet(props.renamedInputKeys, key, key); + if (inputID) { + _.each(values, (inputValue, key) => { + const inputKey = lodashGet(renamedInputKeys, key, key); if (!inputKey) { return; } - props.onInputChange(value, inputKey); + onInputChange(inputValue, inputKey); }); } else { - props.onInputChange(values); + onInputChange(values); } - props.onPress(values); + onPress(values); }; /** Gets the user's current location and registers success/error callbacks */ @@ -313,7 +325,7 @@ function AddressSearch(props) { lng: successData.coords.longitude, address: CONST.YOUR_LOCATION_TEXT, }; - props.onPress(location); + onPress(location); }, (errorData) => { if (!shouldTriggerGeolocationCallbacks.current) { @@ -331,16 +343,16 @@ function AddressSearch(props) { }; const renderHeaderComponent = () => - props.predefinedPlaces.length > 0 && ( + predefinedPlaces.length > 0 && ( <> {/* This will show current location button in list if there are some recent destinations */} {shouldShowCurrentLocationButton && ( )} - {!props.value && {props.translate('common.recentDestinations')}} + {!value && {translate('common.recentDestinations')}} ); @@ -352,6 +364,26 @@ function AddressSearch(props) { }; }, []); + const listEmptyComponent = useCallback( + () => + network.isOffline || !isTyping ? null : ( + {translate('common.noResultsFound')} + ), + [isTyping, translate, network.isOffline], + ); + + const listLoader = useCallback( + () => ( + + + + ), + [], + ); + return ( /* * The GooglePlacesAutocomplete component uses a VirtualizedList internally, @@ -378,12 +410,8 @@ function AddressSearch(props) { fetchDetails suppressDefaultStyles enablePoweredByContainer={false} - predefinedPlaces={props.predefinedPlaces} - listEmptyComponent={ - props.network.isOffline || !isTyping ? null : ( - {props.translate('common.noResultsFound')} - ) - } + predefinedPlaces={predefinedPlaces} + listEmptyComponent={listEmptyComponent} listLoaderComponent={listLoader} renderHeaderComponent={renderHeaderComponent} onPress={(data, details) => { @@ -400,34 +428,31 @@ function AddressSearch(props) { query={query} requestUrl={{ useOnPlatform: 'all', - url: props.network.isOffline ? null : ApiUtils.getCommandURL({command: 'Proxy_GooglePlaces&proxyUrl='}), + url: network.isOffline ? null : ApiUtils.getCommandURL({command: 'Proxy_GooglePlaces&proxyUrl='}), }} textInputProps={{ InputComp: TextInput, ref: (node) => { - if (!props.innerRef) { + if (!innerRef) { return; } - if (_.isFunction(props.innerRef)) { - props.innerRef(node); + if (_.isFunction(innerRef)) { + innerRef(node); return; } // eslint-disable-next-line no-param-reassign - props.innerRef.current = node; + innerRef.current = node; }, - label: props.label, - containerStyles: props.containerStyles, - errorText: props.errorText, - hint: - displayListViewBorder || (props.predefinedPlaces.length === 0 && shouldShowCurrentLocationButton) || (props.canUseCurrentLocation && isTyping) - ? undefined - : props.hint, - value: props.value, - defaultValue: props.defaultValue, - inputID: props.inputID, - shouldSaveDraft: props.shouldSaveDraft, + label, + containerStyles, + errorText, + hint: displayListViewBorder || (predefinedPlaces.length === 0 && shouldShowCurrentLocationButton) || (canUseCurrentLocation && isTyping) ? undefined : hint, + value, + defaultValue, + inputID, + shouldSaveDraft, onFocus: () => { setIsFocused(true); }, @@ -437,24 +462,24 @@ function AddressSearch(props) { setIsFocused(false); setIsTyping(false); } - props.onBlur(); + onBlur(); }, autoComplete: 'off', onInputChange: (text) => { setSearchValue(text); setIsTyping(true); - if (props.inputID) { - props.onInputChange(text); + if (inputID) { + onInputChange(text); } else { - props.onInputChange({street: text}); + onInputChange({street: text}); } // If the text is empty and we have no predefined places, we set displayListViewBorder to false to prevent UI flickering - if (_.isEmpty(text) && _.isEmpty(props.predefinedPlaces)) { + if (_.isEmpty(text) && _.isEmpty(predefinedPlaces)) { setDisplayListViewBorder(false); } }, - maxLength: props.maxInputLength, + maxLength: maxInputLength, spellCheck: false, }} styles={{ @@ -475,17 +500,18 @@ function AddressSearch(props) { }} inbetweenCompo={ // We want to show the current location button even if there are no recent destinations - props.predefinedPlaces.length === 0 && shouldShowCurrentLocationButton ? ( + predefinedPlaces.length === 0 && shouldShowCurrentLocationButton ? ( ) : ( <> ) } + placeholder="" /> setLocationErrorCode(null)} From 5ad7f00172de2160a2934a6f478da21fac25bc0c Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Thu, 19 Oct 2023 16:48:32 +0200 Subject: [PATCH 161/376] 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 64dbcd48507efa6329be72a437f85fabed6e31a6 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 19 Oct 2023 17:45:01 +0200 Subject: [PATCH 162/376] Lint fix --- src/components/AddressSearch/index.js | 2 +- src/pages/settings/Wallet/AddDebitCardPage.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index db8d6b6bae42..7c754a33e370 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -1,5 +1,5 @@ import _ from 'underscore'; -import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import PropTypes from 'prop-types'; import {ActivityIndicator, Keyboard, LogBox, ScrollView, Text, View} from 'react-native'; import {GooglePlacesAutocomplete} from 'react-native-google-places-autocomplete'; diff --git a/src/pages/settings/Wallet/AddDebitCardPage.js b/src/pages/settings/Wallet/AddDebitCardPage.js index b0196e8e620e..b9ea0ac6df3a 100644 --- a/src/pages/settings/Wallet/AddDebitCardPage.js +++ b/src/pages/settings/Wallet/AddDebitCardPage.js @@ -16,7 +16,6 @@ import TextInput from '../../../components/TextInput'; import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; import AddressSearch from '../../../components/AddressSearch'; -import Form from '../../../components/Form'; import Navigation from '../../../libs/Navigation/Navigation'; import ROUTES from '../../../ROUTES'; import usePrevious from '../../../hooks/usePrevious'; From cf90f7bd132d9562b80a09a6e027be38a8fd6d9a Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 19 Oct 2023 09:47:00 -0600 Subject: [PATCH 163/376] Negate logic for the referenced variable --- src/libs/SidebarUtils.js | 2 +- src/pages/home/report/ReportActionsList.js | 2 +- src/pages/home/report/ReportFooter.js | 2 +- src/pages/iou/steps/NewRequestAmountPage.js | 2 +- src/pages/tasks/TaskShareDestinationSelectorModal.js | 2 +- tests/unit/OptionsListUtilsTest.js | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js index 2c679ef3cda7..6eb07c6b19e0 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.js @@ -305,7 +305,7 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, result.parentReportID = report.parentReportID || null; result.isWaitingOnBankAccount = report.isWaitingOnBankAccount; result.notificationPreference = report.notificationPreference || null; - result.isAllowedToComment = !ReportUtils.canUserPerformWriteAction(report); + result.isAllowedToComment = ReportUtils.canUserPerformWriteAction(report); const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat; const subtitle = ReportUtils.getChatRoomSubtitle(report); diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index e986c7b53528..32499ebc4090 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -334,7 +334,7 @@ function ReportActionsList({ // Native mobile does not render updates flatlist the changes even though component did update called. // To notify there something changes we can use extraData prop to flatlist const extraData = [isSmallScreenWidth ? currentUnreadMarker : undefined, ReportUtils.isArchivedRoom(report)]; - const hideComposer = ReportUtils.canUserPerformWriteAction(report); + const hideComposer = !ReportUtils.canUserPerformWriteAction(report); const shouldShowReportRecipientLocalTime = ReportUtils.canShowReportRecipientLocalTime(personalDetailsList, report, currentUserPersonalDetails.accountID) && !isComposerFullSize; const renderFooter = useCallback(() => { diff --git a/src/pages/home/report/ReportFooter.js b/src/pages/home/report/ReportFooter.js index c96824770f07..754fae8d670a 100644 --- a/src/pages/home/report/ReportFooter.js +++ b/src/pages/home/report/ReportFooter.js @@ -70,7 +70,7 @@ function ReportFooter(props) { const isAnonymousUser = Session.isAnonymousUser(); const isSmallSizeLayout = props.windowWidth - (props.isSmallScreenWidth ? 0 : variables.sideBarWidth) < variables.anonymousReportFooterBreakpoint; - const hideComposer = ReportUtils.canUserPerformWriteAction(props.report); + const hideComposer = !ReportUtils.canUserPerformWriteAction(props.report); return ( <> diff --git a/src/pages/iou/steps/NewRequestAmountPage.js b/src/pages/iou/steps/NewRequestAmountPage.js index dda52352e015..c624973a0189 100644 --- a/src/pages/iou/steps/NewRequestAmountPage.js +++ b/src/pages/iou/steps/NewRequestAmountPage.js @@ -85,7 +85,7 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) { // Check and dismiss modal useEffect(() => { - if (!ReportUtils.canUserPerformWriteAction(report)) { + if (ReportUtils.canUserPerformWriteAction(report)) { return; } Navigation.dismissModal(reportID); diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.js b/src/pages/tasks/TaskShareDestinationSelectorModal.js index f1cd009d197d..32e02e7ba42e 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.js +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.js @@ -52,7 +52,7 @@ function TaskShareDestinationSelectorModal(props) { const reports = {}; _.keys(props.reports).forEach((reportKey) => { if ( - ReportUtils.canUserPerformWriteAction(props.reports[reportKey]) || + !ReportUtils.canUserPerformWriteAction(props.reports[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(props.reports[reportKey]) || ReportUtils.isCanceledTaskReport(props.reports[reportKey]) ) { diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index 0032f6a1ad42..50edfa7f0522 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -590,7 +590,7 @@ describe('OptionsListUtils', () => { // Filter current REPORTS as we do in the component, before getting share destination options const filteredReports = {}; _.keys(REPORTS).forEach((reportKey) => { - if (ReportUtils.canUserPerformWriteAction(REPORTS[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(REPORTS[reportKey])) { + if (!ReportUtils.canUserPerformWriteAction(REPORTS[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(REPORTS[reportKey])) { return; } filteredReports[reportKey] = REPORTS[reportKey]; @@ -617,7 +617,7 @@ describe('OptionsListUtils', () => { // Filter current REPORTS_WITH_WORKSPACE_ROOMS as we do in the component, before getting share destination options const filteredReportsWithWorkspaceRooms = {}; _.keys(REPORTS_WITH_WORKSPACE_ROOMS).forEach((reportKey) => { - if (ReportUtils.canUserPerformWriteAction(REPORTS_WITH_WORKSPACE_ROOMS[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(REPORTS_WITH_WORKSPACE_ROOMS[reportKey])) { + if (!ReportUtils.canUserPerformWriteAction(REPORTS_WITH_WORKSPACE_ROOMS[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(REPORTS_WITH_WORKSPACE_ROOMS[reportKey])) { return; } filteredReportsWithWorkspaceRooms[reportKey] = REPORTS_WITH_WORKSPACE_ROOMS[reportKey]; From b4c0c1de45cf18ea21893673a3dc6e38ca0066c6 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 19 Oct 2023 08:11:50 -1000 Subject: [PATCH 164/376] 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 165/376] 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 166/376] 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 167/376] 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 168/376] 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 169/376] 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 170/376] 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 171/376] 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 172/376] 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 173/376] 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 174/376] 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 175/376] 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 176/376] 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 196689db016c212d18b43e13b8316a17cdbc6193 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Fri, 20 Oct 2023 16:35:47 +0200 Subject: [PATCH 177/376] Fix propType --- src/components/CheckboxWithLabel.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/CheckboxWithLabel.js b/src/components/CheckboxWithLabel.js index 777c46f861aa..70fa972a5d2f 100644 --- a/src/components/CheckboxWithLabel.js +++ b/src/components/CheckboxWithLabel.js @@ -8,6 +8,7 @@ import Text from './Text'; import FormHelpMessage from './FormHelpMessage'; import variables from '../styles/variables'; import PressableWithFeedback from './Pressable/PressableWithFeedback'; +import refPropTypes from "./refPropTypes"; /** * Returns an error if the required props are not provided @@ -54,7 +55,7 @@ const propTypes = { defaultValue: PropTypes.bool, /** React ref being forwarded to the Checkbox input */ - forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(React.Component)})]), + forwardedRef: refPropTypes, /** The ID used to uniquely identify the input in a Form */ /* eslint-disable-next-line react/no-unused-prop-types */ From 60799d9b5c91a0ccefde0c747b1fde8649e64850 Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Fri, 20 Oct 2023 21:28:08 +0530 Subject: [PATCH 178/376] 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 179/376] 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 180/376] 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 181/376] 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 182/376] 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 0daa9f1dcc04a37f8516141ad25584f2fc2e8b82 Mon Sep 17 00:00:00 2001 From: jeet-dhandha Date: Sat, 21 Oct 2023 09:46:45 +0530 Subject: [PATCH 183/376] fix: console error old-dot to new-dot new workspace transition --- .../Navigators/CentralPaneNavigator.js | 2 +- src/libs/ReportUtils.js | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js index 64eadcbe06c3..9c2bd6820e85 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js +++ b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js @@ -18,7 +18,7 @@ function CentralPaneNavigator() { Date: Sat, 21 Oct 2023 19:28:18 +0530 Subject: [PATCH 184/376] 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 185/376] 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 186/376] 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 187/376] 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 188/376] 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 189/376] 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 190/376] 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 191/376] 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 192/376] 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 d0ea5b9175ddce96d464c28d6f13fd06d149f526 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Fri, 20 Oct 2023 10:59:05 +0200 Subject: [PATCH 193/376] Add FormProvider to NewTaskDescriptionPage --- src/pages/tasks/NewTaskDescriptionPage.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/tasks/NewTaskDescriptionPage.js b/src/pages/tasks/NewTaskDescriptionPage.js index 44fd4346538d..378adf6a9a41 100644 --- a/src/pages/tasks/NewTaskDescriptionPage.js +++ b/src/pages/tasks/NewTaskDescriptionPage.js @@ -10,7 +10,6 @@ import Navigation from '../../libs/Navigation/Navigation'; import ScreenWrapper from '../../components/ScreenWrapper'; import styles from '../../styles/styles'; import ONYXKEYS from '../../ONYXKEYS'; -import Form from '../../components/Form'; import TextInput from '../../components/TextInput'; import Permissions from '../../libs/Permissions'; import ROUTES from '../../ROUTES'; @@ -18,6 +17,8 @@ import * as Task from '../../libs/actions/Task'; import updateMultilineInputRange from '../../libs/UpdateMultilineInputRange'; import CONST from '../../CONST'; import * as Browser from '../../libs/Browser'; +import FormProvider from '../../components/Form/FormProvider'; +import InputWrapper from '../../components/Form/InputWrapper'; const propTypes = { /** Beta features list */ @@ -82,7 +83,7 @@ function NewTaskDescriptionPage(props) { onCloseButtonPress={() => Task.dismissModalAndClearOutTaskInfo()} onBackButtonPress={() => Navigation.goBack(ROUTES.NEW_TASK)} /> -
- -
+ ); From b9138d78d2bd82c7ac92a8fdea4e993bea733ce1 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Thu, 19 Oct 2023 19:03:08 +0200 Subject: [PATCH 194/376] 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 195/376] 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 d402f9c15e909c7aa834af87145015ea5e647dda Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Mon, 23 Oct 2023 13:10:18 +0200 Subject: [PATCH 196/376] Add FormProvider in NewTaskTitlePage --- src/pages/tasks/NewTaskTitlePage.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/tasks/NewTaskTitlePage.js b/src/pages/tasks/NewTaskTitlePage.js index 62eb1da4872a..2a8d776eaefd 100644 --- a/src/pages/tasks/NewTaskTitlePage.js +++ b/src/pages/tasks/NewTaskTitlePage.js @@ -10,12 +10,13 @@ import ScreenWrapper from '../../components/ScreenWrapper'; import styles from '../../styles/styles'; import ONYXKEYS from '../../ONYXKEYS'; import * as ErrorUtils from '../../libs/ErrorUtils'; -import Form from '../../components/Form'; import TextInput from '../../components/TextInput'; import Permissions from '../../libs/Permissions'; import ROUTES from '../../ROUTES'; import * as Task from '../../libs/actions/Task'; import CONST from '../../CONST'; +import FormProvider from '../../components/Form/FormProvider'; +import InputWrapper from '../../components/Form/InputWrapper'; const propTypes = { /** Beta features list */ @@ -85,7 +86,7 @@ function NewTaskTitlePage(props) { shouldShowBackButton onBackButtonPress={() => Navigation.goBack(ROUTES.NEW_TASK)} /> -
- (inputRef.current = el)} @@ -103,7 +105,7 @@ function NewTaskTitlePage(props) { accessibilityLabel={props.translate('task.title')} /> -
+ ); } From 7ce37a6ea8f7203b146f51b98be73ad4eef096ba Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Mon, 23 Oct 2023 13:21:57 +0200 Subject: [PATCH 197/376] 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 15:07:23 +0200 Subject: [PATCH 198/376] Add FormProvider in MoneyRequestDescriptionPage --- src/pages/iou/MoneyRequestDescriptionPage.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/iou/MoneyRequestDescriptionPage.js b/src/pages/iou/MoneyRequestDescriptionPage.js index cfdbb60b4f0d..4c2ae3bdf4ae 100644 --- a/src/pages/iou/MoneyRequestDescriptionPage.js +++ b/src/pages/iou/MoneyRequestDescriptionPage.js @@ -9,7 +9,6 @@ import {iouPropTypes, iouDefaultProps} from './propTypes'; import TextInput from '../../components/TextInput'; import ScreenWrapper from '../../components/ScreenWrapper'; import HeaderWithBackButton from '../../components/HeaderWithBackButton'; -import Form from '../../components/Form'; import ONYXKEYS from '../../ONYXKEYS'; import styles from '../../styles/styles'; import Navigation from '../../libs/Navigation/Navigation'; @@ -20,6 +19,8 @@ import CONST from '../../CONST'; import useLocalize from '../../hooks/useLocalize'; import updateMultilineInputRange from '../../libs/UpdateMultilineInputRange'; import * as Browser from '../../libs/Browser'; +import FormProvider from '../../components/Form/FormProvider'; +import InputWrapper from '../../components/Form/InputWrapper'; const propTypes = { /** Onyx Props */ @@ -115,7 +116,7 @@ function MoneyRequestDescriptionPage({iou, route, selectedTab}) { title={translate('common.description')} onBackButtonPress={() => navigateBack()} /> -
updateComment(value)} @@ -123,7 +124,8 @@ function MoneyRequestDescriptionPage({iou, route, selectedTab}) { enabledWhenOffline > - -
+ ); From b418a4cb7561b597c3423b5602cbeb491b8d63fc Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Mon, 23 Oct 2023 15:35:53 +0200 Subject: [PATCH 199/376] Code review changes --- src/components/AddressSearch/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 7c754a33e370..a4f712435af3 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -137,9 +137,6 @@ const defaultProps = { resultTypes: 'address', }; -// Do not convert to class component! It's been tried before and presents more challenges than it's worth. -// Relevant thread: https://expensify.slack.com/archives/C03TQ48KC/p1634088400387400 -// Reference: https://github.com/FaridSafi/react-native-google-places-autocomplete/issues/609#issuecomment-886133839 function AddressSearch({ canUseCurrentLocation, containerStyles, @@ -372,7 +369,7 @@ function AddressSearch({ [isTyping, translate, network.isOffline], ); - const listLoader = useCallback( + const listLoader = useMemo( () => ( Date: Mon, 23 Oct 2023 21:25:31 +0700 Subject: [PATCH 200/376] 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 201/376] 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 202/376] 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 203/376] 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 204/376] 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 205/376] 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')}
- + + + - -