From 8ac9d8c5ad401b109fbaf546348c49855ff9ee94 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 10 May 2023 13:32:28 -1000 Subject: [PATCH 001/171] method to get most recent blahblah --- src/libs/ReportActionsUtils.js | 34 ++++++++++++++++++++++++++++++++++ src/libs/actions/App.js | 9 ++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 86034d8a14ef..2534ebb92df9 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -285,6 +285,39 @@ function getLinkedTransactionID(reportID, reportActionID) { return reportAction.originalMessage.IOUTransactionID; } +/** + * @returns {object} + */ +function getMostRecentStoredReportAction() { + let mostRecentReportAction; + let mostRecentReportActionCreated = new Date(0).toISOString(); + + // Flatten all the actions + // Loop over them all to find the one that is the most recent + const flatReportActions = _.flatten(_.map(allReportActions, actions => _.values(actions))); + _.each(flatReportActions, (action) => { + // Pending actions should not be counted here as a user could create a comment or some other action while offline and the server might know about + // messages they have not seen yet. + if (!_.isEmpty(action.pendingAction)) { + return; + } + + // All actions should have this, but if not they are not useful to us. + if (!action.created) { + return; + } + + if (action.created < mostRecentReportActionCreated) { + return; + } + + mostRecentReportActionCreated = action.created; + mostRecentReportAction = action; + }); + + return mostRecentReportAction; +} + export { getSortedReportActions, getLastVisibleAction, @@ -298,4 +331,5 @@ export { getLastClosedReportAction, getLatestReportActionFromOnyxData, getLinkedTransactionID, + getMostRecentStoredReportAction, }; diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index e2d49f11c69e..14eb8a03078a 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -15,6 +15,7 @@ import ROUTES from '../../ROUTES'; import * as SessionUtils from '../SessionUtils'; import getCurrentUrl from '../Navigation/currentUrl'; import * as Session from './Session'; +import * as ReportActionsUtils from '../ReportActionsUtils'; let currentUserAccountID; let currentUserEmail = ''; @@ -184,9 +185,15 @@ function openApp() { * Refreshes data when the app reconnects */ function reconnectApp() { + // When the app reconnects we do a fast "sync" of the LHN and only return chats that have new messages. We achieve this by sending the most recent reportActionID + // we have locally. And then only update the user about chats with messages that have occurred after that reportActionID. + // + // 1. Look through the local report actions to find the most recent reportActionID out of all of them + // 2. Send this to the server so that it can compute which chats are critical for the user to see and then follow up with a more complete sync later + const mostRecentReportAction = ReportActionsUtils.getMostRecentStoredReportAction(); API.write( CONST.NETWORK.COMMAND.RECONNECT_APP, - {policyIDList: getNonOptimisticPolicyIDs(allPolicies)}, + {policyIDList: getNonOptimisticPolicyIDs(allPolicies), mostRecentReportActionID: mostRecentReportAction.reportActionID}, { optimisticData: [ { From 72dcafca73e635bcca1c94947ecbc978f724c443 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 11 May 2023 12:20:33 -1000 Subject: [PATCH 002/171] add getMostRecentReportActionLastModified() --- src/libs/ReportActionsUtils.js | 20 ++++++++++---------- src/libs/actions/App.js | 7 +++---- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 2534ebb92df9..22f8db27f4e4 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -286,11 +286,11 @@ function getLinkedTransactionID(reportID, reportActionID) { } /** - * @returns {object} + * @returns {string} */ -function getMostRecentStoredReportAction() { - let mostRecentReportAction; - let mostRecentReportActionCreated = new Date(0).toISOString(); +function getMostRecentReportActionLastModified() { + let mostRecentlyModifiedReportAction; + let mostRecentReportActionLastModified = new Date(0).toISOString(); // Flatten all the actions // Loop over them all to find the one that is the most recent @@ -303,19 +303,19 @@ function getMostRecentStoredReportAction() { } // All actions should have this, but if not they are not useful to us. - if (!action.created) { + if (!action.lastModified) { return; } - if (action.created < mostRecentReportActionCreated) { + if (action.lastModified < mostRecentReportActionLastModified) { return; } - mostRecentReportActionCreated = action.created; - mostRecentReportAction = action; + mostRecentReportActionLastModified = action.lastModified; + mostRecentlyModifiedReportAction = action; }); - return mostRecentReportAction; + return mostRecentlyModifiedReportAction; } export { @@ -331,5 +331,5 @@ export { getLastClosedReportAction, getLatestReportActionFromOnyxData, getLinkedTransactionID, - getMostRecentStoredReportAction, + getMostRecentReportActionLastModified, }; diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 14eb8a03078a..aa83a8a11d51 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -188,12 +188,11 @@ function reconnectApp() { // When the app reconnects we do a fast "sync" of the LHN and only return chats that have new messages. We achieve this by sending the most recent reportActionID // we have locally. And then only update the user about chats with messages that have occurred after that reportActionID. // - // 1. Look through the local report actions to find the most recent reportActionID out of all of them - // 2. Send this to the server so that it can compute which chats are critical for the user to see and then follow up with a more complete sync later - const mostRecentReportAction = ReportActionsUtils.getMostRecentStoredReportAction(); + // - Look through the local report actions to find the most recently modified report action + // - We Send this to the server so that it can compute which chats are critical for the user to see so we can update them in the LHN API.write( CONST.NETWORK.COMMAND.RECONNECT_APP, - {policyIDList: getNonOptimisticPolicyIDs(allPolicies), mostRecentReportActionID: mostRecentReportAction.reportActionID}, + {policyIDList: getNonOptimisticPolicyIDs(allPolicies), mostRecentReportActionLastModified: ReportActionsUtils.getMostRecentReportActionLastModified()}, { optimisticData: [ { From 3b680b0efafabf4edfe3c391d2be3aaabc4ecddc Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 11 May 2023 12:36:17 -1000 Subject: [PATCH 003/171] Actually send the timestamp --- src/libs/ReportActionsUtils.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 510f48d3a555..427376a7f95f 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -289,7 +289,7 @@ function getLinkedTransactionID(reportID, reportActionID) { * @returns {string} */ function getMostRecentReportActionLastModified() { - let mostRecentlyModifiedReportAction; + // Start with the oldest date possible let mostRecentReportActionLastModified = new Date(0).toISOString(); // Flatten all the actions @@ -312,10 +312,9 @@ function getMostRecentReportActionLastModified() { } mostRecentReportActionLastModified = action.lastModified; - mostRecentlyModifiedReportAction = action; }); - return mostRecentlyModifiedReportAction; + return mostRecentReportActionLastModified; } function isCreatedTaskReportAction(reportAction) { From e17680640320c7e313dee45b2d0505de96e10d46 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 11 May 2023 14:29:00 -1000 Subject: [PATCH 004/171] Subscribe to correct key --- src/libs/ReportActionsUtils.js | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 427376a7f95f..7734e4ea69d8 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -11,6 +11,19 @@ import ONYXKEYS from '../ONYXKEYS'; import Log from './Log'; import isReportMessageAttachment from './isReportMessageAttachment'; +const allReports = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + callback: (report, key) => { + if (!key || !report) { + return; + } + + const reportID = CollectionUtils.extractCollectionItemID(key); + allReports[reportID] = report; + }, +}); + const allReportActions = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, @@ -302,16 +315,27 @@ function getMostRecentReportActionLastModified() { return; } - // All actions should have this, but if not they are not useful to us. - if (!action.lastModified) { + let lastModified = action.lastModified; + if (!lastModified) { + lastModified = action.created; + } + + if (lastModified < mostRecentReportActionLastModified) { return; } - if (action.lastModified < mostRecentReportActionLastModified) { + mostRecentReportActionLastModified = lastModified; + }); + + // We might not have actions so we also look at the report objects to see if any have a lastVisibleActionLastModified that is more recent. We don't need to get + // any reports that have been updated before either a recently updated report or reportAction as we should be up to date on these + _.each(allReports, (report) => { + const reportLastVisibleActionLastModified = report.lastVisibleActionLastModified || report.lastVisibleActionCreated; + if (reportLastVisibleActionLastModified < mostRecentReportActionLastModified) { return; } - mostRecentReportActionLastModified = action.lastModified; + mostRecentReportActionLastModified = reportLastVisibleActionLastModified; }); return mostRecentReportActionLastModified; From 32e19d002a17748f6ad7279137cd0517b62cf7e0 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 19 May 2023 12:09:29 -1000 Subject: [PATCH 005/171] Run prettier --- src/libs/ReportActionsUtils.js | 74 +++++++++++++++++----------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 541f5197f6d0..ea8cca24011a 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -383,43 +383,43 @@ function getLinkedTransactionID(reportID, reportActionID) { * @returns {string} */ function getMostRecentReportActionLastModified() { - // Start with the oldest date possible - let mostRecentReportActionLastModified = new Date(0).toISOString(); - - // Flatten all the actions - // Loop over them all to find the one that is the most recent - const flatReportActions = _.flatten(_.map(allReportActions, actions => _.values(actions))); - _.each(flatReportActions, (action) => { - // Pending actions should not be counted here as a user could create a comment or some other action while offline and the server might know about - // messages they have not seen yet. - if (!_.isEmpty(action.pendingAction)) { - return; - } - - let lastModified = action.lastModified; - if (!lastModified) { - lastModified = action.created; - } - - if (lastModified < mostRecentReportActionLastModified) { - return; - } - - mostRecentReportActionLastModified = lastModified; - }); - - // We might not have actions so we also look at the report objects to see if any have a lastVisibleActionLastModified that is more recent. We don't need to get - // any reports that have been updated before either a recently updated report or reportAction as we should be up to date on these - _.each(allReports, (report) => { - const reportLastVisibleActionLastModified = report.lastVisibleActionLastModified || report.lastVisibleActionCreated; - if (reportLastVisibleActionLastModified < mostRecentReportActionLastModified) { - return; - } - - mostRecentReportActionLastModified = reportLastVisibleActionLastModified; - }); - - return mostRecentReportActionLastModified; + // Start with the oldest date possible + let mostRecentReportActionLastModified = new Date(0).toISOString(); + + // Flatten all the actions + // Loop over them all to find the one that is the most recent + const flatReportActions = _.flatten(_.map(allReportActions, (actions) => _.values(actions))); + _.each(flatReportActions, (action) => { + // Pending actions should not be counted here as a user could create a comment or some other action while offline and the server might know about + // messages they have not seen yet. + if (!_.isEmpty(action.pendingAction)) { + return; + } + + let lastModified = action.lastModified; + if (!lastModified) { + lastModified = action.created; + } + + if (lastModified < mostRecentReportActionLastModified) { + return; + } + + mostRecentReportActionLastModified = lastModified; + }); + + // We might not have actions so we also look at the report objects to see if any have a lastVisibleActionLastModified that is more recent. We don't need to get + // any reports that have been updated before either a recently updated report or reportAction as we should be up to date on these + _.each(allReports, (report) => { + const reportLastVisibleActionLastModified = report.lastVisibleActionLastModified || report.lastVisibleActionCreated; + if (reportLastVisibleActionLastModified < mostRecentReportActionLastModified) { + return; + } + + mostRecentReportActionLastModified = reportLastVisibleActionLastModified; + }); + + return mostRecentReportActionLastModified; } /** From c7b9245f49b38fae0dd03962efb84c0ca9c6bfe1 Mon Sep 17 00:00:00 2001 From: Nikhil Vats Date: Mon, 5 Jun 2023 19:28:38 +0530 Subject: [PATCH 006/171] Migrate NewChatPage to functional component --- src/pages/NewChatPage.js | 266 +++++++++++++++++---------------------- 1 file changed, 117 insertions(+), 149 deletions(-) diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 1765c700221e..d241c46415f4 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -1,5 +1,5 @@ import _ from 'underscore'; -import React, {Component} from 'react'; +import React, {useState, useEffect, useMemo} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; @@ -44,80 +44,57 @@ const defaultProps = { reports: {}, }; -class NewChatPage extends Component { - constructor(props) { - super(props); - - this.toggleOption = this.toggleOption.bind(this); - this.createChat = this.createChat.bind(this); - this.createGroup = this.createGroup.bind(this); - this.updateOptionsWithSearchTerm = this.updateOptionsWithSearchTerm.bind(this); - this.excludedGroupEmails = _.without(CONST.EXPENSIFY_EMAILS, CONST.EMAIL.CONCIERGE); - - const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( - props.reports, - props.personalDetails, - props.betas, - '', - [], - this.props.isGroupChat ? this.excludedGroupEmails : [], - ); - this.state = { - searchTerm: '', - recentReports, - personalDetails, - selectedOptions: [], - userToInvite, - }; - } - - componentDidUpdate(prevProps) { - if (_.isEqual(prevProps.reports, this.props.reports) && _.isEqual(prevProps.personalDetails, this.props.personalDetails)) { - return; - } - this.updateOptionsWithSearchTerm(this.state.searchTerm); - } - - /** - * Returns the sections needed for the OptionsSelector - * - * @param {Boolean} maxParticipantsReached - * @returns {Array} - */ - getSections(maxParticipantsReached) { - const sections = []; +const NewChatPage = (props) => { + const [searchTerm, setSearchTerm] = useState(''); + const [filteredRecentReports, setFilteredRecentReports] = useState([]); + const [filteredPersonalDetails, setFilteredPersonalDetails] = useState([]); + const [filteredUserToInvite, setFilteredUserToInvite] = useState(); + const [selectedOptions, setSelectedOptions] = useState([]); + + const excludedGroupEmails = _.without(CONST.EXPENSIFY_EMAILS, CONST.EMAIL.CONCIERGE); + const maxParticipantsReached = selectedOptions.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; + const headerMessage = OptionsListUtils.getHeaderMessage( + filteredPersonalDetails.length + filteredRecentReports.length !== 0, + Boolean(filteredUserToInvite), + searchTerm, + maxParticipantsReached, + ); + const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(props.personalDetails); + + const sections = useMemo(() => { + const sectionsList = []; let indexOffset = 0; - if (this.props.isGroupChat) { - sections.push({ + if (props.isGroupChat) { + sectionsList.push({ title: undefined, - data: this.state.selectedOptions, - shouldShow: !_.isEmpty(this.state.selectedOptions), + data: selectedOptions, + shouldShow: !_.isEmpty(selectedOptions), indexOffset, }); - indexOffset += this.state.selectedOptions.length; + indexOffset += selectedOptions.length; if (maxParticipantsReached) { - return sections; + return sectionsList; } } // Filtering out selected users from the search results - const filterText = _.reduce(this.state.selectedOptions, (str, {login}) => `${str} ${login}`, ''); - const recentReportsWithoutSelected = _.filter(this.state.recentReports, ({login}) => !filterText.includes(login)); - const personalDetailsWithoutSelected = _.filter(this.state.personalDetails, ({login}) => !filterText.includes(login)); - const hasUnselectedUserToInvite = this.state.userToInvite && !filterText.includes(this.state.userToInvite.login); + const filterText = _.reduce(selectedOptions, (str, {login}) => `${str} ${login}`, ''); + const recentReportsWithoutSelected = _.filter(filteredRecentReports, ({login}) => !filterText.includes(login)); + const personalDetailsWithoutSelected = _.filter(filteredPersonalDetails, ({login}) => !filterText.includes(login)); + const hasUnselectedUserToInvite = filteredUserToInvite && !filterText.includes(filteredUserToInvite.login); - sections.push({ - title: this.props.translate('common.recents'), + sectionsList.push({ + title: props.translate('common.recents'), data: recentReportsWithoutSelected, shouldShow: !_.isEmpty(recentReportsWithoutSelected), indexOffset, }); indexOffset += recentReportsWithoutSelected.length; - sections.push({ - title: this.props.translate('common.contacts'), + sectionsList.push({ + title: props.translate('common.contacts'), data: personalDetailsWithoutSelected, shouldShow: !_.isEmpty(personalDetailsWithoutSelected), indexOffset, @@ -125,67 +102,61 @@ class NewChatPage extends Component { indexOffset += personalDetailsWithoutSelected.length; if (hasUnselectedUserToInvite) { - sections.push({ + sectionsList.push({ title: undefined, - data: [this.state.userToInvite], + data: [filteredUserToInvite], shouldShow: true, indexOffset, }); } - return sections; - } + return sectionsList; + // eslint-disable-next-line react-hooks/exhaustive-deps -- to avoid destructuring props and adding all 'props' as a dependency + }, [props.isGroupChat, props.translate, selectedOptions, filteredRecentReports, filteredPersonalDetails, filteredUserToInvite, maxParticipantsReached]); - updateOptionsWithSearchTerm(searchTerm = '') { + const updateOptionsWithSearchTerm = (newSearchTerm = '') => { const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( - this.props.reports, - this.props.personalDetails, - this.props.betas, - searchTerm, + props.reports, + props.personalDetails, + props.betas, + newSearchTerm, [], - this.props.isGroupChat ? this.excludedGroupEmails : [], + props.isGroupChat ? excludedGroupEmails : [], ); - this.setState({ - searchTerm, - userToInvite, - recentReports, - personalDetails, - }); - } + setSearchTerm(newSearchTerm); + setFilteredRecentReports(recentReports); + setFilteredPersonalDetails(personalDetails); + setFilteredUserToInvite(userToInvite); + }; /** * Removes a selected option from list if already selected. If not already selected add this option to the list. * @param {Object} option */ - toggleOption(option) { - this.setState((prevState) => { - const isOptionInList = _.some(prevState.selectedOptions, (selectedOption) => selectedOption.login === option.login); + const toggleOption = (option) => { + const isOptionInList = _.some(selectedOptions, (selectedOption) => selectedOption.login === option.login); - let newSelectedOptions; + let newSelectedOptions; - if (isOptionInList) { - newSelectedOptions = _.reject(prevState.selectedOptions, (selectedOption) => selectedOption.login === option.login); - } else { - newSelectedOptions = [...prevState.selectedOptions, option]; - } + if (isOptionInList) { + newSelectedOptions = _.reject(selectedOptions, (selectedOption) => selectedOption.login === option.login); + } else { + newSelectedOptions = [...selectedOptions, option]; + } - const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( - this.props.reports, - this.props.personalDetails, - this.props.betas, - prevState.searchTerm, - [], - this.excludedGroupEmails, - ); + const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( + props.reports, + props.personalDetails, + props.betas, + searchTerm, + [], + excludedGroupEmails, + ); - return { - selectedOptions: newSelectedOptions, - recentReports, - personalDetails, - userToInvite, - searchTerm: prevState.searchTerm, - }; - }); + setSelectedOptions(newSelectedOptions); + setFilteredRecentReports(recentReports); + setFilteredPersonalDetails(personalDetails); + setFilteredUserToInvite(userToInvite); } /** @@ -194,7 +165,7 @@ class NewChatPage extends Component { * * @param {Object} option */ - createChat(option) { + const createChat = (option) => { Report.navigateToAndOpenReport([option.login]); } @@ -202,68 +173,65 @@ class NewChatPage extends Component { * Creates a new group chat with all the selected options and the current user, * or navigates to the existing chat if one with those participants already exists. */ - createGroup() { - if (!this.props.isGroupChat) { + const createGroup = () => { + if (!props.isGroupChat) { return; } - const userLogins = _.pluck(this.state.selectedOptions, 'login'); + const userLogins = _.pluck(selectedOptions, 'login'); if (userLogins.length < 1) { return; } Report.navigateToAndOpenReport(userLogins); } - render() { - const maxParticipantsReached = this.state.selectedOptions.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; - const sections = this.getSections(maxParticipantsReached); - const headerMessage = OptionsListUtils.getHeaderMessage( - this.state.personalDetails.length + this.state.recentReports.length !== 0, - Boolean(this.state.userToInvite), - this.state.searchTerm, - maxParticipantsReached, - ); - const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(this.props.personalDetails); - - return ( - - {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( - <> - Navigation.dismissModal(true)} + useEffect(() => { + updateOptionsWithSearchTerm(searchTerm); + // all dependencies are not added below - + // 1. searchTerm - when searchTerm changes updateOptionsWithSearchTerm is called by OptionsSelector's onChangeText prop + // 2. updateOptionsWithSearchTerm - it will change its reference upon each rerender unnecessarily + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [props.reports, props.personalDetails]); + + return ( + + {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( + <> + Navigation.dismissModal(true)} + /> + 0 ? safeAreaPaddingBottomStyle : {}]}> + (props.isGroupChat ? toggleOption(option) : createChat(option))} + onChangeText={updateOptionsWithSearchTerm} + headerMessage={headerMessage} + boldStyle + shouldFocusOnSelectRow={props.isGroupChat} + shouldShowConfirmButton={props.isGroupChat} + shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} + confirmButtonText={props.translate('newChatPage.createGroup')} + onConfirmSelection={createGroup} + textInputLabel={props.translate('optionsSelector.nameEmailOrPhoneNumber')} + safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} /> - 0 ? safeAreaPaddingBottomStyle : {}]}> - (this.props.isGroupChat ? this.toggleOption(option) : this.createChat(option))} - onChangeText={this.updateOptionsWithSearchTerm} - headerMessage={headerMessage} - boldStyle - shouldFocusOnSelectRow={this.props.isGroupChat} - shouldShowConfirmButton={this.props.isGroupChat} - shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} - confirmButtonText={this.props.translate('newChatPage.createGroup')} - onConfirmSelection={this.createGroup} - textInputLabel={this.props.translate('optionsSelector.nameEmailOrPhoneNumber')} - safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} - /> - - - )} - - ); - } -} + + + )} + + ); +}; NewChatPage.propTypes = propTypes; NewChatPage.defaultProps = defaultProps; +NewChatPage.displayName = 'NewChatPage'; export default compose( withLocalize, From a357a85e5f415cd9cf6b4af5982c77659fe121e9 Mon Sep 17 00:00:00 2001 From: Nikhil Vats Date: Mon, 5 Jun 2023 19:49:45 +0530 Subject: [PATCH 007/171] fix linting issues --- src/pages/NewChatPage.js | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index d241c46415f4..e43d0a81d586 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -144,20 +144,13 @@ const NewChatPage = (props) => { newSelectedOptions = [...selectedOptions, option]; } - const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( - props.reports, - props.personalDetails, - props.betas, - searchTerm, - [], - excludedGroupEmails, - ); + const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions(props.reports, props.personalDetails, props.betas, searchTerm, [], excludedGroupEmails); setSelectedOptions(newSelectedOptions); setFilteredRecentReports(recentReports); setFilteredPersonalDetails(personalDetails); setFilteredUserToInvite(userToInvite); - } + }; /** * Creates a new 1:1 chat with the option and the current user, @@ -167,7 +160,7 @@ const NewChatPage = (props) => { */ const createChat = (option) => { Report.navigateToAndOpenReport([option.login]); - } + }; /** * Creates a new group chat with all the selected options and the current user, @@ -183,7 +176,7 @@ const NewChatPage = (props) => { return; } Report.navigateToAndOpenReport(userLogins); - } + }; useEffect(() => { updateOptionsWithSearchTerm(searchTerm); From 7804df230f1d5f3deeca9d7fb113c3a3a17782ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 5 Jun 2023 18:57:49 +0200 Subject: [PATCH 008/171] initial rewrite of BaseTextInput to FC --- src/components/TextInput/BaseTextInput.js | 658 +++++++++--------- .../TextInput/baseTextInputPropTypes.js | 12 +- 2 files changed, 344 insertions(+), 326 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 91ab2162674f..12cbd9a1fcd6 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -1,5 +1,5 @@ import _ from 'underscore'; -import React, {Component} from 'react'; +import React, {useState, useRef, useEffect, useCallback} from 'react'; import {Animated, View, AppState, Keyboard, StyleSheet} from 'react-native'; import Str from 'expensify-common/lib/str'; import RNTextInput from '../RNTextInput'; @@ -21,64 +21,72 @@ import isInputAutoFilled from '../../libs/isInputAutoFilled'; import * as Pressables from '../Pressable'; const PressableWithoutFeedback = Pressables.PressableWithoutFeedback; -class BaseTextInput extends Component { - constructor(props) { - super(props); - - const value = props.value || props.defaultValue || ''; - const activeLabel = props.forceActiveLabel || value.length > 0 || Boolean(props.prefixCharacter); - - this.state = { - isFocused: false, - labelTranslateY: new Animated.Value(activeLabel ? styleConst.ACTIVE_LABEL_TRANSLATE_Y : styleConst.INACTIVE_LABEL_TRANSLATE_Y), - labelScale: new Animated.Value(activeLabel ? styleConst.ACTIVE_LABEL_SCALE : styleConst.INACTIVE_LABEL_SCALE), - passwordHidden: props.secureTextEntry, - textInputWidth: 0, - prefixWidth: 0, - selection: props.selection, - height: variables.componentSizeLarge, - - // Value should be kept in state for the autoGrow feature to work - https://github.com/Expensify/App/pull/8232#issuecomment-1077282006 - value, - }; - this.input = null; - this.isLabelActive = activeLabel; - this.onPress = this.onPress.bind(this); - this.onFocus = this.onFocus.bind(this); - this.onBlur = this.onBlur.bind(this); - this.setValue = this.setValue.bind(this); - this.togglePasswordVisibility = this.togglePasswordVisibility.bind(this); - this.dismissKeyboardWhenBackgrounded = this.dismissKeyboardWhenBackgrounded.bind(this); - this.storePrefixLayoutDimensions = this.storePrefixLayoutDimensions.bind(this); +function dismissKeyboardWhenBackgrounded(nextAppState) { + if (!nextAppState.match(/inactive|background/)) { + return; } - componentDidMount() { - if (this.props.disableKeyboard) { - this.appStateSubscription = AppState.addEventListener('change', this.dismissKeyboardWhenBackgrounded); + Keyboard.dismiss(); +} + +function BaseTextInput(props) { + const initialActiveLabel = props.forceActiveLabel || props.value.length > 0 || Boolean(props.prefixCharacter); + + const [isFocused, setIsFocused] = useState(false); + const [labelTranslateY] = useState(() => new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_TRANSLATE_Y : styleConst.INACTIVE_LABEL_TRANSLATE_Y)); + const [labelScale] = useState(() => new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_SCALE : styleConst.INACTIVE_LABEL_SCALE)); + const [passwordHidden, setPasswordHidden] = useState(props.secureTextEntry); + const [textInputWidth, setTextInputWidth] = useState(0); + const [textInputHeight, setTextInputHeight] = useState(); + const [prefixWidth, setPrefixWidth] = useState(0); + const [height, setHeight] = useState(variables.componentSizeLarge); + const [width, setWidth] = useState(); + + const input = useRef(null); + const isLabelActive = useRef(initialActiveLabel); + + useEffect(() => { + let appStateSubscription; + if (props.disableKeyboard) { + appStateSubscription = AppState.addEventListener('change', dismissKeyboardWhenBackgrounded); } // We are manually managing focus to prevent this issue: https://github.com/Expensify/App/issues/4514 - if (!this.props.autoFocus || !this.input) { + if (!props.autoFocus || !input.current) { return; } - if (this.props.shouldDelayFocus) { - this.focusTimeout = setTimeout(() => this.input.focus(), CONST.ANIMATED_TRANSITION); + let focusTimeout; + if (props.shouldDelayFocus) { + focusTimeout = setTimeout(() => input.current.focus(), CONST.ANIMATED_TRANSITION); return; } - this.input.focus(); - } + input.current.focus(); + + return () => { + if (focusTimeout) { + clearTimeout(focusTimeout); + } + + if (!props.disableKeyboard || !appStateSubscription) { + return; + } + + appStateSubscription.remove(); + }; + }, [props.autoFocus, props.disableKeyboard, props.shouldDelayFocus]); - componentDidUpdate(prevProps) { + /* + componentDidUpdate(prevProps) { // Activate or deactivate the label when value is changed programmatically from outside - const inputValue = _.isUndefined(this.props.value) ? this.input.value : this.props.value; - if ((_.isUndefined(inputValue) || this.state.value === inputValue) && _.isEqual(prevProps.selection, this.props.selection)) { + const inputValue = _.isUndefined(props.value) ? this.input.value : props.value; + if ((_.isUndefined(inputValue) || props.value === inputValue) && _.isEqual(prevProps.selection, props.selection)) { return; } // eslint-disable-next-line react/no-did-update-set-state - this.setState({value: inputValue, selection: this.props.selection}); + this.setState({value: inputValue, selection: props.selection}); // In some cases, When the value prop is empty, it is not properly updated on the TextInput due to its uncontrolled nature, thus manually clearing the TextInput. if (inputValue === '') { @@ -87,315 +95,317 @@ class BaseTextInput extends Component { if (inputValue) { this.activateLabel(); - } else if (!this.state.isFocused) { + } else if (!props.isFocused) { this.deactivateLabel(); } } - - componentWillUnmount() { - if (this.focusTimeout) { - clearTimeout(this.focusTimeout); - } - - if (!this.props.disableKeyboard || !this.appStateSubscription) { + */ + + const propIsDisabled = props.disabled; + const propOnPress = props.onPress; + const onPress = useCallback( + (event) => { + if (propIsDisabled) { + return; + } + + if (propOnPress) { + propOnPress(event); + } + + if (!event.isDefaultPrevented()) { + input.current.focus(); + } + }, + [propIsDisabled, propOnPress], + ); + + const animateLabel = useCallback( + (translateY, scale) => { + Animated.parallel([ + Animated.spring(props.labelTranslateY, { + toValue: translateY, + duration: styleConst.LABEL_ANIMATION_DURATION, + useNativeDriver: true, + }), + Animated.spring(props.labelScale, { + toValue: scale, + duration: styleConst.LABEL_ANIMATION_DURATION, + useNativeDriver: true, + }), + ]).start(); + }, + [props.labelScale, props.labelTranslateY], + ); + + const activateLabel = useCallback(() => { + if (props.value.length < 0 || isLabelActive) { return; } - this.appStateSubscription.remove(); - } + animateLabel(styleConst.ACTIVE_LABEL_TRANSLATE_Y, styleConst.ACTIVE_LABEL_SCALE); + isLabelActive.current = true; + }, [animateLabel, props.value.length]); - onPress(event) { - if (this.props.disabled) { + const deactivateLabel = useCallback(() => { + if (props.forceActiveLabel || props.value.length !== 0 || props.prefixCharacter) { return; } - if (this.props.onPress) { - this.props.onPress(event); - } - - if (!event.isDefaultPrevented()) { - this.input.focus(); - } - } - - onFocus(event) { - if (this.props.onFocus) { - this.props.onFocus(event); - } - this.setState({isFocused: true}); - this.activateLabel(); - } - - onBlur(event) { - if (this.props.onBlur) { - this.props.onBlur(event); - } - this.setState({isFocused: false}); - - // If the text has been supplied by Chrome autofill, the value state is not synced with the value - // as Chrome doesn't trigger a change event. When there is autofill text, don't deactivate label. - if (!isInputAutoFilled(this.input)) { - this.deactivateLabel(); - } - } - + animateLabel(styleConst.INACTIVE_LABEL_TRANSLATE_Y, styleConst.INACTIVE_LABEL_SCALE); + isLabelActive.current = false; + }, [animateLabel, props.forceActiveLabel, props.prefixCharacter, props.value.length]); + + const propOnFocus = props.onFocus; + const onFocus = useCallback( + (event) => { + if (propOnFocus) { + propOnFocus(event); + } + setIsFocused(true); + activateLabel(); + }, + [activateLabel, propOnFocus], + ); + + const propOnBlur = props.onBlur; + const onBlur = useCallback( + (event) => { + if (propOnBlur) { + propOnBlur(event); + } + setIsFocused(false); + + // If the text has been supplied by Chrome autofill, the value state is not synced with the value + // as Chrome doesn't trigger a change event. When there is autofill text, don't deactivate label. + if (!isInputAutoFilled(input.current)) { + deactivateLabel(); + } + }, + [deactivateLabel, propOnBlur], + ); + + const onLayout = useCallback( + (event) => { + if (!props.autoGrowHeight && props.multiline) { + return; + } + + const layout = event.nativeEvent.layout; + + setWidth((prevWidth) => (props.autoGrowHeight ? layout.width : prevWidth)); + setHeight((prevHeight) => (!props.multiline ? layout.height : prevHeight)); + }, + [props.autoGrowHeight, props.multiline], + ); + + // I feel like this is the region where imperative functions are starting: + + const propOnInputChange = props.onInputChange; /** * Set Value & activateLabel * * @param {String} value * @memberof BaseTextInput */ - setValue(value) { - if (this.props.onInputChange) { - this.props.onInputChange(value); - } - this.setState({value}); - Str.result(this.props.onChangeText, value); - this.activateLabel(); - } - - activateLabel() { - if (this.state.value.length < 0 || this.isLabelActive) { - return; - } - - this.animateLabel(styleConst.ACTIVE_LABEL_TRANSLATE_Y, styleConst.ACTIVE_LABEL_SCALE); - this.isLabelActive = true; - } - - deactivateLabel() { - if (this.props.forceActiveLabel || this.state.value.length !== 0 || this.props.prefixCharacter) { - return; - } - - this.animateLabel(styleConst.INACTIVE_LABEL_TRANSLATE_Y, styleConst.INACTIVE_LABEL_SCALE); - this.isLabelActive = false; - } - - dismissKeyboardWhenBackgrounded(nextAppState) { - if (!nextAppState.match(/inactive|background/)) { - return; - } - - Keyboard.dismiss(); - } - - animateLabel(translateY, scale) { - Animated.parallel([ - Animated.spring(this.state.labelTranslateY, { - toValue: translateY, - duration: styleConst.LABEL_ANIMATION_DURATION, - useNativeDriver: true, - }), - Animated.spring(this.state.labelScale, { - toValue: scale, - duration: styleConst.LABEL_ANIMATION_DURATION, - useNativeDriver: true, - }), - ]).start(); - } - - togglePasswordVisibility() { - this.setState((prevState) => ({passwordHidden: !prevState.passwordHidden})); - } - - storePrefixLayoutDimensions(event) { - this.setState({prefixWidth: Math.abs(event.nativeEvent.layout.width)}); - } - - render() { - // eslint-disable-next-line react/forbid-foreign-prop-types - const inputProps = _.omit(this.props, _.keys(baseTextInputPropTypes.propTypes)); - const hasLabel = Boolean(this.props.label.length); - const isEditable = _.isUndefined(this.props.editable) ? !this.props.disabled : this.props.editable; - const inputHelpText = this.props.errorText || this.props.hint; - const placeholder = this.props.prefixCharacter || this.state.isFocused || !hasLabel || (hasLabel && this.props.forceActiveLabel) ? this.props.placeholder : null; - const maxHeight = StyleSheet.flatten(this.props.containerStyles).maxHeight; - const textInputContainerStyles = _.reduce( - [ - styles.textInputContainer, - ...this.props.textInputContainerStyles, - this.props.autoGrow && StyleUtils.getWidthStyle(this.state.textInputWidth), - !this.props.hideFocusedState && this.state.isFocused && styles.borderColorFocus, - (this.props.hasError || this.props.errorText) && styles.borderColorDanger, - this.props.autoGrowHeight && {scrollPaddingTop: 2 * maxHeight}, - ], - (finalStyles, s) => ({...finalStyles, ...s}), - {}, - ); - const isMultiline = this.props.multiline || this.props.autoGrowHeight; - - return ( - <> - - { + if (propOnInputChange) { + propOnInputChange(value); + } + + // TODO: what is the next line used for? + Str.result(props.onChangeText, value); + activateLabel(); + }, + [activateLabel, propOnInputChange, props.onChangeText], + ); + + const togglePasswordVisibility = useCallback(() => { + setPasswordHidden((prevState) => !prevState.passwordHidden); + }, []); + + const storePrefixLayoutDimensions = useCallback((event) => { + setPrefixWidth(Math.abs(event.nativeEvent.layout.width)); + }, []); + + // TODO: don't do that all here? + // eslint-disable-next-line react/forbid-foreign-prop-types + const inputProps = _.omit(props, _.keys(baseTextInputPropTypes.propTypes)); + const hasLabel = Boolean(props.label.length); + const isEditable = _.isUndefined(props.editable) ? !props.disabled : props.editable; + const inputHelpText = props.errorText || props.hint; + const placeholder = props.prefixCharacter || isFocused || !hasLabel || (hasLabel && props.forceActiveLabel) ? props.placeholder : null; + const maxHeight = StyleSheet.flatten(props.containerStyles).maxHeight; + const textInputContainerStyles = _.reduce( + [ + styles.textInputContainer, + ...props.textInputContainerStyles, + props.autoGrow && StyleUtils.getWidthStyle(textInputWidth), + !props.hideFocusedState && isFocused && styles.borderColorFocus, + (props.hasError || props.errorText) && styles.borderColorDanger, + props.autoGrowHeight && {scrollPaddingTop: 2 * maxHeight}, + ], + (finalStyles, s) => ({...finalStyles, ...s}), + {}, + ); + const isMultiline = props.multiline || props.autoGrowHeight; + + return ( + <> + + + - { - if (!this.props.autoGrowHeight && this.props.multiline) { - return; - } - - const layout = event.nativeEvent.layout; - - this.setState((prevState) => ({ - width: this.props.autoGrowHeight ? layout.width : prevState.width, - height: !isMultiline ? layout.height : prevState.height, - })); - }} - style={[ - textInputContainerStyles, - - // When autoGrow is on and minWidth is not supplied, add a minWidth to allow the input to be focusable. - this.props.autoGrow && !textInputContainerStyles.minWidth && styles.mnw2, - ]} - > - {hasLabel ? ( - <> - {/* Adding this background to the label only for multiline text input, - to prevent text overlapping with label when scrolling */} - {isMultiline && ( - - )} - + {/* Adding this background to the label only for multiline text input, + to prevent text overlapping with label when scrolling */} + {isMultiline && ( + - - ) : null} - - {Boolean(this.props.prefixCharacter) && ( - - - {this.props.prefixCharacter} - - )} - { - if (typeof this.props.innerRef === 'function') { - this.props.innerRef(ref); - } else if (this.props.innerRef && _.has(this.props.innerRef, 'current')) { - this.props.innerRef.current = ref; - } - this.input = ref; - }} - // eslint-disable-next-line - {...inputProps} - autoCorrect={this.props.secureTextEntry ? false : this.props.autoCorrect} - placeholder={placeholder} - placeholderTextColor={themeColors.placeholderText} - underlineColorAndroid="transparent" - style={[ - styles.flex1, - styles.w100, - this.props.inputStyle, - (!hasLabel || isMultiline) && styles.pv0, - this.props.prefixCharacter && StyleUtils.getPaddingLeft(this.state.prefixWidth + styles.pl1.paddingLeft), - this.props.secureTextEntry && styles.secureInput, - - // Explicitly remove `lineHeight` from single line inputs so that long text doesn't disappear - // once it exceeds the input space (See https://github.com/Expensify/App/issues/13802) - !isMultiline && {height: this.state.height, lineHeight: undefined}, - - // Stop scrollbar flashing when breaking lines with autoGrowHeight enabled. - this.props.autoGrowHeight && StyleUtils.getAutoGrowHeightInputStyle(this.state.textInputHeight, maxHeight), - ]} - multiline={isMultiline} - maxLength={this.props.maxLength} - onFocus={this.onFocus} - onBlur={this.onBlur} - onChangeText={this.setValue} - secureTextEntry={this.state.passwordHidden} - onPressOut={this.props.onPress} - showSoftInputOnFocus={!this.props.disableKeyboard} - keyboardType={getSecureEntryKeyboardType(this.props.keyboardType, this.props.secureTextEntry, this.state.passwordHidden)} - value={this.state.value} - selection={this.state.selection} - editable={isEditable} - // FormSubmit Enter key handler does not have access to direct props. - // `dataset.submitOnEnter` is used to indicate that pressing Enter on this input should call the submit callback. - dataSet={{submitOnEnter: isMultiline && this.props.submitOnEnter}} + - {Boolean(this.props.secureTextEntry) && ( - e.preventDefault()} + + ) : null} + + {Boolean(props.prefixCharacter) && ( + + - - - )} - {!this.props.secureTextEntry && Boolean(this.props.icon) && ( - - - - )} - + {props.prefixCharacter} + + + )} + { + if (typeof props.innerRef === 'function') { + props.innerRef(ref); + } else if (props.innerRef && _.has(props.innerRef, 'current')) { + // eslint-disable-next-line no-param-reassign + props.innerRef.current = ref; + } + input.current = ref; + }} + // eslint-disable-next-line + {...inputProps} + autoCorrect={props.secureTextEntry ? false : props.autoCorrect} + placeholder={placeholder} + placeholderTextColor={themeColors.placeholderText} + underlineColorAndroid="transparent" + style={[ + styles.flex1, + styles.w100, + props.inputStyle, + (!hasLabel || isMultiline) && styles.pv0, + props.prefixCharacter && StyleUtils.getPaddingLeft(prefixWidth + styles.pl1.paddingLeft), + props.secureTextEntry && styles.secureInput, + + // Explicitly remove `lineHeight` from single line inputs so that long text doesn't disappear + // once it exceeds the input space (See https://github.com/Expensify/App/issues/13802) + !isMultiline && {height, lineHeight: undefined}, + + // Stop scrollbar flashing when breaking lines with autoGrowHeight enabled. + props.autoGrowHeight && StyleUtils.getAutoGrowHeightInputStyle(textInputHeight, maxHeight), + ]} + multiline={isMultiline} + maxLength={props.maxLength} + onFocus={onFocus} + onBlur={onBlur} + onChangeText={setValue} + secureTextEntry={passwordHidden} + onPressOut={props.onPress} + showSoftInputOnFocus={!props.disableKeyboard} + keyboardType={getSecureEntryKeyboardType(props.keyboardType, props.secureTextEntry, passwordHidden)} + value={props.value} + selection={props.selection} + editable={isEditable} + // FormSubmit Enter key handler does not have access to direct props. + // `dataset.submitOnEnter` is used to indicate that pressing Enter on this input should call the submit callback. + dataSet={{submitOnEnter: isMultiline && props.submitOnEnter}} + /> + {Boolean(props.secureTextEntry) && ( + e.preventDefault()} + > + + + )} + {!props.secureTextEntry && Boolean(props.icon) && ( + + + + )} - - {!_.isEmpty(inputHelpText) && ( - - )} - - {/* - Text input component doesn't support auto grow by default. - We're using a hidden text input to achieve that. - This text view is used to calculate width or height of the input value given textStyle in this component. - This Text component is intentionally positioned out of the screen. - */} - {(this.props.autoGrow || this.props.autoGrowHeight) && ( - // Add +2 to width so that the first digit of amount do not cut off on mWeb - https://github.com/Expensify/App/issues/8158. - this.setState({textInputWidth: e.nativeEvent.layout.width + 2, textInputHeight: e.nativeEvent.layout.height})} - > - {this.state.value || this.props.placeholder} - + + + {!_.isEmpty(inputHelpText) && ( + )} - - ); - } + + {/* + Text input component doesn't support auto grow by default. + We're using a hidden text input to achieve that. + This text view is used to calculate width or height of the input value given textStyle in this component. + This Text component is intentionally positioned out of the screen. + */} + {(props.autoGrow || props.autoGrowHeight) && ( + // Add +2 to width so that the first digit of amount do not cut off on mWeb - https://github.com/Expensify/App/issues/8158. + { + setTextInputWidth(e.nativeEvent.layout.width + 2); + setTextInputHeight(e.nativeEvent.layout.height); + }} + > + {props.value || props.placeholder} + + )} + + ); } +BaseTextInput.displayName = 'BaseTextInput'; BaseTextInput.propTypes = baseTextInputPropTypes.propTypes; BaseTextInput.defaultProps = baseTextInputPropTypes.defaultProps; diff --git a/src/components/TextInput/baseTextInputPropTypes.js b/src/components/TextInput/baseTextInputPropTypes.js index 45720ceb8c47..899bbe532d3c 100644 --- a/src/components/TextInput/baseTextInputPropTypes.js +++ b/src/components/TextInput/baseTextInputPropTypes.js @@ -40,10 +40,18 @@ const propTypes = { /** Disable the virtual keyboard */ disableKeyboard: PropTypes.bool, - /** Autogrow input container length based on the entered text */ + /** + * Autogrow input container length based on the entered text. + * Note: If you use this prop, the text input has to be controlled + * by a value prop. + */ autoGrow: PropTypes.bool, - /** Autogrow input container height based on the entered text */ + /** + * Autogrow input container height based on the entered text + * Note: If you use this prop, the text input has to be controlled + * by a value prop. + */ autoGrowHeight: PropTypes.bool, /** Hide the focus styles on TextInput */ From 880c4c008d5defa3a95f197f2f81fd2702f99be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 5 Jun 2023 19:12:29 +0200 Subject: [PATCH 009/171] fix focus/label not working --- src/components/TextInput/BaseTextInput.js | 47 +++++++++-------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 12cbd9a1fcd6..8431073052ac 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -77,30 +77,6 @@ function BaseTextInput(props) { }; }, [props.autoFocus, props.disableKeyboard, props.shouldDelayFocus]); - /* - componentDidUpdate(prevProps) { - // Activate or deactivate the label when value is changed programmatically from outside - const inputValue = _.isUndefined(props.value) ? this.input.value : props.value; - if ((_.isUndefined(inputValue) || props.value === inputValue) && _.isEqual(prevProps.selection, props.selection)) { - return; - } - - // eslint-disable-next-line react/no-did-update-set-state - this.setState({value: inputValue, selection: props.selection}); - - // In some cases, When the value prop is empty, it is not properly updated on the TextInput due to its uncontrolled nature, thus manually clearing the TextInput. - if (inputValue === '') { - this.input.clear(); - } - - if (inputValue) { - this.activateLabel(); - } else if (!props.isFocused) { - this.deactivateLabel(); - } - } - */ - const propIsDisabled = props.disabled; const propOnPress = props.onPress; const onPress = useCallback( @@ -123,23 +99,23 @@ function BaseTextInput(props) { const animateLabel = useCallback( (translateY, scale) => { Animated.parallel([ - Animated.spring(props.labelTranslateY, { + Animated.spring(labelTranslateY, { toValue: translateY, duration: styleConst.LABEL_ANIMATION_DURATION, useNativeDriver: true, }), - Animated.spring(props.labelScale, { + Animated.spring(labelScale, { toValue: scale, duration: styleConst.LABEL_ANIMATION_DURATION, useNativeDriver: true, }), ]).start(); }, - [props.labelScale, props.labelTranslateY], + [labelScale, labelTranslateY], ); const activateLabel = useCallback(() => { - if (props.value.length < 0 || isLabelActive) { + if (props.value.length < 0 || isLabelActive.current) { return; } @@ -199,6 +175,21 @@ function BaseTextInput(props) { [props.autoGrowHeight, props.multiline], ); + useEffect(() => { + // Activate or deactivate the label when value is changed programmatically from outside + + // In some cases, When the value prop is empty, it is not properly updated on the TextInput due to its uncontrolled nature, thus manually clearing the TextInput. + if (props.value === '') { + input.current.clear(); + } + + if (props.value) { + activateLabel(); + } else if (!isFocused) { + deactivateLabel(); + } + }, [activateLabel, deactivateLabel, isFocused, props.value]); + // I feel like this is the region where imperative functions are starting: const propOnInputChange = props.onInputChange; From 13d2e602f3c8383503b61bb51ea5fdc14a8e7845 Mon Sep 17 00:00:00 2001 From: Nikhil Vats Date: Tue, 6 Jun 2023 00:13:14 +0530 Subject: [PATCH 010/171] use function instead of const --- src/pages/NewChatPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index e43d0a81d586..6ee132c4abd6 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -44,7 +44,7 @@ const defaultProps = { reports: {}, }; -const NewChatPage = (props) => { +function NewChatPage(props) { const [searchTerm, setSearchTerm] = useState(''); const [filteredRecentReports, setFilteredRecentReports] = useState([]); const [filteredPersonalDetails, setFilteredPersonalDetails] = useState([]); From 36eb6eb40529e2d221144bd0ee488cfd81fbc04c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 6 Jun 2023 09:36:35 +0200 Subject: [PATCH 011/171] remove comment --- src/components/TextInput/BaseTextInput.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 8431073052ac..0705e607bd4f 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -190,8 +190,6 @@ function BaseTextInput(props) { } }, [activateLabel, deactivateLabel, isFocused, props.value]); - // I feel like this is the region where imperative functions are starting: - const propOnInputChange = props.onInputChange; /** * Set Value & activateLabel From bd6cef713ef2f356215bcbe45b08d467bef60b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 6 Jun 2023 11:04:58 +0200 Subject: [PATCH 012/171] fix text input focus --- src/components/TextInput/BaseTextInput.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 0705e607bd4f..bfddf9bab9c1 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -135,13 +135,13 @@ function BaseTextInput(props) { const propOnFocus = props.onFocus; const onFocus = useCallback( (event) => { + console.log('onFocus'); if (propOnFocus) { propOnFocus(event); } setIsFocused(true); - activateLabel(); }, - [activateLabel, propOnFocus], + [propOnFocus], ); const propOnBlur = props.onBlur; @@ -183,7 +183,7 @@ function BaseTextInput(props) { input.current.clear(); } - if (props.value) { + if (props.value || isFocused) { activateLabel(); } else if (!isFocused) { deactivateLabel(); From 519b1923830f2bd16a1ae10580b7d4247204cdcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 6 Jun 2023 13:35:09 +0200 Subject: [PATCH 013/171] fix focus --- src/components/TextInput/BaseTextInput.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index bfddf9bab9c1..80f8ca128421 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -31,7 +31,8 @@ function dismissKeyboardWhenBackgrounded(nextAppState) { } function BaseTextInput(props) { - const initialActiveLabel = props.forceActiveLabel || props.value.length > 0 || Boolean(props.prefixCharacter); + const inputValue = props.value || ''; + const initialActiveLabel = props.forceActiveLabel || inputValue.length > 0 || Boolean(props.prefixCharacter); const [isFocused, setIsFocused] = useState(false); const [labelTranslateY] = useState(() => new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_TRANSLATE_Y : styleConst.INACTIVE_LABEL_TRANSLATE_Y)); @@ -115,27 +116,26 @@ function BaseTextInput(props) { ); const activateLabel = useCallback(() => { - if (props.value.length < 0 || isLabelActive.current) { + if (inputValue.length < 0 || isLabelActive.current) { return; } animateLabel(styleConst.ACTIVE_LABEL_TRANSLATE_Y, styleConst.ACTIVE_LABEL_SCALE); isLabelActive.current = true; - }, [animateLabel, props.value.length]); + }, [animateLabel, inputValue]); const deactivateLabel = useCallback(() => { - if (props.forceActiveLabel || props.value.length !== 0 || props.prefixCharacter) { + if (props.forceActiveLabel || inputValue.length !== 0 || props.prefixCharacter) { return; } animateLabel(styleConst.INACTIVE_LABEL_TRANSLATE_Y, styleConst.INACTIVE_LABEL_SCALE); isLabelActive.current = false; - }, [animateLabel, props.forceActiveLabel, props.prefixCharacter, props.value.length]); + }, [animateLabel, props.forceActiveLabel, props.prefixCharacter, inputValue]); const propOnFocus = props.onFocus; const onFocus = useCallback( (event) => { - console.log('onFocus'); if (propOnFocus) { propOnFocus(event); } @@ -179,16 +179,16 @@ function BaseTextInput(props) { // Activate or deactivate the label when value is changed programmatically from outside // In some cases, When the value prop is empty, it is not properly updated on the TextInput due to its uncontrolled nature, thus manually clearing the TextInput. - if (props.value === '') { + if (inputValue === '') { input.current.clear(); } - if (props.value || isFocused) { + if (inputValue || isFocused) { activateLabel(); } else if (!isFocused) { deactivateLabel(); } - }, [activateLabel, deactivateLabel, isFocused, props.value]); + }, [activateLabel, deactivateLabel, inputValue, isFocused]); const propOnInputChange = props.onInputChange; /** From f8661e7ebb9cb5afc56440af2bc87ccd5d3fb7dd Mon Sep 17 00:00:00 2001 From: oleh Date: Tue, 6 Jun 2023 16:20:40 +0300 Subject: [PATCH 014/171] Migrate MoneyRequestParticipantsSplitSelector.js to function component --- .../MoneyRequestParticipantsSplitSelector.js | 218 ++++++++---------- 1 file changed, 100 insertions(+), 118 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js index 28bdc2365622..134f35ff2682 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js @@ -1,4 +1,4 @@ -import React, {Component} from 'react'; +import React, {useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; @@ -59,37 +59,32 @@ const defaultProps = { safeAreaPaddingBottomStyle: {}, }; -class MoneyRequestParticipantsSplitSelector extends Component { - constructor(props) { - super(props); - - this.toggleOption = this.toggleOption.bind(this); - this.finalizeParticipants = this.finalizeParticipants.bind(this); - this.updateOptionsWithSearchTerm = this.updateOptionsWithSearchTerm.bind(this); +function MoneyRequestParticipantsSplitSelector(props) { + const [searchTerm, setSearchTerm] = useState(''); + const [newChatOptions, setNewChatOptions] = useState({ + recentReports: [], + personalDetails: [], + userToInvite: null, + }); + function updateOptionsWithSearchTerm (newSearchTerm = '') { const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( props.reports, props.personalDetails, props.betas, - '', + newSearchTerm, props.participants, CONST.EXPENSIFY_EMAILS, ); - - this.state = { - searchTerm: '', + setSearchTerm(newSearchTerm); + setNewChatOptions({ recentReports, personalDetails, - userToInvite, - }; + userToInvite + }); } - componentDidUpdate(prevProps) { - if (_.isEqual(prevProps.reports, this.props.reports) && _.isEqual(prevProps.personalDetails, this.props.personalDetails)) { - return; - } - this.updateOptionsWithSearchTerm(this.state.searchTerm); - } + const maxParticipantsReached = props.participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; /** * Returns the sections needed for the OptionsSelector @@ -97,145 +92,132 @@ class MoneyRequestParticipantsSplitSelector extends Component { * @param {Boolean} maxParticipantsReached * @returns {Array} */ - getSections(maxParticipantsReached) { - const sections = []; + const sections = useMemo(() => { + const newSections = []; let indexOffset = 0; - sections.push({ + newSections.push({ title: undefined, - data: this.props.participants, + data: props.participants, shouldShow: true, indexOffset, }); - indexOffset += this.props.participants.length; + indexOffset += props.participants.length; if (maxParticipantsReached) { - return sections; + return newSections; } - sections.push({ - title: this.props.translate('common.recents'), - data: this.state.recentReports, - shouldShow: !_.isEmpty(this.state.recentReports), + const { + recentReports, + personalDetails, + userToInvite + } = newChatOptions; + + newSections.push({ + title: props.translate('common.recents'), + data: recentReports, + shouldShow: !_.isEmpty(recentReports), indexOffset, }); - indexOffset += this.state.recentReports.length; + indexOffset += recentReports.length; - sections.push({ - title: this.props.translate('common.contacts'), - data: this.state.personalDetails, - shouldShow: !_.isEmpty(this.state.personalDetails), + newSections.push({ + title: props.translate('common.contacts'), + data: personalDetails, + shouldShow: !_.isEmpty(personalDetails), indexOffset, }); - indexOffset += this.state.personalDetails.length; + indexOffset += personalDetails.length; - if (this.state.userToInvite && !OptionsListUtils.isCurrentUser(this.state.userToInvite)) { - sections.push({ + if (userToInvite && !OptionsListUtils.isCurrentUser(userToInvite)) { + newSections.push({ undefined, - data: [this.state.userToInvite], + data: [userToInvite], shouldShow: true, indexOffset, }); } - return sections; - } - - updateOptionsWithSearchTerm(searchTerm = '') { - const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( - this.props.reports, - this.props.personalDetails, - this.props.betas, - searchTerm, - this.props.participants, - CONST.EXPENSIFY_EMAILS, - ); - this.setState({ - searchTerm, - userToInvite, - recentReports, - personalDetails, - }); - } - - /** - * Once a single or more users are selected, navigates to next step - */ - finalizeParticipants() { - this.props.onStepComplete(); - } + return newSections; + }, [props.translate, props.participants, newChatOptions, maxParticipantsReached]) /** * Removes a selected option from list if already selected. If not already selected add this option to the list. * @param {Object} option */ - toggleOption(option) { - const isOptionInList = _.some(this.props.participants, (selectedOption) => selectedOption.login === option.login); + function toggleOption(option) { + const isOptionInList = _.some(props.participants, (selectedOption) => selectedOption.login === option.login); let newSelectedOptions; if (isOptionInList) { - newSelectedOptions = _.reject(this.props.participants, (selectedOption) => selectedOption.login === option.login); + newSelectedOptions = _.reject(props.participants, (selectedOption) => selectedOption.login === option.login); } else { - newSelectedOptions = [...this.props.participants, option]; + newSelectedOptions = [...props.participants, option]; } - this.props.onAddParticipants(newSelectedOptions); - - this.setState((prevState) => { - const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( - this.props.reports, - this.props.personalDetails, - this.props.betas, - isOptionInList ? prevState.searchTerm : '', - newSelectedOptions, - CONST.EXPENSIFY_EMAILS, - ); - return { - recentReports, - personalDetails, - userToInvite, - searchTerm: isOptionInList ? prevState.searchTerm : '', - }; + const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( + props.reports, + props.personalDetails, + props.betas, + isOptionInList ? searchTerm : '', + newSelectedOptions, + CONST.EXPENSIFY_EMAILS, + ); + + setNewChatOptions({ + recentReports, + personalDetails, + userToInvite }); + props.onAddParticipants(newSelectedOptions) } - render() { - const maxParticipantsReached = this.props.participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; - const sections = this.getSections(maxParticipantsReached); - const headerMessage = OptionsListUtils.getHeaderMessage( - this.state.personalDetails.length + this.state.recentReports.length !== 0, - Boolean(this.state.userToInvite), - this.state.searchTerm, - maxParticipantsReached, - ); - const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(this.props.personalDetails); - - return ( - 0 ? this.props.safeAreaPaddingBottomStyle : {}]}> - - - ); + /** + * Once a single or more users are selected, navigates to next step + */ + function finalizeParticipants() { + props.onStepComplete(); } -} + + const headerMessage = OptionsListUtils.getHeaderMessage( + newChatOptions.personalDetails.length + newChatOptions.recentReports.length !== 0, + Boolean(newChatOptions.userToInvite), + searchTerm, + maxParticipantsReached, + ); + const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(props.personalDetails); + + useEffect(() => { + updateOptionsWithSearchTerm(searchTerm); + }, [props.reports, props.personalDetails, searchTerm]) + + return ( + 0 ? props.safeAreaPaddingBottomStyle : {}]}> + + + ); +}; MoneyRequestParticipantsSplitSelector.propTypes = propTypes; MoneyRequestParticipantsSplitSelector.defaultProps = defaultProps; +MoneyRequestParticipantsSplitSelector.displayName = 'MoneyRequestParticipantsSplitSelector'; export default compose( withLocalize, From 8ecd4fbb6de90d4a67ce775b46e3bb7d27f99f8f Mon Sep 17 00:00:00 2001 From: oleh Date: Wed, 7 Jun 2023 00:01:13 +0300 Subject: [PATCH 015/171] Fixed lint issues --- ios/Podfile.lock | 6 +-- .../MoneyRequestParticipantsSplitSelector.js | 48 +++++++++---------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6cb4235c58c8..c6c8ee0b4dce 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1019,7 +1019,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Airship: c70eed50e429f97f5adb285423c7291fb7a032ae AirshipFrameworkProxy: 7bc4130c668c6c98e2d4c60fe4c9eb61a999be99 - boost: a7c83b31436843459a1961bfd74b96033dc77234 + boost: 57d2868c099736d80fcd648bf211b4431e51a558 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 FBLazyVector: ff54429f0110d3c722630a98096ba689c39f6d5f @@ -1062,7 +1062,7 @@ SPEC CHECKSUMS: Permission-LocationWhenInUse: 3ba99e45c852763f730eabecec2870c2382b7bd4 Plaid: 7d340abeadb46c7aa1a91f896c5b22395a31fcf2 PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef - RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda + RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 RCTRequired: e9e7b8b45aa9bedb2fdad71740adf07a7265b9be RCTTypeSafety: 9ae0e9206625e995f0df4d5b9ddc94411929fb30 React: a71c8e1380f07e01de721ccd52bcf9c03e81867d @@ -1135,4 +1135,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 4ed1c7b099741c82e2b0411b95f6468e72be6c76 -COCOAPODS: 1.12.0 +COCOAPODS: 1.11.3 diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js index 134f35ff2682..ad4925e5893d 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js @@ -1,4 +1,4 @@ -import React, {useEffect, useMemo, useState} from 'react'; +import React, {useEffect, useState} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; @@ -67,32 +67,13 @@ function MoneyRequestParticipantsSplitSelector(props) { userToInvite: null, }); - function updateOptionsWithSearchTerm (newSearchTerm = '') { - const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( - props.reports, - props.personalDetails, - props.betas, - newSearchTerm, - props.participants, - CONST.EXPENSIFY_EMAILS, - ); - setSearchTerm(newSearchTerm); - setNewChatOptions({ - recentReports, - personalDetails, - userToInvite - }); - } - - const maxParticipantsReached = props.participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; - /** * Returns the sections needed for the OptionsSelector * * @param {Boolean} maxParticipantsReached * @returns {Array} */ - const sections = useMemo(() => { + const getSections = (maxParticipantsReached) => { const newSections = []; let indexOffset = 0; @@ -140,13 +121,13 @@ function MoneyRequestParticipantsSplitSelector(props) { } return newSections; - }, [props.translate, props.participants, newChatOptions, maxParticipantsReached]) + } /** * Removes a selected option from list if already selected. If not already selected add this option to the list. * @param {Object} option */ - function toggleOption(option) { + const toggleOption = (option) => { const isOptionInList = _.some(props.participants, (selectedOption) => selectedOption.login === option.login); let newSelectedOptions; @@ -177,10 +158,11 @@ function MoneyRequestParticipantsSplitSelector(props) { /** * Once a single or more users are selected, navigates to next step */ - function finalizeParticipants() { + const finalizeParticipants = () => { props.onStepComplete(); } + const maxParticipantsReached = props.participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; const headerMessage = OptionsListUtils.getHeaderMessage( newChatOptions.personalDetails.length + newChatOptions.recentReports.length !== 0, Boolean(newChatOptions.userToInvite), @@ -189,9 +171,23 @@ function MoneyRequestParticipantsSplitSelector(props) { ); const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(props.personalDetails); + const sections = getSections(maxParticipantsReached); + useEffect(() => { - updateOptionsWithSearchTerm(searchTerm); - }, [props.reports, props.personalDetails, searchTerm]) + const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( + props.reports, + props.personalDetails, + props.betas, + searchTerm, + props.participants, + CONST.EXPENSIFY_EMAILS, + ); + setNewChatOptions({ + recentReports, + personalDetails, + userToInvite + }); + }, [props.betas, props.reports, props.participants, props.personalDetails, searchTerm]) return ( 0 ? props.safeAreaPaddingBottomStyle : {}]}> From 0b66de41bff8c8b06acd1c00e769e2726cbb18a0 Mon Sep 17 00:00:00 2001 From: oleh Date: Wed, 7 Jun 2023 00:12:00 +0300 Subject: [PATCH 016/171] Revert podfile.lock --- ios/Podfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c6c8ee0b4dce..0ba0e5d249ee 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1019,7 +1019,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Airship: c70eed50e429f97f5adb285423c7291fb7a032ae AirshipFrameworkProxy: 7bc4130c668c6c98e2d4c60fe4c9eb61a999be99 - boost: 57d2868c099736d80fcd648bf211b4431e51a558 + boost: a7c83b31436843459a1961bfd74b96033dc77234 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 FBLazyVector: ff54429f0110d3c722630a98096ba689c39f6d5f @@ -1062,7 +1062,7 @@ SPEC CHECKSUMS: Permission-LocationWhenInUse: 3ba99e45c852763f730eabecec2870c2382b7bd4 Plaid: 7d340abeadb46c7aa1a91f896c5b22395a31fcf2 PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef - RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 + RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda RCTRequired: e9e7b8b45aa9bedb2fdad71740adf07a7265b9be RCTTypeSafety: 9ae0e9206625e995f0df4d5b9ddc94411929fb30 React: a71c8e1380f07e01de721ccd52bcf9c03e81867d @@ -1135,4 +1135,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 4ed1c7b099741c82e2b0411b95f6468e72be6c76 -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.0 \ No newline at end of file From e10cd886530beb97a88b2ee7780d83ca417bdfca Mon Sep 17 00:00:00 2001 From: oleh Date: Wed, 7 Jun 2023 00:14:07 +0300 Subject: [PATCH 017/171] Fix --- ios/Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 0ba0e5d249ee..6cb4235c58c8 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1135,4 +1135,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 4ed1c7b099741c82e2b0411b95f6468e72be6c76 -COCOAPODS: 1.12.0 \ No newline at end of file +COCOAPODS: 1.12.0 From c26fdb9334e6acb5c5cd4f6e8a6a72393ef495ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Wed, 7 Jun 2023 10:00:28 +0200 Subject: [PATCH 018/171] fix usage of useCallback --- src/components/TextInput/BaseTextInput.js | 95 ++++++++++------------- 1 file changed, 39 insertions(+), 56 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 80f8ca128421..2208abb38890 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -78,25 +78,6 @@ function BaseTextInput(props) { }; }, [props.autoFocus, props.disableKeyboard, props.shouldDelayFocus]); - const propIsDisabled = props.disabled; - const propOnPress = props.onPress; - const onPress = useCallback( - (event) => { - if (propIsDisabled) { - return; - } - - if (propOnPress) { - propOnPress(event); - } - - if (!event.isDefaultPrevented()) { - input.current.focus(); - } - }, - [propIsDisabled, propOnPress], - ); - const animateLabel = useCallback( (translateY, scale) => { Animated.parallel([ @@ -133,33 +114,39 @@ function BaseTextInput(props) { isLabelActive.current = false; }, [animateLabel, props.forceActiveLabel, props.prefixCharacter, inputValue]); - const propOnFocus = props.onFocus; - const onFocus = useCallback( - (event) => { - if (propOnFocus) { - propOnFocus(event); - } - setIsFocused(true); - }, - [propOnFocus], - ); + const onFocus = (event) => { + if (props.onFocus) { + props.onFocus(event); + } + setIsFocused(true); + }; - const propOnBlur = props.onBlur; - const onBlur = useCallback( - (event) => { - if (propOnBlur) { - propOnBlur(event); - } - setIsFocused(false); + const onBlur = (event) => { + if (props.onBlur) { + props.onBlur(event); + } + setIsFocused(false); - // If the text has been supplied by Chrome autofill, the value state is not synced with the value - // as Chrome doesn't trigger a change event. When there is autofill text, don't deactivate label. - if (!isInputAutoFilled(input.current)) { - deactivateLabel(); - } - }, - [deactivateLabel, propOnBlur], - ); + // If the text has been supplied by Chrome autofill, the value state is not synced with the value + // as Chrome doesn't trigger a change event. When there is autofill text, don't deactivate label. + if (!isInputAutoFilled(input.current)) { + deactivateLabel(); + } + }; + + const onPress = (event) => { + if (props.disabled) { + return; + } + + if (props.onPress) { + props.onPress(event); + } + + if (!event.isDefaultPrevented()) { + input.current.focus(); + } + }; const onLayout = useCallback( (event) => { @@ -190,25 +177,21 @@ function BaseTextInput(props) { } }, [activateLabel, deactivateLabel, inputValue, isFocused]); - const propOnInputChange = props.onInputChange; /** * Set Value & activateLabel * * @param {String} value * @memberof BaseTextInput */ - const setValue = useCallback( - (value) => { - if (propOnInputChange) { - propOnInputChange(value); - } + const setValue = (value) => { + if (props.onInputChange) { + props.onInputChange(value); + } - // TODO: what is the next line used for? - Str.result(props.onChangeText, value); - activateLabel(); - }, - [activateLabel, propOnInputChange, props.onChangeText], - ); + // TODO: what is the next line used for? + Str.result(props.onChangeText, value); + activateLabel(); + }; const togglePasswordVisibility = useCallback(() => { setPasswordHidden((prevState) => !prevState.passwordHidden); From a75802f4c84f55cf13f9a215e8b2a8fe4cf1b24a Mon Sep 17 00:00:00 2001 From: Nikhil Vats Date: Wed, 7 Jun 2023 20:34:31 +0530 Subject: [PATCH 019/171] fix linting issues --- src/pages/NewChatPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index ce5b11c703ad..80e39422821b 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -216,7 +216,7 @@ function NewChatPage(props) { )} ); -}; +} NewChatPage.propTypes = propTypes; NewChatPage.defaultProps = defaultProps; From 35b3f79d09f34518bee78388e7aad2121fdd0926 Mon Sep 17 00:00:00 2001 From: oleh Date: Fri, 9 Jun 2023 17:42:43 +0300 Subject: [PATCH 020/171] added translate per reviewer request --- .../MoneyRequestParticipantsSplitSelector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js index ad4925e5893d..575b374fa75c 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js @@ -187,7 +187,7 @@ function MoneyRequestParticipantsSplitSelector(props) { personalDetails, userToInvite }); - }, [props.betas, props.reports, props.participants, props.personalDetails, searchTerm]) + }, [props.betas, props.reports, props.participants, props.personalDetails, props.translate, searchTerm]) return ( 0 ? props.safeAreaPaddingBottomStyle : {}]}> From 7a3f3f3fff1320e0159cb1d5c34e2d8ae87ab3c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 12 Jun 2023 11:17:27 +0200 Subject: [PATCH 021/171] remove todo --- src/components/TextInput/BaseTextInput.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 2208abb38890..12206f82d163 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -188,7 +188,6 @@ function BaseTextInput(props) { props.onInputChange(value); } - // TODO: what is the next line used for? Str.result(props.onChangeText, value); activateLabel(); }; From 3886aaf84de1d827b3aa07a70ab2e50c43d1e9e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 12 Jun 2023 11:19:10 +0200 Subject: [PATCH 022/171] use named import directly --- src/components/TextInput/BaseTextInput.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 12206f82d163..e55b81f78925 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -20,8 +20,6 @@ import FormHelpMessage from '../FormHelpMessage'; import isInputAutoFilled from '../../libs/isInputAutoFilled'; import * as Pressables from '../Pressable'; -const PressableWithoutFeedback = Pressables.PressableWithoutFeedback; - function dismissKeyboardWhenBackgrounded(nextAppState) { if (!nextAppState.match(/inactive|background/)) { return; @@ -225,7 +223,7 @@ function BaseTextInput(props) { return ( <> - - + {!_.isEmpty(inputHelpText) && ( Date: Mon, 12 Jun 2023 11:20:02 +0200 Subject: [PATCH 023/171] use function directly --- src/components/TextInput/BaseTextInput.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index e55b81f78925..2fc5c395981d 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -20,14 +20,6 @@ import FormHelpMessage from '../FormHelpMessage'; import isInputAutoFilled from '../../libs/isInputAutoFilled'; import * as Pressables from '../Pressable'; -function dismissKeyboardWhenBackgrounded(nextAppState) { - if (!nextAppState.match(/inactive|background/)) { - return; - } - - Keyboard.dismiss(); -} - function BaseTextInput(props) { const inputValue = props.value || ''; const initialActiveLabel = props.forceActiveLabel || inputValue.length > 0 || Boolean(props.prefixCharacter); @@ -48,7 +40,13 @@ function BaseTextInput(props) { useEffect(() => { let appStateSubscription; if (props.disableKeyboard) { - appStateSubscription = AppState.addEventListener('change', dismissKeyboardWhenBackgrounded); + appStateSubscription = AppState.addEventListener('change', (nextAppState) => { + if (!nextAppState.match(/inactive|background/)) { + return; + } + + Keyboard.dismiss(); + }); } // We are manually managing focus to prevent this issue: https://github.com/Expensify/App/issues/4514 From 21ded8cef5e59393745387b13972c116db89c277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 12 Jun 2023 11:22:09 +0200 Subject: [PATCH 024/171] only autoFocus on mount --- src/components/TextInput/BaseTextInput.js | 24 +++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 2fc5c395981d..b30da00048b5 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -49,6 +49,17 @@ function BaseTextInput(props) { }); } + return () => { + if (!props.disableKeyboard || !appStateSubscription) { + return; + } + + appStateSubscription.remove(); + }; + }, [props.disableKeyboard]); + + // AutoFocus which only works on mount: + useEffect(() => { // We are manually managing focus to prevent this issue: https://github.com/Expensify/App/issues/4514 if (!props.autoFocus || !input.current) { return; @@ -62,17 +73,14 @@ function BaseTextInput(props) { input.current.focus(); return () => { - if (focusTimeout) { - clearTimeout(focusTimeout); - } - - if (!props.disableKeyboard || !appStateSubscription) { + if (!focusTimeout) { return; } - - appStateSubscription.remove(); + clearTimeout(focusTimeout); }; - }, [props.autoFocus, props.disableKeyboard, props.shouldDelayFocus]); + // We only want this to run on mount + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const animateLabel = useCallback( (translateY, scale) => { From 68ee867dd2fb9af1a8ea75f064c291b7b7437690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 12 Jun 2023 11:27:55 +0200 Subject: [PATCH 025/171] use stylesheet flatten --- src/components/TextInput/BaseTextInput.js | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index b30da00048b5..7038a4e321f9 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -204,7 +204,6 @@ function BaseTextInput(props) { setPrefixWidth(Math.abs(event.nativeEvent.layout.width)); }, []); - // TODO: don't do that all here? // eslint-disable-next-line react/forbid-foreign-prop-types const inputProps = _.omit(props, _.keys(baseTextInputPropTypes.propTypes)); const hasLabel = Boolean(props.label.length); @@ -212,18 +211,14 @@ function BaseTextInput(props) { const inputHelpText = props.errorText || props.hint; const placeholder = props.prefixCharacter || isFocused || !hasLabel || (hasLabel && props.forceActiveLabel) ? props.placeholder : null; const maxHeight = StyleSheet.flatten(props.containerStyles).maxHeight; - const textInputContainerStyles = _.reduce( - [ - styles.textInputContainer, - ...props.textInputContainerStyles, - props.autoGrow && StyleUtils.getWidthStyle(textInputWidth), - !props.hideFocusedState && isFocused && styles.borderColorFocus, - (props.hasError || props.errorText) && styles.borderColorDanger, - props.autoGrowHeight && {scrollPaddingTop: 2 * maxHeight}, - ], - (finalStyles, s) => ({...finalStyles, ...s}), - {}, - ); + const textInputContainerStyles = StyleSheet.flatten([ + styles.textInputContainer, + ...props.textInputContainerStyles, + props.autoGrow && StyleUtils.getWidthStyle(textInputWidth), + !props.hideFocusedState && isFocused && styles.borderColorFocus, + (props.hasError || props.errorText) && styles.borderColorDanger, + props.autoGrowHeight && {scrollPaddingTop: 2 * maxHeight}, + ]); const isMultiline = props.multiline || props.autoGrowHeight; return ( From 4c375f918095f454fcec1a2486377eb4cb2ad1de Mon Sep 17 00:00:00 2001 From: oleh Date: Mon, 12 Jun 2023 17:57:28 +0300 Subject: [PATCH 026/171] fixed lint issues --- .../MoneyRequestParticipantsSplitSelector.js | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js index 575b374fa75c..9c77ef68b0e2 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js @@ -89,11 +89,7 @@ function MoneyRequestParticipantsSplitSelector(props) { return newSections; } - const { - recentReports, - personalDetails, - userToInvite - } = newChatOptions; + const {recentReports, personalDetails, userToInvite} = newChatOptions; newSections.push({ title: props.translate('common.recents'), @@ -121,7 +117,7 @@ function MoneyRequestParticipantsSplitSelector(props) { } return newSections; - } + }; /** * Removes a selected option from list if already selected. If not already selected add this option to the list. @@ -150,17 +146,17 @@ function MoneyRequestParticipantsSplitSelector(props) { setNewChatOptions({ recentReports, personalDetails, - userToInvite + userToInvite, }); - props.onAddParticipants(newSelectedOptions) - } + props.onAddParticipants(newSelectedOptions); + }; /** * Once a single or more users are selected, navigates to next step */ const finalizeParticipants = () => { props.onStepComplete(); - } + }; const maxParticipantsReached = props.participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; const headerMessage = OptionsListUtils.getHeaderMessage( @@ -185,9 +181,9 @@ function MoneyRequestParticipantsSplitSelector(props) { setNewChatOptions({ recentReports, personalDetails, - userToInvite + userToInvite, }); - }, [props.betas, props.reports, props.participants, props.personalDetails, props.translate, searchTerm]) + }, [props.betas, props.reports, props.participants, props.personalDetails, props.translate, searchTerm]); return ( 0 ? props.safeAreaPaddingBottomStyle : {}]}> @@ -209,7 +205,7 @@ function MoneyRequestParticipantsSplitSelector(props) { /> ); -}; +} MoneyRequestParticipantsSplitSelector.propTypes = propTypes; MoneyRequestParticipantsSplitSelector.defaultProps = defaultProps; From 86dea8ce4d5d13ede77bf10c2ba4dfd38c192b66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 12 Jun 2023 17:59:11 +0200 Subject: [PATCH 027/171] Update src/components/TextInput/BaseTextInput.js Co-authored-by: Rajat Parashar --- src/components/TextInput/BaseTextInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 7038a4e321f9..5a59db07879f 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -18,7 +18,7 @@ import getSecureEntryKeyboardType from '../../libs/getSecureEntryKeyboardType'; import CONST from '../../CONST'; import FormHelpMessage from '../FormHelpMessage'; import isInputAutoFilled from '../../libs/isInputAutoFilled'; -import * as Pressables from '../Pressable'; +import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; function BaseTextInput(props) { const inputValue = props.value || ''; From 71fb82c0dad473ac551beeb742e4cf3ca0772fab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 12 Jun 2023 17:59:28 +0200 Subject: [PATCH 028/171] Update src/components/TextInput/BaseTextInput.js Co-authored-by: Rajat Parashar --- src/components/TextInput/BaseTextInput.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 5a59db07879f..9875dead95d1 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -39,7 +39,9 @@ function BaseTextInput(props) { useEffect(() => { let appStateSubscription; - if (props.disableKeyboard) { + if (!props.disableKeyboard) { + return; + } appStateSubscription = AppState.addEventListener('change', (nextAppState) => { if (!nextAppState.match(/inactive|background/)) { return; From 528d63eb33e74b724c75dbf09fce247f42f2b8c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 12 Jun 2023 18:00:18 +0200 Subject: [PATCH 029/171] Update src/components/TextInput/BaseTextInput.js Co-authored-by: Rajat Parashar --- src/components/TextInput/BaseTextInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 9875dead95d1..dded7a117d70 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -226,7 +226,7 @@ function BaseTextInput(props) { return ( <> - Date: Mon, 12 Jun 2023 18:04:34 +0200 Subject: [PATCH 030/171] fix useEffect --- src/components/TextInput/BaseTextInput.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index dded7a117d70..dca8d4b83f26 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -38,21 +38,20 @@ function BaseTextInput(props) { const isLabelActive = useRef(initialActiveLabel); useEffect(() => { - let appStateSubscription; - if (!props.disableKeyboard) { - return; + if (!props.disableKeyboard) { + return; } - appStateSubscription = AppState.addEventListener('change', (nextAppState) => { - if (!nextAppState.match(/inactive|background/)) { - return; - } - Keyboard.dismiss(); - }); - } + const appStateSubscription = AppState.addEventListener('change', (nextAppState) => { + if (!nextAppState.match(/inactive|background/)) { + return; + } + + Keyboard.dismiss(); + }); return () => { - if (!props.disableKeyboard || !appStateSubscription) { + if (!props.disableKeyboard) { return; } @@ -347,7 +346,7 @@ function BaseTextInput(props) { )} - + {!_.isEmpty(inputHelpText) && ( Date: Mon, 12 Jun 2023 18:06:06 +0200 Subject: [PATCH 031/171] useRef for stable values --- src/components/TextInput/BaseTextInput.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index dca8d4b83f26..f797df2063c6 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -25,8 +25,8 @@ function BaseTextInput(props) { const initialActiveLabel = props.forceActiveLabel || inputValue.length > 0 || Boolean(props.prefixCharacter); const [isFocused, setIsFocused] = useState(false); - const [labelTranslateY] = useState(() => new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_TRANSLATE_Y : styleConst.INACTIVE_LABEL_TRANSLATE_Y)); - const [labelScale] = useState(() => new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_SCALE : styleConst.INACTIVE_LABEL_SCALE)); + const labelTranslateY = useRef(new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_TRANSLATE_Y : styleConst.INACTIVE_LABEL_TRANSLATE_Y)).current; + const labelScale = useRef(new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_SCALE : styleConst.INACTIVE_LABEL_SCALE)).current; const [passwordHidden, setPasswordHidden] = useState(props.secureTextEntry); const [textInputWidth, setTextInputWidth] = useState(0); const [textInputHeight, setTextInputHeight] = useState(); From 0a756fc5534f35c6c2f321f0d316c1c5415107dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 12 Jun 2023 18:19:56 +0200 Subject: [PATCH 032/171] use defaultValue as well --- src/components/TextInput/BaseTextInput.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index f797df2063c6..ff03590844e3 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -21,7 +21,7 @@ import isInputAutoFilled from '../../libs/isInputAutoFilled'; import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; function BaseTextInput(props) { - const inputValue = props.value || ''; + const inputValue = props.value || props.defaultValue || ''; const initialActiveLabel = props.forceActiveLabel || inputValue.length > 0 || Boolean(props.prefixCharacter); const [isFocused, setIsFocused] = useState(false); @@ -102,22 +102,26 @@ function BaseTextInput(props) { ); const activateLabel = useCallback(() => { - if (inputValue.length < 0 || isLabelActive.current) { + const value = props.value || ''; + + if (value.length < 0 || isLabelActive.current) { return; } animateLabel(styleConst.ACTIVE_LABEL_TRANSLATE_Y, styleConst.ACTIVE_LABEL_SCALE); isLabelActive.current = true; - }, [animateLabel, inputValue]); + }, [animateLabel, props.value]); const deactivateLabel = useCallback(() => { - if (props.forceActiveLabel || inputValue.length !== 0 || props.prefixCharacter) { + const value = props.value || ''; + + if (props.forceActiveLabel || value.length !== 0 || props.prefixCharacter) { return; } animateLabel(styleConst.INACTIVE_LABEL_TRANSLATE_Y, styleConst.INACTIVE_LABEL_SCALE); isLabelActive.current = false; - }, [animateLabel, props.forceActiveLabel, props.prefixCharacter, inputValue]); + }, [animateLabel, props.forceActiveLabel, props.prefixCharacter, props.value]); const onFocus = (event) => { if (props.onFocus) { From 7d1cb482a6c8627268743adcaf12edad5a6b8cf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 13 Jun 2023 10:20:47 +0200 Subject: [PATCH 033/171] removed unnecessary condition --- src/components/TextInput/BaseTextInput.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index ff03590844e3..60f2ed739740 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -51,10 +51,6 @@ function BaseTextInput(props) { }); return () => { - if (!props.disableKeyboard) { - return; - } - appStateSubscription.remove(); }; }, [props.disableKeyboard]); From 553111e9845d363ae0c7a58ba32aa186c3fe2916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 13 Jun 2023 10:21:33 +0200 Subject: [PATCH 034/171] beautify code order --- src/components/TextInput/BaseTextInput.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 60f2ed739740..724e39c0cdce 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -25,14 +25,14 @@ function BaseTextInput(props) { const initialActiveLabel = props.forceActiveLabel || inputValue.length > 0 || Boolean(props.prefixCharacter); const [isFocused, setIsFocused] = useState(false); - const labelTranslateY = useRef(new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_TRANSLATE_Y : styleConst.INACTIVE_LABEL_TRANSLATE_Y)).current; - const labelScale = useRef(new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_SCALE : styleConst.INACTIVE_LABEL_SCALE)).current; const [passwordHidden, setPasswordHidden] = useState(props.secureTextEntry); const [textInputWidth, setTextInputWidth] = useState(0); const [textInputHeight, setTextInputHeight] = useState(); const [prefixWidth, setPrefixWidth] = useState(0); const [height, setHeight] = useState(variables.componentSizeLarge); const [width, setWidth] = useState(); + const labelScale = useRef(new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_SCALE : styleConst.INACTIVE_LABEL_SCALE)).current; + const labelTranslateY = useRef(new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_TRANSLATE_Y : styleConst.INACTIVE_LABEL_TRANSLATE_Y)).current; const input = useRef(null); const isLabelActive = useRef(initialActiveLabel); From 9974bc5e538d0acc90e8d8b6e7bcfd2ca3096411 Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 13 Jun 2023 12:30:58 -0700 Subject: [PATCH 035/171] Create usePermissions hook --- src/components/OnyxProvider.js | 4 ++-- src/hooks/usePermissions.js | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 src/hooks/usePermissions.js diff --git a/src/components/OnyxProvider.js b/src/components/OnyxProvider.js index 6cee7e5b7a62..76cda71da2b2 100644 --- a/src/components/OnyxProvider.js +++ b/src/components/OnyxProvider.js @@ -11,7 +11,7 @@ const [withPersonalDetails, PersonalDetailsProvider] = createOnyxContext(ONYXKEY const [withCurrentDate, CurrentDateProvider] = createOnyxContext(ONYXKEYS.CURRENT_DATE); const [withReportActionsDrafts, ReportActionsDraftsProvider] = createOnyxContext(ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS); const [withBlockedFromConcierge, BlockedFromConciergeProvider] = createOnyxContext(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE); -const [withBetas, BetasProvider] = createOnyxContext(ONYXKEYS.BETAS); +const [withBetas, BetasProvider, BetasContext] = createOnyxContext(ONYXKEYS.BETAS); const propTypes = { /** Rendered child component */ @@ -29,4 +29,4 @@ OnyxProvider.propTypes = propTypes; export default OnyxProvider; -export {withNetwork, withPersonalDetails, withReportActionsDrafts, withCurrentDate, withBlockedFromConcierge, withBetas, NetworkContext}; +export {withNetwork, withPersonalDetails, withReportActionsDrafts, withCurrentDate, withBlockedFromConcierge, withBetas, NetworkContext, BetasContext}; diff --git a/src/hooks/usePermissions.js b/src/hooks/usePermissions.js new file mode 100644 index 000000000000..1c31ffc8bb64 --- /dev/null +++ b/src/hooks/usePermissions.js @@ -0,0 +1,15 @@ +import _ from 'underscore'; +import {useContext, useMemo} from 'react'; +import Permissions from '../libs/Permissions'; +import {BetasContext} from '../components/OnyxProvider'; + +export default function usePermissions() { + const betas = useContext(BetasContext); + return useMemo(() => { + const permissions = {}; + _.each(Permissions, (checkerFunction, beta) => { + permissions[beta] = checkerFunction(betas); + }); + return permissions; + }, [betas]); +} From cc028f6f4a1c0556b62fcdd9c45273a126efd59b Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 13 Jun 2023 12:32:21 -0700 Subject: [PATCH 036/171] Create useLocalize hook --- src/components/withLocalize.js | 2 +- src/hooks/useLocalize.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 src/hooks/useLocalize.js diff --git a/src/components/withLocalize.js b/src/components/withLocalize.js index 4cbdda876231..def7110c1b40 100755 --- a/src/components/withLocalize.js +++ b/src/components/withLocalize.js @@ -179,4 +179,4 @@ export default function withLocalize(WrappedComponent) { return WithLocalize; } -export {withLocalizePropTypes, Provider as LocaleContextProvider}; +export {withLocalizePropTypes, Provider as LocaleContextProvider, LocaleContext}; diff --git a/src/hooks/useLocalize.js b/src/hooks/useLocalize.js new file mode 100644 index 000000000000..9ad5048729bd --- /dev/null +++ b/src/hooks/useLocalize.js @@ -0,0 +1,6 @@ +import {useContext} from 'react'; +import {LocaleContext} from '../components/withLocalize'; + +export default function useLocalize() { + return useContext(LocaleContext); +} From 64811dc2c71089abfd1387197b410c47f6018ccb Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 13 Jun 2023 12:41:11 -0700 Subject: [PATCH 037/171] Migrate SignInPage to functional component --- src/pages/signin/SignInPage.js | 257 ++++++++++++++++----------------- 1 file changed, 125 insertions(+), 132 deletions(-) diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index a875c25359b0..8c3d513f5303 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -1,30 +1,27 @@ -import React, {Component} from 'react'; +import React, {useEffect} from 'react'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import {View} from 'react-native'; -import lodashGet from 'lodash/get'; import Str from 'expensify-common/lib/str'; -import {withSafeAreaInsets} from 'react-native-safe-area-context'; +import {useSafeAreaInsets} from 'react-native-safe-area-context'; import ONYXKEYS from '../../ONYXKEYS'; import styles from '../../styles/styles'; -import compose from '../../libs/compose'; import SignInPageLayout from './SignInPageLayout'; import LoginForm from './LoginForm'; import PasswordForm from './PasswordForm'; import ValidateCodeForm from './ValidateCodeForm'; import ResendValidationForm from './ResendValidationForm'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import Performance from '../../libs/Performance'; import * as App from '../../libs/actions/App'; -import Permissions from '../../libs/Permissions'; import UnlinkLoginForm from './UnlinkLoginForm'; -import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions'; import * as Localize from '../../libs/Localize'; import * as StyleUtils from '../../styles/StyleUtils'; +import useLocalize from '../../hooks/useLocalize'; +import usePermissions from '../../hooks/usePermissions'; +import useWindowDimensions from '../../hooks/useWindowDimensions'; +import Log from '../../libs/Log'; const propTypes = { - /* Onyx Props */ - /** The details about the account that the user is signing in with */ account: PropTypes.shape({ /** Error to display when there is an account error returned */ @@ -35,153 +32,149 @@ const propTypes = { /** The primaryLogin associated with the account */ primaryLogin: PropTypes.string, - }), - /** List of betas available to current user */ - betas: PropTypes.arrayOf(PropTypes.string), + /** Has the user pressed the forgot password button? */ + forgotPassword: PropTypes.bool, + + /** Does this account require 2FA? */ + requiresTwoFactorAuth: PropTypes.bool, + }), /** The credentials of the person signing in */ credentials: PropTypes.shape({ login: PropTypes.string, password: PropTypes.string, twoFactorAuthCode: PropTypes.string, + validateCode: PropTypes.string, }), - - ...withLocalizePropTypes, - - ...windowDimensionsPropTypes, }; const defaultProps = { account: {}, - betas: [], credentials: {}, }; -class SignInPage extends Component { - componentDidMount() { - Performance.measureTTI(); +/** + * @param {Boolean} hasLogin + * @param {Boolean} hasPassword + * @param {Boolean} hasValidateCode + * @param {Boolean} isPrimaryLogin + * @param {Boolean} isAccountValidated + * @param {Boolean} didForgetPassword + * @param {Boolean} canUsePasswordlessLogins + * @returns {Object} + */ +function getRenderOptions({hasLogin, hasPassword, hasValidateCode, isPrimaryLogin, isAccountValidated, didForgetPassword, canUsePasswordlessLogins}) { + const shouldShowLoginForm = !hasLogin && !hasValidateCode; + const isUnvalidatedSecondaryLogin = hasLogin && !isPrimaryLogin && !isAccountValidated; + const shouldShowPasswordForm = hasLogin && isAccountValidated && !hasPassword && !didForgetPassword && !isUnvalidatedSecondaryLogin && !canUsePasswordlessLogins; + const shouldShowValidateCodeForm = (hasLogin || hasValidateCode) && !isUnvalidatedSecondaryLogin && canUsePasswordlessLogins; + const shouldShowResendValidationForm = hasLogin && (!isAccountValidated || didForgetPassword) && !isUnvalidatedSecondaryLogin && !canUsePasswordlessLogins; + const shouldShowWelcomeHeader = shouldShowLoginForm || shouldShowPasswordForm || shouldShowValidateCodeForm || isUnvalidatedSecondaryLogin; + const shouldShowWelcomeText = shouldShowLoginForm || shouldShowPasswordForm || shouldShowValidateCodeForm; + return { + shouldShowLoginForm, + shouldShowUnlinkLoginForm: isUnvalidatedSecondaryLogin, + shouldShowPasswordForm, + shouldShowValidateCodeForm, + shouldShowResendValidationForm, + shouldShowWelcomeHeader, + shouldShowWelcomeText, + }; +} - App.setLocale(Localize.getDevicePreferredLocale()); - } +function SignInPage({credentials, account}) { + const {translate, formatPhoneNumber} = useLocalize(); + const {canUsePasswordlessLogins} = usePermissions(); + const {isSmallScreenWidth} = useWindowDimensions(); + const safeAreaInsets = useSafeAreaInsets(); - render() { - // Show the login form if - // - A login has not been entered yet - // - AND a validateCode has not been cached with sign in link - const showLoginForm = !this.props.credentials.login && !this.props.credentials.validateCode; - - // Show the unlink form if - // - A login has been entered - // - AND the login is not the primary login - // - AND the login is not validated - const showUnlinkLoginForm = - Boolean(this.props.credentials.login && this.props.account.primaryLogin) && this.props.account.primaryLogin !== this.props.credentials.login && !this.props.account.validated; - - // Show the old password form if - // - A login has been entered - // - AND an account exists and is validated for this login - // - AND a password hasn't been entered yet - // - AND haven't forgotten password - // - AND the login isn't an unvalidated secondary login - // - AND the user is NOT on the passwordless beta - const showPasswordForm = - Boolean(this.props.credentials.login) && - this.props.account.validated && - !this.props.credentials.password && - !this.props.account.forgotPassword && - !showUnlinkLoginForm && - !Permissions.canUsePasswordlessLogins(this.props.betas); - - // Show the new magic code / validate code form if - // - A login has been entered or a validateCode has been cached from sign in link - // - AND the login isn't an unvalidated secondary login - // - AND the user is on the 'passwordless' beta - const showValidateCodeForm = - Boolean(this.props.credentials.login || this.props.credentials.validateCode) && !showUnlinkLoginForm && Permissions.canUsePasswordlessLogins(this.props.betas); - - // Show the resend validation link form if - // - A login has been entered - // - AND is not validated or password is forgotten - // - AND the login isn't an unvalidated secondary login - // - AND user is not on 'passwordless' beta - const showResendValidationForm = - Boolean(this.props.credentials.login) && - (!this.props.account.validated || this.props.account.forgotPassword) && - !showUnlinkLoginForm && - !Permissions.canUsePasswordlessLogins(this.props.betas); - - let welcomeHeader = ''; - let welcomeText = ''; - if (showValidateCodeForm) { - if (this.props.account.requiresTwoFactorAuth) { - // We will only know this after a user signs in successfully, without their 2FA code - welcomeHeader = this.props.isSmallScreenWidth ? '' : this.props.translate('welcomeText.welcomeBack'); - welcomeText = this.props.translate('validateCodeForm.enterAuthenticatorCode'); + useEffect(() => Performance.measureTTI(), []); + useEffect(() => { + App.setLocale(Localize.getDevicePreferredLocale()); + }, []); + + const { + shouldShowLoginForm, + shouldShowUnlinkLoginForm, + shouldShowPasswordForm, + shouldShowValidateCodeForm, + shouldShowResendValidationForm, + shouldShowWelcomeHeader, + shouldShowWelcomeText, + } = getRenderOptions({ + hasLogin: Boolean(credentials.login), + hasPassword: Boolean(credentials.password), + hasValidateCode: Boolean(credentials.validateCode), + isPrimaryLogin: !account.primaryLogin || account.primaryLogin === credentials.login, + isAccountValidated: Boolean(account.validated), + didForgetPassword: Boolean(account.forgotPassword), + canUsePasswordlessLogins, + }); + + let welcomeHeader; + let welcomeText; + if (shouldShowValidateCodeForm) { + if (account.requiresTwoFactorAuth) { + // We will only know this after a user signs in successfully, without their 2FA code + welcomeHeader = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); + welcomeText = translate('validateCodeForm.enterAuthenticatorCode'); + } else { + const userLogin = Str.removeSMSDomain(credentials.login || ''); + + // replacing spaces with "hard spaces" to prevent breaking the number + const userLoginToDisplay = Str.isSMSLogin(userLogin) ? formatPhoneNumber(userLogin).replace(/ /g, '\u00A0') : userLogin; + if (account.validated) { + welcomeHeader = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); + welcomeText = isSmallScreenWidth + ? `${translate('welcomeText.welcomeBack')} ${translate('welcomeText.welcomeEnterMagicCode', {login: userLoginToDisplay})}` + : translate('welcomeText.welcomeEnterMagicCode', {login: userLoginToDisplay}); } else { - const userLogin = Str.removeSMSDomain(lodashGet(this.props, 'credentials.login', '')); - - // replacing spaces with "hard spaces" to prevent breaking the number - const userLoginToDisplay = Str.isSMSLogin(userLogin) ? this.props.formatPhoneNumber(userLogin).replace(/ /g, '\u00A0') : userLogin; - if (this.props.account.validated) { - welcomeHeader = this.props.isSmallScreenWidth ? '' : this.props.translate('welcomeText.welcomeBack'); - welcomeText = this.props.isSmallScreenWidth - ? `${this.props.translate('welcomeText.welcomeBack')} ${this.props.translate('welcomeText.welcomeEnterMagicCode', {login: userLoginToDisplay})}` - : this.props.translate('welcomeText.welcomeEnterMagicCode', {login: userLoginToDisplay}); - } else { - welcomeHeader = this.props.isSmallScreenWidth ? '' : this.props.translate('welcomeText.welcome'); - welcomeText = this.props.isSmallScreenWidth - ? `${this.props.translate('welcomeText.welcome')} ${this.props.translate('welcomeText.newFaceEnterMagicCode', {login: userLoginToDisplay})}` - : this.props.translate('welcomeText.newFaceEnterMagicCode', {login: userLoginToDisplay}); - } + welcomeHeader = isSmallScreenWidth ? '' : translate('welcomeText.welcome'); + welcomeText = isSmallScreenWidth + ? `${translate('welcomeText.welcome')} ${translate('welcomeText.newFaceEnterMagicCode', {login: userLoginToDisplay})}` + : translate('welcomeText.newFaceEnterMagicCode', {login: userLoginToDisplay}); } - } else if (showPasswordForm) { - welcomeHeader = this.props.isSmallScreenWidth ? '' : this.props.translate('welcomeText.welcomeBack'); - welcomeText = this.props.isSmallScreenWidth - ? `${this.props.translate('welcomeText.welcomeBack')} ${this.props.translate('welcomeText.enterPassword')}` - : this.props.translate('welcomeText.enterPassword'); - } else if (showUnlinkLoginForm) { - welcomeHeader = this.props.isSmallScreenWidth ? this.props.translate('login.hero.header') : this.props.translate('welcomeText.welcomeBack'); - } else if (!showResendValidationForm) { - welcomeHeader = this.props.isSmallScreenWidth ? this.props.translate('login.hero.header') : this.props.translate('welcomeText.getStarted'); - welcomeText = this.props.isSmallScreenWidth ? this.props.translate('welcomeText.getStarted') : ''; } + } else if (shouldShowPasswordForm) { + welcomeHeader = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); + welcomeText = isSmallScreenWidth ? `${translate('welcomeText.welcomeBack')} ${translate('welcomeText.enterPassword')}` : translate('welcomeText.enterPassword'); + } else if (shouldShowUnlinkLoginForm) { + welcomeHeader = isSmallScreenWidth ? translate('login.hero.header') : translate('welcomeText.welcomeBack'); + } else if (!shouldShowResendValidationForm) { + welcomeHeader = isSmallScreenWidth ? translate('login.hero.header') : translate('welcomeText.getStarted'); + welcomeText = isSmallScreenWidth ? translate('welcomeText.getStarted') : ''; + } else { + Log.warn('SignInPage in unexpected state!'); + } - return ( - // There is an issue SafeAreaView on Android where wrong insets flicker on app start. - // Can be removed once https://github.com/th3rdwave/react-native-safe-area-context/issues/364 is resolved. - - - {/* LoginForm and PasswordForm must use the isVisible prop. This keeps them mounted, but visually hidden + return ( + + + {/* LoginForm and PasswordForm must use the isVisible prop. This keeps them mounted, but visually hidden so that password managers can access the values. Conditionally rendering these components will break this feature. */} - - {showValidateCodeForm ? : } - {showResendValidationForm && } - {showUnlinkLoginForm && } - - - ); - } + + {shouldShowValidateCodeForm ? : } + {shouldShowResendValidationForm && } + {shouldShowUnlinkLoginForm && } + + + ); } SignInPage.propTypes = propTypes; SignInPage.defaultProps = defaultProps; +SignInPage.displayName = 'SignInPage'; -export default compose( - withSafeAreaInsets, - withLocalize, - withWindowDimensions, - withOnyx({ - account: {key: ONYXKEYS.ACCOUNT}, - betas: {key: ONYXKEYS.BETAS}, - credentials: {key: ONYXKEYS.CREDENTIALS}, - }), -)(SignInPage); +export default withOnyx({ + account: {key: ONYXKEYS.ACCOUNT}, + credentials: {key: ONYXKEYS.CREDENTIALS}, +})(SignInPage); From bb88c621a6d5c4b64b4ab735181b4f35e1182388 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 13 Jun 2023 13:47:28 -1000 Subject: [PATCH 038/171] check reports and policies --- src/libs/actions/App.js | 50 +++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index aa83a8a11d51..56cfb5109983 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -138,17 +138,50 @@ AppState.addEventListener('change', (nextAppState) => { appState = nextAppState; }); -/** - * Fetches data needed for app initialization - */ -function openApp() { - isReadyToOpenApp.then(() => { - // We need a fresh connection/callback here to make sure that the list of policyIDs that is sent to OpenApp is the most updated list from Onyx +function getPolicies() { + return new Promise((resolve) => { const connectionID = Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY, waitForCollectionCallback: true, callback: (policies) => { Onyx.disconnect(connectionID); + resolve(policies); + }, + }); + }); +} +function getCollection(key) { + return new Promise((resolve) => { + const connectionID = Onyx.connect({ + key, + waitForCollectionCallback: true, + callback: (reports) => { + Onyx.disconnect(connectionID); + resolve(reports); + }, + }); + }); +} + +function getReports() { +} + +/** + * Fetches data needed for app initialization + */ +function openApp() { + let hasExistingReportData = false; + isReadyToOpenApp.then(() => + // If we are opening the app after a first sign in then we will have no data whatsoever. This is easily checked by looking to see if + // the user has any report data at all. All users should have at least one report with Concierge so this is a reliable way to check if + // we are signing in the first time or if the app is being opened after it was killed or the page refreshed. + getCollection(ONYXKEYS.COLLECTION.REPORT) + .then((reports) => { + console.log({reports}); + hasExistingReportData = !_.isEmpty(reports); + return getCollection(ONYXKEYS.COLLECTION.POLICY); + }) + .then((policies) => { API.read( 'OpenApp', {policyIDList: getNonOptimisticPolicyIDs(policies)}, @@ -176,9 +209,8 @@ function openApp() { ], }, ); - }, - }); - }); + }) + ); } /** From ec5a7962c0f68fe415738206c10e93622c37a450 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 13 Jun 2023 15:15:46 -1000 Subject: [PATCH 039/171] no app changes --- src/libs/actions/App.js | 58 +++++++---------------------------------- 1 file changed, 10 insertions(+), 48 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 556c7696eea7..eccf2623f7f5 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -15,7 +15,6 @@ import ROUTES from '../../ROUTES'; import * as SessionUtils from '../SessionUtils'; import getCurrentUrl from '../Navigation/currentUrl'; import * as Session from './Session'; -import * as ReportActionsUtils from '../ReportActionsUtils'; let currentUserAccountID; let currentUserEmail; @@ -138,50 +137,17 @@ AppState.addEventListener('change', (nextAppState) => { appState = nextAppState; }); -function getPolicies() { - return new Promise((resolve) => { +/** + * Fetches data needed for app initialization + */ +function openApp() { + isReadyToOpenApp.then(() => { + // We need a fresh connection/callback here to make sure that the list of policyIDs that is sent to OpenApp is the most updated list from Onyx const connectionID = Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY, waitForCollectionCallback: true, callback: (policies) => { Onyx.disconnect(connectionID); - resolve(policies); - }, - }); - }); -} -function getCollection(key) { - return new Promise((resolve) => { - const connectionID = Onyx.connect({ - key, - waitForCollectionCallback: true, - callback: (reports) => { - Onyx.disconnect(connectionID); - resolve(reports); - }, - }); - }); -} - -function getReports() { -} - -/** - * Fetches data needed for app initialization - */ -function openApp() { - let hasExistingReportData = false; - isReadyToOpenApp.then(() => - // If we are opening the app after a first sign in then we will have no data whatsoever. This is easily checked by looking to see if - // the user has any report data at all. All users should have at least one report with Concierge so this is a reliable way to check if - // we are signing in the first time or if the app is being opened after it was killed or the page refreshed. - getCollection(ONYXKEYS.COLLECTION.REPORT) - .then((reports) => { - console.log({reports}); - hasExistingReportData = !_.isEmpty(reports); - return getCollection(ONYXKEYS.COLLECTION.POLICY); - }) - .then((policies) => { API.read( 'OpenApp', {policyIDList: getNonOptimisticPolicyIDs(policies)}, @@ -209,22 +175,18 @@ function openApp() { ], }, ); - }) - ); + }, + }); + }); } /** * Refreshes data when the app reconnects */ function reconnectApp() { - // When the app reconnects we do a fast "sync" of the LHN and only return chats that have new messages. We achieve this by sending the most recent reportActionID - // we have locally. And then only update the user about chats with messages that have occurred after that reportActionID. - // - // - Look through the local report actions to find the most recently modified report action - // - We Send this to the server so that it can compute which chats are critical for the user to see so we can update them in the LHN API.write( CONST.NETWORK.COMMAND.RECONNECT_APP, - {policyIDList: getNonOptimisticPolicyIDs(allPolicies), mostRecentReportActionLastModified: ReportActionsUtils.getMostRecentReportActionLastModified()}, + {policyIDList: getNonOptimisticPolicyIDs(allPolicies)}, { optimisticData: [ { From 79feeef15079076e609314cc1d0a8c1a002b1cb5 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 13 Jun 2023 15:16:50 -1000 Subject: [PATCH 040/171] Revert "no app changes" This reverts commit ec5a7962c0f68fe415738206c10e93622c37a450. --- src/libs/actions/App.js | 58 ++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index eccf2623f7f5..556c7696eea7 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -15,6 +15,7 @@ import ROUTES from '../../ROUTES'; import * as SessionUtils from '../SessionUtils'; import getCurrentUrl from '../Navigation/currentUrl'; import * as Session from './Session'; +import * as ReportActionsUtils from '../ReportActionsUtils'; let currentUserAccountID; let currentUserEmail; @@ -137,17 +138,50 @@ AppState.addEventListener('change', (nextAppState) => { appState = nextAppState; }); -/** - * Fetches data needed for app initialization - */ -function openApp() { - isReadyToOpenApp.then(() => { - // We need a fresh connection/callback here to make sure that the list of policyIDs that is sent to OpenApp is the most updated list from Onyx +function getPolicies() { + return new Promise((resolve) => { const connectionID = Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY, waitForCollectionCallback: true, callback: (policies) => { Onyx.disconnect(connectionID); + resolve(policies); + }, + }); + }); +} +function getCollection(key) { + return new Promise((resolve) => { + const connectionID = Onyx.connect({ + key, + waitForCollectionCallback: true, + callback: (reports) => { + Onyx.disconnect(connectionID); + resolve(reports); + }, + }); + }); +} + +function getReports() { +} + +/** + * Fetches data needed for app initialization + */ +function openApp() { + let hasExistingReportData = false; + isReadyToOpenApp.then(() => + // If we are opening the app after a first sign in then we will have no data whatsoever. This is easily checked by looking to see if + // the user has any report data at all. All users should have at least one report with Concierge so this is a reliable way to check if + // we are signing in the first time or if the app is being opened after it was killed or the page refreshed. + getCollection(ONYXKEYS.COLLECTION.REPORT) + .then((reports) => { + console.log({reports}); + hasExistingReportData = !_.isEmpty(reports); + return getCollection(ONYXKEYS.COLLECTION.POLICY); + }) + .then((policies) => { API.read( 'OpenApp', {policyIDList: getNonOptimisticPolicyIDs(policies)}, @@ -175,18 +209,22 @@ function openApp() { ], }, ); - }, - }); - }); + }) + ); } /** * Refreshes data when the app reconnects */ function reconnectApp() { + // When the app reconnects we do a fast "sync" of the LHN and only return chats that have new messages. We achieve this by sending the most recent reportActionID + // we have locally. And then only update the user about chats with messages that have occurred after that reportActionID. + // + // - Look through the local report actions to find the most recently modified report action + // - We Send this to the server so that it can compute which chats are critical for the user to see so we can update them in the LHN API.write( CONST.NETWORK.COMMAND.RECONNECT_APP, - {policyIDList: getNonOptimisticPolicyIDs(allPolicies)}, + {policyIDList: getNonOptimisticPolicyIDs(allPolicies), mostRecentReportActionLastModified: ReportActionsUtils.getMostRecentReportActionLastModified()}, { optimisticData: [ { From e1ddfc22cb6da1dbe99415d68b5a0b07b8536cdf Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 13 Jun 2023 15:19:25 -1000 Subject: [PATCH 041/171] revert stuff --- src/libs/actions/App.js | 38 +++++++++----------------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 556c7696eea7..a07087379ab3 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -150,38 +150,17 @@ function getPolicies() { }); }); } -function getCollection(key) { - return new Promise((resolve) => { - const connectionID = Onyx.connect({ - key, - waitForCollectionCallback: true, - callback: (reports) => { - Onyx.disconnect(connectionID); - resolve(reports); - }, - }); - }); -} - -function getReports() { -} /** * Fetches data needed for app initialization */ function openApp() { - let hasExistingReportData = false; - isReadyToOpenApp.then(() => - // If we are opening the app after a first sign in then we will have no data whatsoever. This is easily checked by looking to see if - // the user has any report data at all. All users should have at least one report with Concierge so this is a reliable way to check if - // we are signing in the first time or if the app is being opened after it was killed or the page refreshed. - getCollection(ONYXKEYS.COLLECTION.REPORT) - .then((reports) => { - console.log({reports}); - hasExistingReportData = !_.isEmpty(reports); - return getCollection(ONYXKEYS.COLLECTION.POLICY); - }) - .then((policies) => { + isReadyToOpenApp.then(() => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.POLICY, + waitForCollectionCallback: true, + callback: (policies) => { + Onyx.disconnect(connectionID); API.read( 'OpenApp', {policyIDList: getNonOptimisticPolicyIDs(policies)}, @@ -209,8 +188,9 @@ function openApp() { ], }, ); - }) - ); + }, + }); + }); } /** From 2ac3528f15c86b1a6531a1e902f09e0a5c29b715 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 13 Jun 2023 15:19:44 -1000 Subject: [PATCH 042/171] revert more stuff --- src/libs/actions/App.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index a07087379ab3..e20188cabbe5 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -138,19 +138,6 @@ AppState.addEventListener('change', (nextAppState) => { appState = nextAppState; }); -function getPolicies() { - return new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.POLICY, - waitForCollectionCallback: true, - callback: (policies) => { - Onyx.disconnect(connectionID); - resolve(policies); - }, - }); - }); -} - /** * Fetches data needed for app initialization */ From 6adf6de408d27e415ae924f638c52c962333ed2a Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 13 Jun 2023 15:20:07 -1000 Subject: [PATCH 043/171] More --- src/libs/actions/App.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index e20188cabbe5..308c01e3da6a 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -143,8 +143,9 @@ AppState.addEventListener('change', (nextAppState) => { */ function openApp() { isReadyToOpenApp.then(() => { + // We need a fresh connection/callback here to make sure that the list of policyIDs that is sent to OpenApp is the most updated list from Onyx const connectionID = Onyx.connect({ - key: ONYXKEYS.POLICY, + key: ONYXKEYS.COLLECTION.POLICY, waitForCollectionCallback: true, callback: (policies) => { Onyx.disconnect(connectionID); From 81158575786f1375fe94f695f7fbda3dd53a8daf Mon Sep 17 00:00:00 2001 From: Nikhil Vats Date: Thu, 15 Jun 2023 15:53:03 +0530 Subject: [PATCH 044/171] Change const to function --- src/pages/NewChatPage.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 80e39422821b..0eb28f5a8c75 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -1,5 +1,5 @@ import _ from 'underscore'; -import React, {useState, useEffect, useMemo} from 'react'; +import React, {useState, useEffect} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; @@ -60,7 +60,7 @@ function NewChatPage(props) { ); const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(props.personalDetails); - const sections = useMemo(() => { + function getSections() { const sectionsList = []; let indexOffset = 0; @@ -110,8 +110,7 @@ function NewChatPage(props) { } return sectionsList; - // eslint-disable-next-line react-hooks/exhaustive-deps -- to avoid destructuring props and adding all 'props' as a dependency - }, [props.isGroupChat, props.translate, selectedOptions, filteredRecentReports, filteredPersonalDetails, filteredUserToInvite, maxParticipantsReached]); + } const updateOptionsWithSearchTerm = (newSearchTerm = '') => { const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( @@ -132,7 +131,7 @@ function NewChatPage(props) { * Removes a selected option from list if already selected. If not already selected add this option to the list. * @param {Object} option */ - const toggleOption = (option) => { + function toggleOption(option) { const isOptionInList = _.some(selectedOptions, (selectedOption) => selectedOption.login === option.login); let newSelectedOptions; @@ -149,7 +148,7 @@ function NewChatPage(props) { setFilteredRecentReports(recentReports); setFilteredPersonalDetails(personalDetails); setFilteredUserToInvite(userToInvite); - }; + } /** * Creates a new 1:1 chat with the option and the current user, @@ -157,9 +156,9 @@ function NewChatPage(props) { * * @param {Object} option */ - const createChat = (option) => { + function createChat(option) { Report.navigateToAndOpenReport([option.login]); - }; + } /** * Creates a new group chat with all the selected options and the current user, @@ -185,6 +184,8 @@ function NewChatPage(props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.reports, props.personalDetails]); + const sections = getSections(); + return ( Date: Thu, 15 Jun 2023 07:03:14 -1000 Subject: [PATCH 045/171] account for possible undefined report value --- src/libs/ReportActionsUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 992c751889c7..9de23c6bd61c 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -451,7 +451,7 @@ function getMostRecentReportActionLastModified() { // any reports that have been updated before either a recently updated report or reportAction as we should be up to date on these _.each(allReports, (report) => { const reportLastVisibleActionLastModified = report.lastVisibleActionLastModified || report.lastVisibleActionCreated; - if (reportLastVisibleActionLastModified < mostRecentReportActionLastModified) { + if (!reportLastVisibleActionLastModified || reportLastVisibleActionLastModified < mostRecentReportActionLastModified) { return; } From 041d78227ec5164f8aeeb8423358857c1b1ff04d Mon Sep 17 00:00:00 2001 From: oleh Date: Thu, 15 Jun 2023 20:04:01 +0300 Subject: [PATCH 046/171] fixed conflicts from main branch --- .../MoneyRequestParticipantsSplitSelector.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js index 9c77ef68b0e2..6f01444b1f04 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js @@ -29,6 +29,7 @@ const propTypes = { participants: PropTypes.arrayOf( PropTypes.shape({ login: PropTypes.string.isRequired, + accountID: PropTypes.number.isRequired, alternateText: PropTypes.string, hasDraftComment: PropTypes.bool, icons: PropTypes.arrayOf(avatarPropTypes), @@ -124,12 +125,12 @@ function MoneyRequestParticipantsSplitSelector(props) { * @param {Object} option */ const toggleOption = (option) => { - const isOptionInList = _.some(props.participants, (selectedOption) => selectedOption.login === option.login); + const isOptionInList = _.some(props.participants, (selectedOption) => selectedOption.accountID === option.accountID); let newSelectedOptions; if (isOptionInList) { - newSelectedOptions = _.reject(props.participants, (selectedOption) => selectedOption.login === option.login); + newSelectedOptions = _.reject(props.participants, (selectedOption) => selectedOption.accountID === option.accountID); } else { newSelectedOptions = [...props.participants, option]; } @@ -215,7 +216,7 @@ export default compose( withLocalize, withOnyx({ personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS, + key: ONYXKEYS.PERSONAL_DETAILS_LIST, }, reports: { key: ONYXKEYS.COLLECTION.REPORT, From 112b97dc19462b79bad54c4ebc29a82aa6480f33 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 16 Jun 2023 08:19:42 -1000 Subject: [PATCH 047/171] update comment --- src/libs/actions/App.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 308c01e3da6a..294ee71fc1b5 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -185,11 +185,11 @@ function openApp() { * Refreshes data when the app reconnects */ function reconnectApp() { - // When the app reconnects we do a fast "sync" of the LHN and only return chats that have new messages. We achieve this by sending the most recent reportActionID + // When the app reconnects we do a fast "sync" of the LHN and only return chats that have new messages. We achieve this by sending the most recent reportActionID. // we have locally. And then only update the user about chats with messages that have occurred after that reportActionID. // - // - Look through the local report actions to find the most recently modified report action - // - We Send this to the server so that it can compute which chats are critical for the user to see so we can update them in the LHN + // - Look through the local report actions and reports to find the most recently modified report action or report. + // - We send this to the server so that it can compute which chats are critical for the user to see and return only those as an optimization. API.write( CONST.NETWORK.COMMAND.RECONNECT_APP, {policyIDList: getNonOptimisticPolicyIDs(allPolicies), mostRecentReportActionLastModified: ReportActionsUtils.getMostRecentReportActionLastModified()}, From 8e176eece66b154c89f8914296d27be101dc98b5 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Fri, 16 Jun 2023 11:52:47 -0700 Subject: [PATCH 048/171] refactor isThread to isChatThread --- src/libs/OptionsListUtils.js | 4 ++-- src/libs/ReportUtils.js | 22 +++++++++---------- src/libs/SidebarUtils.js | 2 +- src/pages/ReportDetailsPage.js | 2 +- src/pages/ReportParticipantsPage.js | 2 +- src/pages/home/HeaderView.js | 2 +- src/pages/home/report/ReportActionsList.js | 2 +- .../settings/Report/ReportSettingsPage.js | 4 ++-- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 8669eea4703a..40a3a3dc53fb 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -452,7 +452,7 @@ function createOption(accountIDs, personalDetails, report, reportActions = {}, { result.isArchivedRoom = ReportUtils.isArchivedRoom(report); result.isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report); result.isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); - result.isThread = ReportUtils.isThread(report); + result.isThread = ReportUtils.isChatThread(report); result.isTaskReport = ReportUtils.isTaskReport(report); result.shouldShowSubscript = ReportUtils.shouldReportShowSubscript(report); result.allReportErrors = getAllReportErrors(report, reportActions); @@ -630,7 +630,7 @@ function getOptions( return; } - const isThread = ReportUtils.isThread(report); + const isThread = ReportUtils.isChatThread(report); const isChatRoom = ReportUtils.isChatRoom(report); const isTaskReport = ReportUtils.isTaskReport(report); const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report); diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 02c3308898f8..9a4dd5a5c738 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -536,7 +536,7 @@ function isPolicyExpenseChatAdmin(report, policies) { * @param {Object} report * @returns {Boolean} */ -function isThread(report) { +function isChatThread(report) { return Boolean(report && report.parentReportID && report.parentReportActionID && report.type === CONST.REPORT.TYPE.CHAT); } @@ -733,7 +733,7 @@ function getIcons(report, personalDetails, defaultIcon = null, isPayer = false) result.source = Expensicons.DeletedRoomAvatar; return [result]; } - if (isThread(report)) { + if (isChatThread(report)) { const parentReportAction = ReportActionsUtils.getParentReportAction(report); const actorEmail = lodashGet(parentReportAction, 'actorEmail', ''); @@ -1006,7 +1006,7 @@ function getTransactionReportName(reportAction) { */ function getReportName(report) { let formattedName; - if (isThread(report)) { + if (isChatThread(report)) { const parentReportAction = ReportActionsUtils.getParentReportAction(report); if (ReportActionsUtils.isTransactionThread(parentReportAction)) { return getTransactionReportName(parentReportAction); @@ -1053,7 +1053,7 @@ function getReportName(report) { * @returns {String|*} */ function getDMRootReportName(report) { - if (isThread(report) && !getChatType(report)) { + if (isChatThread(report) && !getChatType(report)) { const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); return getDMRootReportName(parentReport); } @@ -1067,7 +1067,7 @@ function getDMRootReportName(report) { * @returns {String} */ function getChatRoomSubtitle(report) { - if (isThread(report)) { + if (isChatThread(report)) { if (!getChatType(report)) { return `${Localize.translateLocal('threads.from')} ${getDMRootReportName(report)}`; } @@ -1119,7 +1119,7 @@ function getReport(reportID) { function navigateToDetailsPage(report) { const participantAccountIDs = lodashGet(report, 'participantAccountIDs', []); - if (isChatRoom(report) || isPolicyExpenseChat(report) || isThread(report)) { + if (isChatRoom(report) || isPolicyExpenseChat(report) || isChatThread(report)) { Navigation.navigate(ROUTES.getReportDetailsRoute(report.reportID)); return; } @@ -1918,7 +1918,7 @@ function shouldReportBeInOptionList(report, reportIDFromRoute, isInGSDMode, iouR if ( !report || !report.reportID || - (_.isEmpty(report.participantAccountIDs) && !isThread(report) && !isPublicRoom(report) && !isArchivedRoom(report) && !isMoneyRequestReport(report) && !isTaskReport(report)) + (_.isEmpty(report.participantAccountIDs) && !isChatThread(report) && !isPublicRoom(report) && !isArchivedRoom(report) && !isMoneyRequestReport(report) && !isTaskReport(report)) ) { return false; } @@ -1973,7 +1973,7 @@ function getChatByParticipants(newParticipantList) { newParticipantList.sort(); return _.find(allReports, (report) => { // If the report has been deleted, or there are no participants (like an empty #admins room) then skip it - if (!report || !report.participantAccountIDs || isThread(report)) { + if (!report || !report.participantAccountIDs || isChatThread(report)) { return false; } @@ -2113,7 +2113,7 @@ function canRequestMoney(report) { */ function getMoneyRequestOptions(report, reportParticipants, betas) { // In any thread, we do not allow any new money requests yet - if (isThread(report)) { + if (isChatThread(report)) { return []; } @@ -2219,7 +2219,7 @@ function shouldReportShowSubscript(report) { return false; } - if (isPolicyExpenseChat(report) && !isThread(report) && !isTaskReport(report) && !report.isOwnPolicyExpenseChat) { + if (isPolicyExpenseChat(report) && !isChatThread(report) && !isTaskReport(report) && !report.isOwnPolicyExpenseChat) { return true; } @@ -2329,7 +2329,7 @@ export { canRequestMoney, getWhisperDisplayNames, getWorkspaceAvatar, - isThread, + isChatThread, isThreadParent, isThreadFirstChat, shouldReportShowSubscript, diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js index a41249ebfa3d..7bd30a13af16 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.js @@ -263,7 +263,7 @@ function getOptionData(reportID) { const participantPersonalDetailList = _.values(OptionsListUtils.getPersonalDetailsForAccountIDs(report.participantAccountIDs, personalDetails)); const personalDetail = participantPersonalDetailList[0] || {}; - result.isThread = ReportUtils.isThread(report); + result.isThread = ReportUtils.isChatThread(report); result.isChatRoom = ReportUtils.isChatRoom(report); result.isTaskReport = ReportUtils.isTaskReport(report); if (result.isTaskReport) { diff --git a/src/pages/ReportDetailsPage.js b/src/pages/ReportDetailsPage.js index 19f78e08c604..c0e3a07bc214 100644 --- a/src/pages/ReportDetailsPage.js +++ b/src/pages/ReportDetailsPage.js @@ -63,7 +63,7 @@ function ReportDetailsPage(props) { const isPolicyAdmin = useMemo(() => PolicyUtils.isPolicyAdmin(policy), [policy]); const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(props.report), [props.report]); const isChatRoom = useMemo(() => ReportUtils.isChatRoom(props.report), [props.report]); - const isThread = useMemo(() => ReportUtils.isThread(props.report), [props.report]); + const isThread = useMemo(() => ReportUtils.isChatThread(props.report), [props.report]); const isUserCreatedPolicyRoom = useMemo(() => ReportUtils.isUserCreatedPolicyRoom(props.report), [props.report]); const isArchivedRoom = useMemo(() => ReportUtils.isArchivedRoom(props.report), [props.report]); diff --git a/src/pages/ReportParticipantsPage.js b/src/pages/ReportParticipantsPage.js index da065dadcb77..176a0d8f07a4 100755 --- a/src/pages/ReportParticipantsPage.js +++ b/src/pages/ReportParticipantsPage.js @@ -92,7 +92,7 @@ function ReportParticipantsPage(props) { 1; const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(participantPersonalDetails, isMultipleParticipant); - const isThread = ReportUtils.isThread(props.report); + const isThread = ReportUtils.isChatThread(props.report); const isChatRoom = ReportUtils.isChatRoom(props.report); const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(props.report); const isTaskReport = ReportUtils.isTaskReport(props.report); diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index c3288c3eeb6a..1a8b74600e28 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -115,7 +115,7 @@ function ReportActionsList(props) { ({item: reportAction, index}) => { // When the new indicator should not be displayed we explicitly set it to null const shouldDisplayNewMarker = reportAction.reportActionID === newMarkerReportActionID; - const shouldDisplayParentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED && ReportUtils.isThread(report); + const shouldDisplayParentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED && ReportUtils.isChatThread(report); const shouldHideThreadDividerLine = shouldDisplayParentAction && sortedReportActions.length > 1 && sortedReportActions[sortedReportActions.length - 2].reportActionID === newMarkerReportActionID; return shouldDisplayParentAction ? ( diff --git a/src/pages/settings/Report/ReportSettingsPage.js b/src/pages/settings/Report/ReportSettingsPage.js index ad57de80aaaa..d07da128acd6 100644 --- a/src/pages/settings/Report/ReportSettingsPage.js +++ b/src/pages/settings/Report/ReportSettingsPage.js @@ -94,9 +94,9 @@ class ReportSettingsPage extends Component { } render() { - const shouldShowRoomName = !ReportUtils.isPolicyExpenseChat(this.props.report) && !ReportUtils.isThread(this.props.report); + const shouldShowRoomName = !ReportUtils.isPolicyExpenseChat(this.props.report) && !ReportUtils.isChatThread(this.props.report); const linkedWorkspace = _.find(this.props.policies, (policy) => policy && policy.id === this.props.report.policyID); - const shouldDisableRename = this.shouldDisableRename(linkedWorkspace) || ReportUtils.isThread(this.props.report); + const shouldDisableRename = this.shouldDisableRename(linkedWorkspace) || ReportUtils.isChatThread(this.props.report); const notificationPreference = this.props.translate(`notificationPreferencesPage.notificationPreferences.${this.props.report.notificationPreference}`); const shouldDisableWelcomeMessage = this.shouldDisableWelcomeMessage(linkedWorkspace); const writeCapability = this.props.report.writeCapability || CONST.REPORT.WRITE_CAPABILITIES.ALL; From f054742c6a02441ca0e9d301a7f4e4ff0d68e1d1 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Fri, 16 Jun 2023 11:55:47 -0700 Subject: [PATCH 049/171] isthread and getChatRoomSubtitleLink --- src/libs/ReportUtils.js | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 9a4dd5a5c738..2a5476e1ef64 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -531,7 +531,17 @@ function isPolicyExpenseChatAdmin(report, policies) { } /** - * Returns true if report has a parent and is therefore a Thread. + * Returns true if report has a parent + * + * @param {Object} report + * @returns {Boolean} + */ +function isThread(report) { + return Boolean(report && report.parentReportID && report.parentReportActionID); +} + +/** + * Returns true if report is of type chat and has a parent and is therefore a Thread. * * @param {Object} report * @returns {Boolean} @@ -1101,6 +1111,33 @@ function getChatRoomSubtitle(report) { return getPolicyName(report); } +/** + * Get either the policyName or domainName the chat is tied to + * @param {Object} report + * @returns {String} + */ +function getChatRoomSubtitleLink(report) { + if (isThread(report)) { + if (!getChatType(report)) { + return `${Localize.translateLocal('threads.from')} ${getDMRootReportName(report)}`; + } + + let roomName = ''; + if (isChatRoom(report)) { + const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); + if (parentReport) { + roomName = lodashGet(parentReport, 'displayName', ''); + } else { + roomName = lodashGet(report, 'displayName', ''); + } + } + + const workspaceName = getPolicyName(report); + return `${Localize.translateLocal('threads.from')} ${roomName ? [roomName, workspaceName].join(' in ') : workspaceName}`; + } + return ''; +} + /** * Get the report for a reportID * @@ -2263,6 +2300,7 @@ export { isUserCreatedPolicyRoom, isChatRoom, getChatRoomSubtitle, + getChatRoomSubtitleLink, getPolicyName, getPolicyType, isArchivedRoom, @@ -2329,6 +2367,7 @@ export { canRequestMoney, getWhisperDisplayNames, getWorkspaceAvatar, + isThread, isChatThread, isThreadParent, isThreadFirstChat, From d78fe9420702d98093560c22eeed14bb0ba8d329 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Fri, 16 Jun 2023 11:58:14 -0700 Subject: [PATCH 050/171] getRootReportName refactor --- src/libs/ReportUtils.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 2a5476e1ef64..0ca112f01703 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1058,14 +1058,14 @@ function getReportName(report) { } /** - * Recursively navigates through parent to get the root reports name only for DM reports. + * Recursively navigates through thread parents to get the root reports name. * @param {Object} report * @returns {String|*} */ -function getDMRootReportName(report) { - if (isChatThread(report) && !getChatType(report)) { +function getRootReportName(report) { + if (isThread(report)) { const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); - return getDMRootReportName(parentReport); + return getRootReportName(parentReport); } return getReportName(report); @@ -1079,7 +1079,7 @@ function getDMRootReportName(report) { function getChatRoomSubtitle(report) { if (isChatThread(report)) { if (!getChatType(report)) { - return `${Localize.translateLocal('threads.from')} ${getDMRootReportName(report)}`; + return `${Localize.translateLocal('threads.from')} ${getRootReportName(report)}`; } let roomName = ''; @@ -1118,10 +1118,6 @@ function getChatRoomSubtitle(report) { */ function getChatRoomSubtitleLink(report) { if (isThread(report)) { - if (!getChatType(report)) { - return `${Localize.translateLocal('threads.from')} ${getDMRootReportName(report)}`; - } - let roomName = ''; if (isChatRoom(report)) { const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); @@ -1130,10 +1126,12 @@ function getChatRoomSubtitleLink(report) { } else { roomName = lodashGet(report, 'displayName', ''); } + + const workspaceName = getPolicyName(report); + return `${Localize.translateLocal('threads.from')} ${roomName ? [roomName, workspaceName].join(' in ') : workspaceName}`; } - const workspaceName = getPolicyName(report); - return `${Localize.translateLocal('threads.from')} ${roomName ? [roomName, workspaceName].join(' in ') : workspaceName}`; + return `${Localize.translateLocal('threads.from')} ${getRootReportName(report)}`; } return ''; } From 563fe3220c4736e13d0af6769fb4a9c1bb0c7b95 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Fri, 16 Jun 2023 12:45:14 -0700 Subject: [PATCH 051/171] updating getChatRoomSubtitleLink --- src/libs/ReportUtils.js | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 0ca112f01703..bb8bd8b7c288 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1078,22 +1078,7 @@ function getRootReportName(report) { */ function getChatRoomSubtitle(report) { if (isChatThread(report)) { - if (!getChatType(report)) { - return `${Localize.translateLocal('threads.from')} ${getRootReportName(report)}`; - } - - let roomName = ''; - if (isChatRoom(report)) { - const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); - if (parentReport) { - roomName = lodashGet(parentReport, 'displayName', ''); - } else { - roomName = lodashGet(report, 'displayName', ''); - } - } - - const workspaceName = getPolicyName(report); - return `${Localize.translateLocal('threads.from')} ${roomName ? [roomName, workspaceName].join(' in ') : workspaceName}`; + return ''; } if (!isDefaultRoom(report) && !isUserCreatedPolicyRoom(report) && !isPolicyExpenseChat(report)) { return ''; @@ -1117,10 +1102,15 @@ function getChatRoomSubtitle(report) { * @returns {String} */ function getChatRoomSubtitleLink(report) { + if (report.reportID == '3346402972554959') { + debugger; + } + if (isThread(report)) { - let roomName = ''; + const from = Localize.translateLocal('threads.from'); if (isChatRoom(report)) { const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); + let roomName = ''; if (parentReport) { roomName = lodashGet(parentReport, 'displayName', ''); } else { @@ -1128,10 +1118,15 @@ function getChatRoomSubtitleLink(report) { } const workspaceName = getPolicyName(report); - return `${Localize.translateLocal('threads.from')} ${roomName ? [roomName, workspaceName].join(' in ') : workspaceName}`; + return `${from} ${roomName ? [roomName, workspaceName].join(' in ') : workspaceName}`; } - return `${Localize.translateLocal('threads.from')} ${getRootReportName(report)}`; + if (isExpenseReport(report)) { + const payeeEmail = getDisplayNameForParticipant(report.managerID); + const workspaceName = getPolicyName(report); + return `${from} ${workspaceName ? [payeeEmail, workspaceName].join(' in ') : payeeEmail}`; + } + return `${from} ${getRootReportName(report)}`; } return ''; } From ff3b93d06c816cc5fdaab5431b3c1b04ec8dc9bc Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Fri, 16 Jun 2023 12:45:25 -0700 Subject: [PATCH 052/171] using subtitleLink --- src/pages/home/HeaderView.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 7334bf174e60..86eaa10c2311 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -82,6 +82,7 @@ function HeaderView(props) { const reportHeaderData = !isTaskReport && !isThread && props.report.parentReportID ? props.parentReport : props.report; const title = ReportUtils.getReportName(reportHeaderData); const subtitle = ReportUtils.getChatRoomSubtitle(reportHeaderData); + const subtitleLink = ReportUtils.getChatRoomSubtitleLink(reportHeaderData); const isConcierge = participants.length === 1 && _.contains(participants, CONST.ACCOUNT_ID.CONCIERGE); const isAutomatedExpensifyAccount = participants.length === 1 && ReportUtils.hasAutomatedExpensifyAccountIDs(participants); const guideCalendarLink = lodashGet(props.account, 'guideCalendarLink'); @@ -177,24 +178,25 @@ function HeaderView(props) { textStyles={[styles.headerText, styles.pre]} shouldUseFullTitle={isChatRoom || isPolicyExpenseChat || isThread || isTaskReport} /> - {(isChatRoom || isPolicyExpenseChat || isThread) && !_.isEmpty(subtitle) && ( + {(isChatRoom || isPolicyExpenseChat || isThread) && ( <> - {isThread ? ( + {!_.isEmpty(subtitleLink) && ( { Navigation.navigate(ROUTES.getReportRoute(props.report.parentReportID)); }} - accessibilityLabel={subtitle} + accessibilityLabel={subtitleLink} accessibilityRole="link" > - {subtitle} + {subtitleLink} - ) : ( + )} + {!_.isEmpty(subtitle) && ( Date: Fri, 16 Jun 2023 12:45:46 -0700 Subject: [PATCH 053/171] using subtitleLink in AvatarWithDisplayName --- src/components/AvatarWithDisplayName.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index 7889dcb0b703..c5a7fcbacc1c 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -17,6 +17,10 @@ import compose from '../libs/compose'; import * as OptionsListUtils from '../libs/OptionsListUtils'; import Text from './Text'; import * as StyleUtils from '../styles/StyleUtils'; +import {getChatRoomSubtitleLink} from '../libs/ReportUtils'; +import Navigation from '../libs/Navigation/Navigation'; +import ROUTES from '../ROUTES'; +import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; const propTypes = { /** The report currently being looked at */ @@ -57,6 +61,7 @@ function AvatarWithDisplayName(props) { const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs([props.report.ownerAccountID], props.personalDetails); const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(_.values(ownerPersonalDetails), false); const avatarContainerStyle = StyleUtils.getEmptyAvatarStyle(props.size) || styles.emptyAvatar; + const subtitleLink = ReportUtils.getChatRoomSubtitleLink(props.report); return ( {Boolean(props.report && title) && ( @@ -88,6 +93,22 @@ function AvatarWithDisplayName(props) { textStyles={[props.isAnonymous ? styles.headerAnonymousFooter : styles.headerText, styles.pre]} shouldUseFullTitle={isExpenseReport || props.isAnonymous} /> + {!_.isEmpty(subtitleLink) && ( + { + Navigation.navigate(ROUTES.getReportRoute(props.report.parentReportID)); + }} + accessibilityLabel={subtitle} + accessibilityRole="link" + > + + {subtitleLink} + + + )} {!_.isEmpty(subtitle) && ( Date: Fri, 16 Jun 2023 10:48:04 -1000 Subject: [PATCH 054/171] Revert problem code --- src/CONST.js | 3 - src/libs/HttpUtils.js | 22 +----- .../Navigation/AppNavigator/AuthScreens.js | 10 ++- src/libs/SessionUtils.js | 33 ++++++++- src/libs/actions/App.js | 73 +++++++++++-------- src/libs/actions/PersistedRequests.js | 9 +-- src/libs/actions/SignInRedirect.js | 2 + 7 files changed, 88 insertions(+), 64 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index 50bfb5675bd8..8b954a010da6 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -724,9 +724,6 @@ const CONST = { MAX_RETRY_WAIT_TIME_MS: 10 * 1000, PROCESS_REQUEST_DELAY_MS: 1000, MAX_PENDING_TIME_MS: 10 * 1000, - COMMAND: { - RECONNECT_APP: 'ReconnectApp', - }, }, DEFAULT_TIME_ZONE: {automatic: true, selected: 'America/Los_Angeles'}, DEFAULT_ACCOUNT_DATA: {errors: null, success: '', isLoading: false}, diff --git a/src/libs/HttpUtils.js b/src/libs/HttpUtils.js index e76517e2059f..5a8185a03038 100644 --- a/src/libs/HttpUtils.js +++ b/src/libs/HttpUtils.js @@ -22,9 +22,6 @@ Onyx.connect({ // We use the AbortController API to terminate pending request in `cancelPendingRequests` let cancellationController = new AbortController(); -// To terminate pending ReconnectApp requests https://github.com/Expensify/App/issues/15627 -let reconnectAppCancellationController = new AbortController(); - /** * Send an HTTP request, and attempt to resolve the json response. * If there is a network error, we'll set the application offline. @@ -33,18 +30,12 @@ let reconnectAppCancellationController = new AbortController(); * @param {String} [method] * @param {Object} [body] * @param {Boolean} [canCancel] - * @param {String} [command] * @returns {Promise} */ -function processHTTPRequest(url, method = 'get', body = null, canCancel = true, command = '') { - let signal; - if (canCancel) { - signal = command === CONST.NETWORK.COMMAND.RECONNECT_APP ? reconnectAppCancellationController.signal : cancellationController.signal; - } - +function processHTTPRequest(url, method = 'get', body = null, canCancel = true) { return fetch(url, { // We hook requests to the same Controller signal, so we can cancel them all at once - signal, + signal: canCancel ? cancellationController.signal : undefined, method, body, }) @@ -136,12 +127,7 @@ function xhr(command, data, type = CONST.NETWORK.METHOD.POST, shouldUseSecure = }); const url = ApiUtils.getCommandURL({shouldUseSecure, command}); - return processHTTPRequest(url, type, formData, data.canCancel, command); -} - -function cancelPendingReconnectAppRequest() { - reconnectAppCancellationController.abort(); - reconnectAppCancellationController = new AbortController(); + return processHTTPRequest(url, type, formData, data.canCancel); } function cancelPendingRequests() { @@ -150,11 +136,9 @@ function cancelPendingRequests() { // We create a new instance because once `abort()` is called any future requests using the same controller would // automatically get rejected: https://dom.spec.whatwg.org/#abortcontroller-api-integration cancellationController = new AbortController(); - cancelPendingReconnectAppRequest(); } export default { xhr, cancelPendingRequests, - cancelPendingReconnectAppRequest, }; diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index fb02a811b0ec..182e1cca4a65 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -32,6 +32,7 @@ import CentralPaneNavigator from './Navigators/CentralPaneNavigator'; import NAVIGATORS from '../../../NAVIGATORS'; import FullScreenNavigator from './Navigators/FullScreenNavigator'; import styles from '../../../styles/styles'; +import * as SessionUtils from '../../SessionUtils'; let currentUserEmail; Onyx.connect({ @@ -119,7 +120,14 @@ class AuthScreens extends React.Component { User.subscribeToUserEvents(); }); - App.openApp(); + // If we are on this screen then we are "logged in", but the user might not have "just logged in". They could be reopening the app + // or returning from background. If so, we'll assume they have some app data already and we can call reconnectApp() instead of openApp(). + if (SessionUtils.didUserLogInDuringSession()) { + App.openApp(); + } else { + App.reconnectApp(); + } + App.setUpPoliciesAndNavigate(this.props.session); if (this.props.lastOpenedPublicRoomID) { diff --git a/src/libs/SessionUtils.js b/src/libs/SessionUtils.js index 875b540e5599..4d4d4b7658b9 100644 --- a/src/libs/SessionUtils.js +++ b/src/libs/SessionUtils.js @@ -1,4 +1,7 @@ +import Onyx from 'react-native-onyx'; +import _ from 'underscore'; import lodashGet from 'lodash/get'; +import ONYXKEYS from '../ONYXKEYS'; /** * Determine if the transitioning user is logging in as a new user. @@ -28,7 +31,35 @@ function isLoggingInAsNewUser(transitionURL, sessionEmail) { return linkedEmail !== sessionEmail; } +let loggedInDuringSession; + +// To tell if the user logged in during this session we will check the value of session.authToken once when the app's JS inits. When the user logs out +// we can reset this flag so that it can be updated again. +Onyx.connect({ + key: ONYXKEYS.SESSION, + callback: (session) => { + if (!_.isUndefined(loggedInDuringSession)) { + return; + } + + if (session && session.authToken) { + loggedInDuringSession = false; + } else { + loggedInDuringSession = true; + } + }, +}); + +function resetDidUserLogInDuringSession() { + loggedInDuringSession = undefined; +} + +function didUserLogInDuringSession() { + return Boolean(loggedInDuringSession); +} + export { - // eslint-disable-next-line import/prefer-default-export isLoggingInAsNewUser, + didUserLogInDuringSession, + resetDidUserLogInDuringSession, }; diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 821e7f13a254..d01ebd25aebb 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -183,38 +183,47 @@ function openApp() { * Refreshes data when the app reconnects */ function reconnectApp() { - // When the app reconnects we do a fast "sync" of the LHN and only return chats that have new messages. We achieve this by sending the most recent reportActionID. - // we have locally. And then only update the user about chats with messages that have occurred after that reportActionID. - // - // - Look through the local report actions and reports to find the most recently modified report action or report. - // - We send this to the server so that it can compute which chats are critical for the user to see and return only those as an optimization. - API.write( - CONST.NETWORK.COMMAND.RECONNECT_APP, - {policyIDList: getNonOptimisticPolicyIDs(allPolicies), mostRecentReportActionLastModified: ReportActionsUtils.getMostRecentReportActionLastModified()}, - { - optimisticData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.IS_LOADING_REPORT_DATA, - value: true, - }, - ], - successData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.IS_LOADING_REPORT_DATA, - value: false, - }, - ], - failureData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.IS_LOADING_REPORT_DATA, - value: false, - }, - ], - }, - ); + isReadyToOpenApp.then(() => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.POLICY, + waitForCollectionCallback: true, + callback: (policies) => { + Onyx.disconnect(connectionID); + // When the app reconnects we do a fast "sync" of the LHN and only return chats that have new messages. We achieve this by sending the most recent reportActionID. + // we have locally. And then only update the user about chats with messages that have occurred after that reportActionID. + // + // - Look through the local report actions and reports to find the most recently modified report action or report. + // - We send this to the server so that it can compute which chats are critical for the user to see and return only those as an optimization. + API.write( + 'ReconnectApp', + {policyIDList: getNonOptimisticPolicyIDs(policies), mostRecentReportActionLastModified: ReportActionsUtils.getMostRecentReportActionLastModified()}, + { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.IS_LOADING_REPORT_DATA, + value: true, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.IS_LOADING_REPORT_DATA, + value: false, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.IS_LOADING_REPORT_DATA, + value: false, + }, + ], + }, + ); + }, + }); + }); } /** diff --git a/src/libs/actions/PersistedRequests.js b/src/libs/actions/PersistedRequests.js index e3aafd18c35d..d893ee255287 100644 --- a/src/libs/actions/PersistedRequests.js +++ b/src/libs/actions/PersistedRequests.js @@ -1,8 +1,6 @@ import Onyx from 'react-native-onyx'; import _ from 'underscore'; -import CONST from '../../CONST'; import ONYXKEYS from '../../ONYXKEYS'; -import HttpUtils from '../HttpUtils'; let persistedRequests = []; @@ -19,12 +17,7 @@ function clear() { * @param {Array} requestsToPersist */ function save(requestsToPersist) { - HttpUtils.cancelPendingReconnectAppRequest(); - persistedRequests = _.chain(persistedRequests) - .concat(requestsToPersist) - .partition((request) => request.command !== CONST.NETWORK.COMMAND.RECONNECT_APP) - .flatten() - .value(); + persistedRequests = persistedRequests.concat(requestsToPersist); Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, persistedRequests); } diff --git a/src/libs/actions/SignInRedirect.js b/src/libs/actions/SignInRedirect.js index a500635222d6..a010621c4eea 100644 --- a/src/libs/actions/SignInRedirect.js +++ b/src/libs/actions/SignInRedirect.js @@ -10,6 +10,7 @@ import navigationRef from '../Navigation/navigationRef'; import SCREENS from '../../SCREENS'; import Navigation from '../Navigation/Navigation'; import * as ErrorUtils from '../ErrorUtils'; +import * as SessionUtils from '../SessionUtils'; let currentIsOffline; let currentShouldForceOffline; @@ -87,6 +88,7 @@ function redirectToSignIn(errorMessage) { NetworkConnection.clearReconnectionCallbacks(); clearStorageAndRedirect(errorMessage); resetHomeRouteParams(); + SessionUtils.resetDidUserLogInDuringSession(); } export default redirectToSignIn; From c0583f8ca6ba077fbc1bf2eb0f4093ee652ffa3d Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 16 Jun 2023 11:02:54 -1000 Subject: [PATCH 055/171] DRY up openApp/reconnectApp method --- src/libs/actions/App.js | 71 +++++++++++------------------------------ 1 file changed, 18 insertions(+), 53 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index d01ebd25aebb..5d23fddd9654 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -44,13 +44,6 @@ Onyx.connect({ }, }); -let allPolicies = []; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.POLICY, - waitForCollectionCallback: true, - callback: (policies) => (allPolicies = policies), -}); - let preferredLocale; Onyx.connect({ key: ONYXKEYS.NVP_PREFERRED_LOCALE, @@ -138,18 +131,30 @@ AppState.addEventListener('change', (nextAppState) => { /** * Fetches data needed for app initialization + * @param {boolean} [isReconnecting] */ -function openApp() { +function openApp(isReconnecting = false) { isReadyToOpenApp.then(() => { - // We need a fresh connection/callback here to make sure that the list of policyIDs that is sent to OpenApp is the most updated list from Onyx const connectionID = Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY, waitForCollectionCallback: true, callback: (policies) => { + // When the app reconnects we do a fast "sync" of the LHN and only return chats that have new messages. We achieve this by sending the most recent reportActionID. + // we have locally. And then only update the user about chats with messages that have occurred after that reportActionID. + // + // - Look through the local report actions and reports to find the most recently modified report action or report. + // - We send this to the server so that it can compute which chats are critical for the user to see and return only those as an optimization. + const params = {policyIDList: getNonOptimisticPolicyIDs(policies)}; + if (isReconnecting) { + params.mostRecentReportActionLastModified = ReportActionsUtils.getMostRecentReportActionLastModified(); + } Onyx.disconnect(connectionID); - API.read( - 'OpenApp', - {policyIDList: getNonOptimisticPolicyIDs(policies)}, + + // eslint-disable-next-line rulesdir/no-multiple-api-calls + const apiMethod = isReconnecting ? API.write : API.read; + apiMethod( + isReconnecting ? 'ReconnectApp' : 'OpenApp', + params, { optimisticData: [ { @@ -183,47 +188,7 @@ function openApp() { * Refreshes data when the app reconnects */ function reconnectApp() { - isReadyToOpenApp.then(() => { - const connectionID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.POLICY, - waitForCollectionCallback: true, - callback: (policies) => { - Onyx.disconnect(connectionID); - // When the app reconnects we do a fast "sync" of the LHN and only return chats that have new messages. We achieve this by sending the most recent reportActionID. - // we have locally. And then only update the user about chats with messages that have occurred after that reportActionID. - // - // - Look through the local report actions and reports to find the most recently modified report action or report. - // - We send this to the server so that it can compute which chats are critical for the user to see and return only those as an optimization. - API.write( - 'ReconnectApp', - {policyIDList: getNonOptimisticPolicyIDs(policies), mostRecentReportActionLastModified: ReportActionsUtils.getMostRecentReportActionLastModified()}, - { - optimisticData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.IS_LOADING_REPORT_DATA, - value: true, - }, - ], - successData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.IS_LOADING_REPORT_DATA, - value: false, - }, - ], - failureData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.IS_LOADING_REPORT_DATA, - value: false, - }, - ], - }, - ); - }, - }); - }); + openApp(true); } /** From 0942351991c4328d8731995eaf96752658b9c9ae Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 16 Jun 2023 11:09:46 -1000 Subject: [PATCH 056/171] run prettier add js doc --- src/libs/SessionUtils.js | 9 ++++---- src/libs/actions/App.js | 50 ++++++++++++++++++---------------------- 2 files changed, 27 insertions(+), 32 deletions(-) diff --git a/src/libs/SessionUtils.js b/src/libs/SessionUtils.js index 4d4d4b7658b9..7b1fc9f42d25 100644 --- a/src/libs/SessionUtils.js +++ b/src/libs/SessionUtils.js @@ -54,12 +54,11 @@ function resetDidUserLogInDuringSession() { loggedInDuringSession = undefined; } +/** + * @returns {boolean} + */ function didUserLogInDuringSession() { return Boolean(loggedInDuringSession); } -export { - isLoggingInAsNewUser, - didUserLogInDuringSession, - resetDidUserLogInDuringSession, -}; +export {isLoggingInAsNewUser, didUserLogInDuringSession, resetDidUserLogInDuringSession}; diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 5d23fddd9654..6053bbb81a98 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -152,33 +152,29 @@ function openApp(isReconnecting = false) { // eslint-disable-next-line rulesdir/no-multiple-api-calls const apiMethod = isReconnecting ? API.write : API.read; - apiMethod( - isReconnecting ? 'ReconnectApp' : 'OpenApp', - params, - { - optimisticData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.IS_LOADING_REPORT_DATA, - value: true, - }, - ], - successData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.IS_LOADING_REPORT_DATA, - value: false, - }, - ], - failureData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.IS_LOADING_REPORT_DATA, - value: false, - }, - ], - }, - ); + apiMethod(isReconnecting ? 'ReconnectApp' : 'OpenApp', params, { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.IS_LOADING_REPORT_DATA, + value: true, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.IS_LOADING_REPORT_DATA, + value: false, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.IS_LOADING_REPORT_DATA, + value: false, + }, + ], + }); }, }); }); From 8dc7639eb1d7c0a1180ca181f039b512296844a8 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Fri, 16 Jun 2023 16:57:33 -0700 Subject: [PATCH 057/171] getRootOrExpenseReportName refactor --- src/libs/ReportUtils.js | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index bb8bd8b7c288..d3d15f812372 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1062,10 +1062,13 @@ function getReportName(report) { * @param {Object} report * @returns {String|*} */ -function getRootReportName(report) { +function getRootOrExpenseReportName(report) { if (isThread(report)) { + if (isExpenseReport(report)) { + return lodashGet(report, 'displayName', ''); + } const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); - return getRootReportName(parentReport); + return getRootOrExpenseReportName(parentReport); } return getReportName(report); @@ -1102,31 +1105,24 @@ function getChatRoomSubtitle(report) { * @returns {String} */ function getChatRoomSubtitleLink(report) { - if (report.reportID == '3346402972554959') { - debugger; - } - if (isThread(report)) { const from = Localize.translateLocal('threads.from'); + const workspaceName = getPolicyName(report); if (isChatRoom(report)) { const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); - let roomName = ''; - if (parentReport) { - roomName = lodashGet(parentReport, 'displayName', ''); - } else { - roomName = lodashGet(report, 'displayName', ''); - } - - const workspaceName = getPolicyName(report); + const roomName = parentReport + ? lodashGet(parentReport, 'displayName', '') + : lodashGet(report, 'displayName', ''); + return `${from} ${roomName ? [roomName, workspaceName].join(' in ') : workspaceName}`; } if (isExpenseReport(report)) { const payeeEmail = getDisplayNameForParticipant(report.managerID); - const workspaceName = getPolicyName(report); return `${from} ${workspaceName ? [payeeEmail, workspaceName].join(' in ') : payeeEmail}`; } - return `${from} ${getRootReportName(report)}`; + + return `${from} ${getRootOrExpenseReportName(report)}`; } return ''; } From 511c5b6e8bbad414952e5cd9205863ae97bf97ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Sat, 17 Jun 2023 16:57:53 +0200 Subject: [PATCH 058/171] fix default value --- src/components/TextInput/BaseTextInput.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 724e39c0cdce..6fe66c76f5aa 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -320,6 +320,7 @@ function BaseTextInput(props) { value={props.value} selection={props.selection} editable={isEditable} + defaultValue={props.defaultValue} // FormSubmit Enter key handler does not have access to direct props. // `dataset.submitOnEnter` is used to indicate that pressing Enter on this input should call the submit callback. dataSet={{submitOnEnter: isMultiline && props.submitOnEnter}} From 682ad7e548607b3fc15102676a8694f955ca1acd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Sat, 17 Jun 2023 16:58:17 +0200 Subject: [PATCH 059/171] fix stories --- src/stories/TextInput.stories.js | 65 +++++++++++++++++++------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/src/stories/TextInput.stories.js b/src/stories/TextInput.stories.js index 64329ffed715..2a9e862d6876 100644 --- a/src/stories/TextInput.stories.js +++ b/src/stories/TextInput.stories.js @@ -60,32 +60,6 @@ PlaceholderInput.args = { placeholder: 'My placeholder text', }; -const AutoGrowInput = Template.bind({}); -AutoGrowInput.args = { - label: 'Autogrow input', - name: 'AutoGrow', - placeholder: 'My placeholder text', - autoGrow: true, - textInputContainerStyles: [ - { - minWidth: 150, - }, - ], -}; - -const AutoGrowHeightInput = Template.bind({}); -AutoGrowHeightInput.args = { - label: 'Autogrowheight input', - name: 'AutoGrowHeight', - placeholder: 'My placeholder text', - autoGrowHeight: true, - textInputContainerStyles: [ - { - maxHeight: 115, - }, - ], -}; - const PrefixedInput = Template.bind({}); PrefixedInput.args = { label: 'Prefixed input', @@ -126,5 +100,44 @@ HintAndErrorInput.args = { hint: 'Type "Oops!" to see the error', }; +// To use autoGrow we need to control the TextInput's value +function AutoGrowSupportInput(args) { + const [value, setValue] = useState(''); + return ( + + ); +} + +const AutoGrowInput = AutoGrowSupportInput.bind({}); +AutoGrowInput.args = { + label: 'Autogrow input', + name: 'AutoGrow', + placeholder: 'My placeholder text', + autoGrow: true, + textInputContainerStyles: [ + { + minWidth: 150, + }, + ], +}; + +const AutoGrowHeightInput = AutoGrowSupportInput.bind({}); +AutoGrowHeightInput.args = { + label: 'Autogrowheight input', + name: 'AutoGrowHeight', + placeholder: 'My placeholder text', + autoGrowHeight: true, + textInputContainerStyles: [ + { + maxHeight: 115, + }, + ], +}; + export default story; export {AutoFocus, DefaultInput, DefaultValueInput, ErrorInput, ForceActiveLabel, PlaceholderInput, AutoGrowInput, AutoGrowHeightInput, PrefixedInput, MaxLengthInput, HintAndErrorInput}; From 31da9a50bdf120026763c100c35016d5a74ff4a3 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Mon, 19 Jun 2023 13:48:53 -0700 Subject: [PATCH 060/171] centralizing logic --- src/components/AvatarWithDisplayName.js | 4 ++ src/components/MoneyRequestHeader.js | 1 - src/pages/home/HeaderView.js | 50 ++++++++++++------------- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index c5a7fcbacc1c..359734b5ba5e 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -54,6 +54,10 @@ const defaultProps = { }; function AvatarWithDisplayName(props) { + if (props.report.reportID == '5154639516299574') { + debugger; + } + const title2 = ReportUtils.getReportName(props.report); const title = props.isAnonymous ? props.report.displayName : ReportUtils.getDisplayNameForParticipant(props.report.ownerAccountID, true); const subtitle = ReportUtils.getChatRoomSubtitle(props.report); const isExpenseReport = ReportUtils.isExpenseReport(props.report); diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index af79fd2b8132..714a41035b75 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -105,7 +105,6 @@ function MoneyRequestHeader(props) { ]} threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(props.windowWidth)} report={props.report} - parentReport={moneyRequestReport} policies={props.policies} personalDetails={props.personalDetails} shouldShowBackButton={props.isSmallScreenWidth} diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 86eaa10c2311..eae64822aa34 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -178,33 +178,29 @@ function HeaderView(props) { textStyles={[styles.headerText, styles.pre]} shouldUseFullTitle={isChatRoom || isPolicyExpenseChat || isThread || isTaskReport} /> - {(isChatRoom || isPolicyExpenseChat || isThread) && ( - <> - {!_.isEmpty(subtitleLink) && ( - { - Navigation.navigate(ROUTES.getReportRoute(props.report.parentReportID)); - }} - accessibilityLabel={subtitleLink} - accessibilityRole="link" - > - - {subtitleLink} - - - )} - {!_.isEmpty(subtitle) && ( - - {subtitle} - - )} - + {!_.isEmpty(subtitleLink) && ( + { + Navigation.navigate(ROUTES.getReportRoute(props.report.parentReportID)); + }} + accessibilityLabel={subtitleLink} + accessibilityRole="link" + > + + {subtitleLink} + + + )} + {!_.isEmpty(subtitle) && ( + + {subtitle} + )} {brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR && ( From 4285fc2a82341bc22fd0e6f3c6010f77087ee7f0 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Mon, 19 Jun 2023 15:52:03 -0700 Subject: [PATCH 061/171] removing debug code --- src/components/AvatarWithDisplayName.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index 359734b5ba5e..c5a7fcbacc1c 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -54,10 +54,6 @@ const defaultProps = { }; function AvatarWithDisplayName(props) { - if (props.report.reportID == '5154639516299574') { - debugger; - } - const title2 = ReportUtils.getReportName(props.report); const title = props.isAnonymous ? props.report.displayName : ReportUtils.getDisplayNameForParticipant(props.report.ownerAccountID, true); const subtitle = ReportUtils.getChatRoomSubtitle(props.report); const isExpenseReport = ReportUtils.isExpenseReport(props.report); From 5bccf00bd969b243f8d8603d34d0216478b8f12e Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Mon, 19 Jun 2023 15:52:18 -0700 Subject: [PATCH 062/171] correcting props --- src/components/MoneyRequestHeader.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 714a41035b75..80b2b54bd9a1 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -90,6 +90,11 @@ function MoneyRequestHeader(props) { const shouldShowSettlementButton = !isSettled && !props.isSingleTransactionView && isPayer; const bankAccountRoute = ReportUtils.getBankAccountRoute(props.chatReport); const shouldShowPaypal = Boolean(lodashGet(props.personalDetails, [moneyRequestReport.managerID, 'payPalMeAddress'])); + const report = props.report; + if (props.isSingleTransactionView) { + report.ownerAccountID = props.parentReport.ownerAccountID; + report.ownerEmail = props.parentReport.ownerEmail; + } return ( Date: Mon, 19 Jun 2023 15:52:42 -0700 Subject: [PATCH 063/171] updating logic for IOU and expense report children --- src/libs/ReportUtils.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index d3d15f812372..589c653a9662 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -20,6 +20,7 @@ import isReportMessageAttachment from './isReportMessageAttachment'; import * as defaultWorkspaceAvatars from '../components/Icon/WorkspaceDefaultAvatars'; import * as CurrencyUtils from './CurrencyUtils'; import * as UserUtils from './UserUtils'; +import {isMoneyRequestAction} from './ReportActionsUtils'; let sessionEmail; let sessionAccountID; @@ -1064,7 +1065,7 @@ function getReportName(report) { */ function getRootOrExpenseReportName(report) { if (isThread(report)) { - if (isExpenseReport(report)) { + if (isMoneyRequestReport(report)) { return lodashGet(report, 'displayName', ''); } const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); @@ -1105,6 +1106,9 @@ function getChatRoomSubtitle(report) { * @returns {String} */ function getChatRoomSubtitleLink(report) { + if (report.reportID == '3519318573359925') { + debugger; + } if (isThread(report)) { const from = Localize.translateLocal('threads.from'); const workspaceName = getPolicyName(report); @@ -1117,9 +1121,9 @@ function getChatRoomSubtitleLink(report) { return `${from} ${roomName ? [roomName, workspaceName].join(' in ') : workspaceName}`; } - if (isExpenseReport(report)) { + if (isMoneyRequestReport(report)) { const payeeEmail = getDisplayNameForParticipant(report.managerID); - return `${from} ${workspaceName ? [payeeEmail, workspaceName].join(' in ') : payeeEmail}`; + return isIOUReport(report) ? `${from} ${payeeEmail}` : `${from} ${payeeEmail} in ${workspaceName}`; } return `${from} ${getRootOrExpenseReportName(report)}`; From 599d489d8aa685c080a249cdb3a7ea8a30bf278d Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Mon, 19 Jun 2023 15:52:57 -0700 Subject: [PATCH 064/171] removing debug --- src/libs/ReportUtils.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 589c653a9662..5e4ebd190440 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1106,9 +1106,6 @@ function getChatRoomSubtitle(report) { * @returns {String} */ function getChatRoomSubtitleLink(report) { - if (report.reportID == '3519318573359925') { - debugger; - } if (isThread(report)) { const from = Localize.translateLocal('threads.from'); const workspaceName = getPolicyName(report); From 687fff96fb398ced0ea359eaba023fe24902febc Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Mon, 19 Jun 2023 17:22:50 -0700 Subject: [PATCH 065/171] code improvements --- src/libs/ReportUtils.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 1e3ccdce0360..68fea3726ec6 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1058,13 +1058,18 @@ function getReportName(report) { * @param {Object} report * @returns {String|*} */ -function getRootOrExpenseReportName(report) { +function getRootOrExpenseReportNameWithWorkspace(report) { if (isThread(report)) { - if (isMoneyRequestReport(report)) { + if (isIOUReport(report)) { return lodashGet(report, 'displayName', ''); } + if (isExpenseReport(report)) { + const workspaceName = getPolicyName(report); + return `${lodashGet(report, 'displayName', '')} in ${workspaceName}`; + } + const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); - return getRootOrExpenseReportName(parentReport); + return getRootOrExpenseReportNameWithWorkspace(parentReport); } return getReportName(report); @@ -1118,7 +1123,7 @@ function getChatRoomSubtitleLink(report) { return isIOUReport(report) ? `${from} ${payeeEmail}` : `${from} ${payeeEmail} in ${workspaceName}`; } - return `${from} ${getRootOrExpenseReportName(report)}`; + return `${from} ${getRootOrExpenseReportNameWithWorkspace(report)}`; } return ''; } From a0765633253b843a703bd13097f5282ef270240e Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Mon, 19 Jun 2023 17:36:13 -0700 Subject: [PATCH 066/171] task logic improvements --- src/libs/ReportUtils.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 68fea3726ec6..2660c6751c92 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1068,6 +1068,12 @@ function getRootOrExpenseReportNameWithWorkspace(report) { return `${lodashGet(report, 'displayName', '')} in ${workspaceName}`; } + if (isTaskReport(report)) { + const workspaceName = report.policyID === CONST.POLICY.OWNER_EMAIL_FAKE ? '' : getPolicyName(report); + const displayName = getDisplayNameForParticipant(report.ownerAccountID); + return `${workspaceName ? [displayName, workspaceName].join(' in ') : displayName}`; + } + const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); return getRootOrExpenseReportNameWithWorkspace(parentReport); } From dc5a7f005392f9e6b2c5b39f1bb37bb51f7df711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 20 Jun 2023 09:27:42 +0200 Subject: [PATCH 067/171] fix update label --- src/components/TextInput/BaseTextInput.js | 22 ++++++++++++++++++---- src/stories/TextInput.stories.js | 7 ++++++- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 6fe66c76f5aa..2c4f5a983e6e 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -167,17 +167,26 @@ function BaseTextInput(props) { [props.autoGrowHeight, props.multiline], ); + const hasValueRef = useRef(inputValue.length > 0); useEffect(() => { - // Activate or deactivate the label when value is changed programmatically from outside + // Handle side effects when the value gets changed programatically from the outside // In some cases, When the value prop is empty, it is not properly updated on the TextInput due to its uncontrolled nature, thus manually clearing the TextInput. if (inputValue === '') { input.current.clear(); } - if (inputValue || isFocused) { + if (inputValue) { + activateLabel() + } + }, [activateLabel, inputValue]) + + useEffect(() => { + // Activate or deactivate the label when the focus changes + + if (hasValueRef.current || isFocused) { activateLabel(); - } else if (!isFocused) { + } else if (!hasValueRef.current && !isFocused) { deactivateLabel(); } }, [activateLabel, deactivateLabel, inputValue, isFocused]); @@ -194,7 +203,12 @@ function BaseTextInput(props) { } Str.result(props.onChangeText, value); - activateLabel(); + if (value && value.length > 0) { + hasValueRef.current = true + activateLabel(); + } else { + hasValueRef.current = false + } }; const togglePasswordVisibility = useCallback(() => { diff --git a/src/stories/TextInput.stories.js b/src/stories/TextInput.stories.js index 2a9e862d6876..4c24e5b7904e 100644 --- a/src/stories/TextInput.stories.js +++ b/src/stories/TextInput.stories.js @@ -102,7 +102,11 @@ HintAndErrorInput.args = { // To use autoGrow we need to control the TextInput's value function AutoGrowSupportInput(args) { - const [value, setValue] = useState(''); + const [value, setValue] = useState(args.value || ''); + React.useEffect(() => { + setValue(args.value || '') + }, [args.value]) + return ( Date: Tue, 20 Jun 2023 09:28:33 +0200 Subject: [PATCH 068/171] add max width --- src/stories/TextInput.stories.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/stories/TextInput.stories.js b/src/stories/TextInput.stories.js index 4c24e5b7904e..72467f0535bf 100644 --- a/src/stories/TextInput.stories.js +++ b/src/stories/TextInput.stories.js @@ -123,12 +123,13 @@ AutoGrowInput.args = { name: 'AutoGrow', placeholder: 'My placeholder text', autoGrow: true, - value: '', textInputContainerStyles: [ { minWidth: 150, + maxWidth: 500, }, ], + value: '', }; const AutoGrowHeightInput = AutoGrowSupportInput.bind({}); From 7c52921b1e1e0ec6f2ade41dbf88ea949115efa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 20 Jun 2023 09:51:57 +0200 Subject: [PATCH 069/171] prettier --- src/components/TextInput/BaseTextInput.js | 8 ++++---- src/stories/TextInput.stories.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 2c4f5a983e6e..b3fe23f99511 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -177,9 +177,9 @@ function BaseTextInput(props) { } if (inputValue) { - activateLabel() + activateLabel(); } - }, [activateLabel, inputValue]) + }, [activateLabel, inputValue]); useEffect(() => { // Activate or deactivate the label when the focus changes @@ -204,10 +204,10 @@ function BaseTextInput(props) { Str.result(props.onChangeText, value); if (value && value.length > 0) { - hasValueRef.current = true + hasValueRef.current = true; activateLabel(); } else { - hasValueRef.current = false + hasValueRef.current = false; } }; diff --git a/src/stories/TextInput.stories.js b/src/stories/TextInput.stories.js index 72467f0535bf..098828c65198 100644 --- a/src/stories/TextInput.stories.js +++ b/src/stories/TextInput.stories.js @@ -104,8 +104,8 @@ HintAndErrorInput.args = { function AutoGrowSupportInput(args) { const [value, setValue] = useState(args.value || ''); React.useEffect(() => { - setValue(args.value || '') - }, [args.value]) + setValue(args.value || ''); + }, [args.value]); return ( Date: Tue, 20 Jun 2023 19:55:14 +0530 Subject: [PATCH 070/171] Fix wrongly merged code --- src/pages/NewChatPage.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 159500a22367..ed8ddf69b18f 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -14,6 +14,7 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '../components/wit import HeaderWithBackButton from '../components/HeaderWithBackButton'; import ScreenWrapper from '../components/ScreenWrapper'; import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; +import * as Browser from '../libs/Browser'; import compose from '../libs/compose'; import personalDetailsPropType from './personalDetailsPropType'; import reportPropTypes from './reportPropTypes'; @@ -203,7 +204,7 @@ function NewChatPage(props) { onChangeText={updateOptionsWithSearchTerm} headerMessage={headerMessage} boldStyle - shouldFocusOnSelectRow={props.isGroupChat} + shouldFocusOnSelectRow={props.isGroupChat && !Browser.isMobile()} shouldShowConfirmButton={props.isGroupChat} shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} confirmButtonText={props.translate('newChatPage.createGroup')} From 80a9a9b0c44e9ddf79611f36c1c4a4b6fba23905 Mon Sep 17 00:00:00 2001 From: Nikhil Vats Date: Tue, 20 Jun 2023 20:01:38 +0530 Subject: [PATCH 071/171] fix lint --- src/pages/NewChatPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index ed8ddf69b18f..7585c979b2b4 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -174,7 +174,7 @@ function NewChatPage(props) { return; } Report.navigateToAndOpenReport(logins); - } + }; useEffect(() => { updateOptionsWithSearchTerm(searchTerm); From 4dd5f0a66e81cbe66d7a5f420f17c45b102bc7e7 Mon Sep 17 00:00:00 2001 From: oleh Date: Tue, 20 Jun 2023 17:58:01 +0300 Subject: [PATCH 072/171] restore function orders per reviewer's request --- .../MoneyRequestParticipantsSplitSelector.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js index 6f01444b1f04..fd32e2737574 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js @@ -134,6 +134,8 @@ function MoneyRequestParticipantsSplitSelector(props) { } else { newSelectedOptions = [...props.participants, option]; } + + props.onAddParticipants(newSelectedOptions); const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( props.reports, @@ -149,14 +151,6 @@ function MoneyRequestParticipantsSplitSelector(props) { personalDetails, userToInvite, }); - props.onAddParticipants(newSelectedOptions); - }; - - /** - * Once a single or more users are selected, navigates to next step - */ - const finalizeParticipants = () => { - props.onStepComplete(); }; const maxParticipantsReached = props.participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; @@ -199,7 +193,7 @@ function MoneyRequestParticipantsSplitSelector(props) { boldStyle shouldShowConfirmButton confirmButtonText={props.translate('common.next')} - onConfirmSelection={finalizeParticipants} + onConfirmSelection={props.onStepComplete()} textInputLabel={props.translate('optionsSelector.nameEmailOrPhoneNumber')} safeAreaPaddingBottomStyle={props.safeAreaPaddingBottomStyle} shouldShowOptions={isOptionsDataReady} From ffd6fadc1592092fd8c0db6ee3b2e0d968f291af Mon Sep 17 00:00:00 2001 From: oleh Date: Tue, 20 Jun 2023 18:18:21 +0300 Subject: [PATCH 073/171] fixed prettier issue --- .../MoneyRequestParticipantsSplitSelector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js index fd32e2737574..cb61d59a903c 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js @@ -134,7 +134,7 @@ function MoneyRequestParticipantsSplitSelector(props) { } else { newSelectedOptions = [...props.participants, option]; } - + props.onAddParticipants(newSelectedOptions); const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( From dadbc9cb9955dfd5357baec381ef7eab69f134b4 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Tue, 20 Jun 2023 14:52:44 -0700 Subject: [PATCH 074/171] logic corrections --- src/libs/ReportUtils.js | 63 +++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 2660c6751c92..2b8283e81c7a 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1056,29 +1056,30 @@ function getReportName(report) { /** * Recursively navigates through thread parents to get the root reports name. * @param {Object} report - * @returns {String|*} + * @returns {Object} */ -function getRootOrExpenseReportNameWithWorkspace(report) { - if (isThread(report)) { - if (isIOUReport(report)) { - return lodashGet(report, 'displayName', ''); - } - if (isExpenseReport(report)) { - const workspaceName = getPolicyName(report); - return `${lodashGet(report, 'displayName', '')} in ${workspaceName}`; - } - - if (isTaskReport(report)) { - const workspaceName = report.policyID === CONST.POLICY.OWNER_EMAIL_FAKE ? '' : getPolicyName(report); - const displayName = getDisplayNameForParticipant(report.ownerAccountID); - return `${workspaceName ? [displayName, workspaceName].join(' in ') : displayName}`; - } - +function getRootReportAndWorkspaceName(report) { + if (isThread(report) && !isMoneyRequestReport(report)) { const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); - return getRootOrExpenseReportNameWithWorkspace(parentReport); + return getRootReportAndWorkspaceName(parentReport); } - return getReportName(report); + if (isIOUReport(report)) { + return { + rootReportName: lodashGet(report, 'displayName', ''), + }; + } + if (isMoneyRequestReport(report)) { + return { + rootReportName: lodashGet(report, 'displayName', ''), + workspaceName: isIOUReport(report) ? CONST.POLICY.OWNER_EMAIL_FAKE : getPolicyName(report), + }; + } + + return { + rootReportName: getReportName(report), + workspaceName: getPolicyName(report), + }; } /** @@ -1113,23 +1114,17 @@ function getChatRoomSubtitle(report) { */ function getChatRoomSubtitleLink(report) { if (isThread(report)) { - const from = Localize.translateLocal('threads.from'); - const workspaceName = getPolicyName(report); - if (isChatRoom(report)) { - const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); - const roomName = parentReport - ? lodashGet(parentReport, 'displayName', '') - : lodashGet(report, 'displayName', ''); - - return `${from} ${roomName ? [roomName, workspaceName].join(' in ') : workspaceName}`; + if (report.reportID == '280084830934340') { + debugger; } - - if (isMoneyRequestReport(report)) { - const payeeEmail = getDisplayNameForParticipant(report.managerID); - return isIOUReport(report) ? `${from} ${payeeEmail}` : `${from} ${payeeEmail} in ${workspaceName}`; + const from = Localize.translateLocal('threads.from'); + const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); + const {rootReportName, workspaceName} = getRootReportAndWorkspaceName(parentReport); + let subtitleLink = `${from} ${rootReportName}`; + if (workspaceName && workspaceName !== rootReportName) { + subtitleLink += ` in ${workspaceName}`; } - - return `${from} ${getRootOrExpenseReportNameWithWorkspace(report)}`; + return subtitleLink; } return ''; } From a3954a7ddd37e3b1b90ca95de1a5589a5a42a56c Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Tue, 20 Jun 2023 15:00:28 -0700 Subject: [PATCH 075/171] ignore unavailable workspace --- src/libs/ReportUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 2b8283e81c7a..d676f8ea3364 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1121,7 +1121,7 @@ function getChatRoomSubtitleLink(report) { const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); const {rootReportName, workspaceName} = getRootReportAndWorkspaceName(parentReport); let subtitleLink = `${from} ${rootReportName}`; - if (workspaceName && workspaceName !== rootReportName) { + if (workspaceName && workspaceName !== rootReportName && workspaceName !== Localize.translateLocal('workspace.common.unavailable')) { subtitleLink += ` in ${workspaceName}`; } return subtitleLink; From e7cc616e5924154329b3588323d499b0315be868 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Tue, 20 Jun 2023 15:08:29 -0700 Subject: [PATCH 076/171] translation for in --- src/languages/en.js | 3 ++- src/languages/es.js | 3 ++- src/libs/ReportUtils.js | 10 +++------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index fac5a6c82db1..1293041f9d1e 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -146,6 +146,8 @@ export default { km: 'kilometer', copied: 'Copied!', someone: 'Someone', + from: 'From', + conjunctionIn: 'in', }, anonymousReportFooter: { logoTagline: 'Join in on the discussion.', @@ -1412,7 +1414,6 @@ export default { lastReply: 'Last reply', replies: 'Replies', reply: 'Reply', - from: 'From', }, qrCodes: { copyUrlToClipboard: 'Copy URL to clipboard', diff --git a/src/languages/es.js b/src/languages/es.js index 28b98f18e25b..a0a3995ea9e6 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -145,6 +145,8 @@ export default { km: 'kilómetro', copied: '¡Copiado!', someone: 'Alguien', + from: 'De', + conjunctionIn: 'en', }, anonymousReportFooter: { logoTagline: 'Únete a la discussion.', @@ -1880,7 +1882,6 @@ export default { lastReply: 'Última respuesta', replies: 'Respuestas', reply: 'Respuesta', - from: 'De', }, qrCodes: { copyUrlToClipboard: 'Copiar URL al portapapeles', diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index d676f8ea3364..eba7aa9843dc 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1108,21 +1108,17 @@ function getChatRoomSubtitle(report) { } /** - * Get either the policyName or domainName the chat is tied to + * Get the subtitleLink for the report * @param {Object} report * @returns {String} */ function getChatRoomSubtitleLink(report) { if (isThread(report)) { - if (report.reportID == '280084830934340') { - debugger; - } - const from = Localize.translateLocal('threads.from'); const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); const {rootReportName, workspaceName} = getRootReportAndWorkspaceName(parentReport); - let subtitleLink = `${from} ${rootReportName}`; + let subtitleLink = `${Localize.translateLocal('common.from')} ${rootReportName}`; if (workspaceName && workspaceName !== rootReportName && workspaceName !== Localize.translateLocal('workspace.common.unavailable')) { - subtitleLink += ` in ${workspaceName}`; + subtitleLink += ` ${Localize.translateLocal('common.conjunctionIn')} ${workspaceName}`; } return subtitleLink; } From 1ceff1ead32c6da093b89fd9851934ec41a0b59e Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Tue, 20 Jun 2023 15:10:45 -0700 Subject: [PATCH 077/171] comment update --- src/libs/ReportUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index eba7aa9843dc..e3ec3767a4c7 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1054,7 +1054,7 @@ function getReportName(report) { } /** - * Recursively navigates through thread parents to get the root reports name. + * Recursively navigates through thread parents to get the root report and workspace name. * @param {Object} report * @returns {Object} */ From f10878fd12b5b47861a91d8684e9d76eebe0fee7 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Tue, 20 Jun 2023 15:13:39 -0700 Subject: [PATCH 078/171] lint fixes --- src/components/AvatarWithDisplayName.js | 1 - src/libs/ReportUtils.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index c5a7fcbacc1c..b5de99514b42 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -17,7 +17,6 @@ import compose from '../libs/compose'; import * as OptionsListUtils from '../libs/OptionsListUtils'; import Text from './Text'; import * as StyleUtils from '../styles/StyleUtils'; -import {getChatRoomSubtitleLink} from '../libs/ReportUtils'; import Navigation from '../libs/Navigation/Navigation'; import ROUTES from '../ROUTES'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index e3ec3767a4c7..14adf3be5461 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -20,7 +20,6 @@ import isReportMessageAttachment from './isReportMessageAttachment'; import * as defaultWorkspaceAvatars from '../components/Icon/WorkspaceDefaultAvatars'; import * as CurrencyUtils from './CurrencyUtils'; import * as UserUtils from './UserUtils'; -import {isMoneyRequestAction} from './ReportActionsUtils'; let sessionEmail; let sessionAccountID; From 82be71e43a1bb8a0c83732b74b7f2f3505bad0aa Mon Sep 17 00:00:00 2001 From: oleh Date: Wed, 21 Jun 2023 01:29:09 +0300 Subject: [PATCH 079/171] Updated per reviewer's requests --- .../MoneyRequestParticipantsSplitSelector.js | 75 ++++++++++--------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js index cb61d59a903c..14fca3dfdfcd 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js @@ -1,4 +1,4 @@ -import React, {useEffect, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; @@ -68,13 +68,15 @@ function MoneyRequestParticipantsSplitSelector(props) { userToInvite: null, }); + const maxParticipantsReached = props.participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; + /** * Returns the sections needed for the OptionsSelector * * @param {Boolean} maxParticipantsReached * @returns {Array} */ - const getSections = (maxParticipantsReached) => { + const sections = useMemo(() => { const newSections = []; let indexOffset = 0; @@ -118,42 +120,47 @@ function MoneyRequestParticipantsSplitSelector(props) { } return newSections; - }; + }, [maxParticipantsReached, newChatOptions, props.participants, props.translate]); /** * Removes a selected option from list if already selected. If not already selected add this option to the list. * @param {Object} option */ - const toggleOption = (option) => { - const isOptionInList = _.some(props.participants, (selectedOption) => selectedOption.accountID === option.accountID); - - let newSelectedOptions; - - if (isOptionInList) { - newSelectedOptions = _.reject(props.participants, (selectedOption) => selectedOption.accountID === option.accountID); - } else { - newSelectedOptions = [...props.participants, option]; - } - - props.onAddParticipants(newSelectedOptions); - - const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( - props.reports, - props.personalDetails, - props.betas, - isOptionInList ? searchTerm : '', - newSelectedOptions, - CONST.EXPENSIFY_EMAILS, - ); - - setNewChatOptions({ - recentReports, - personalDetails, - userToInvite, - }); - }; + const toggleOption = useCallback( + (option) => { + const isOptionInList = _.some(props.participants, (selectedOption) => selectedOption.accountID === option.accountID); + + let newSelectedOptions; + + if (isOptionInList) { + newSelectedOptions = _.reject(props.participants, (selectedOption) => selectedOption.accountID === option.accountID); + } else { + newSelectedOptions = [...props.participants, option]; + } + + props.onAddParticipants(newSelectedOptions); + + const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( + props.reports, + props.personalDetails, + props.betas, + isOptionInList ? searchTerm : '', + newSelectedOptions, + CONST.EXPENSIFY_EMAILS, + ); + + setNewChatOptions({ + recentReports, + personalDetails, + userToInvite, + }); + if (!isOptionInList) { + setSearchTerm(''); + } + }, + [searchTerm, props.participants, props.onAddParticipants, props.reports, props.personalDetails, props.betas, setNewChatOptions, setSearchTerm], + ); - const maxParticipantsReached = props.participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; const headerMessage = OptionsListUtils.getHeaderMessage( newChatOptions.personalDetails.length + newChatOptions.recentReports.length !== 0, Boolean(newChatOptions.userToInvite), @@ -162,8 +169,6 @@ function MoneyRequestParticipantsSplitSelector(props) { ); const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(props.personalDetails); - const sections = getSections(maxParticipantsReached); - useEffect(() => { const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( props.reports, @@ -193,7 +198,7 @@ function MoneyRequestParticipantsSplitSelector(props) { boldStyle shouldShowConfirmButton confirmButtonText={props.translate('common.next')} - onConfirmSelection={props.onStepComplete()} + onConfirmSelection={props.onStepComplete} textInputLabel={props.translate('optionsSelector.nameEmailOrPhoneNumber')} safeAreaPaddingBottomStyle={props.safeAreaPaddingBottomStyle} shouldShowOptions={isOptionsDataReady} From a96154590e4cefa722da4e174f85ffbf6ff4bc99 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Tue, 20 Jun 2023 16:04:05 -0700 Subject: [PATCH 080/171] prettier fixes --- src/libs/ReportUtils.js | 4 ++++ src/pages/ReportParticipantsPage.js | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 14adf3be5461..9526247cd1c6 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -470,6 +470,10 @@ function isArchivedRoom(report) { * @returns {String} */ function getPolicyName(report) { + if (report === undefined) { + return ''; + } + // Public rooms send back the policy name with the reportSummary, // since they can also be accessed by people who aren't in the workspace if (report.policyName) { diff --git a/src/pages/ReportParticipantsPage.js b/src/pages/ReportParticipantsPage.js index b0c2f2e38954..6d9bdbffba54 100755 --- a/src/pages/ReportParticipantsPage.js +++ b/src/pages/ReportParticipantsPage.js @@ -93,7 +93,9 @@ function ReportParticipantsPage(props) { Date: Wed, 21 Jun 2023 02:13:14 +0300 Subject: [PATCH 081/171] fixed lint issue --- .../MoneyRequestParticipantsSplitSelector.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js index 14fca3dfdfcd..b1a68cb13c7f 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js @@ -120,6 +120,7 @@ function MoneyRequestParticipantsSplitSelector(props) { } return newSections; + // eslint-disable-next-line react-hooks/exhaustive-deps -- props does not need to be a dependency as it will always exist }, [maxParticipantsReached, newChatOptions, props.participants, props.translate]); /** @@ -158,6 +159,7 @@ function MoneyRequestParticipantsSplitSelector(props) { setSearchTerm(''); } }, + // eslint-disable-next-line react-hooks/exhaustive-deps -- props does not need to be a dependency as it will always exist [searchTerm, props.participants, props.onAddParticipants, props.reports, props.personalDetails, props.betas, setNewChatOptions, setSearchTerm], ); From daf4e6f7b976383692eaf4d758b08b615d23aed9 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 20 Jun 2023 13:54:37 -1000 Subject: [PATCH 082/171] Log an alert if we exceed max execution time --- src/CONST.js | 1 + src/libs/ReportActionsUtils.js | 4 ++++ src/libs/actions/Timing.js | 8 +++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/CONST.js b/src/CONST.js index 8b954a010da6..e158f8efe38c 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -652,6 +652,7 @@ const CONST = { }, }, TIMING: { + CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION: 'calc_most_recent_last_modified_action', SEARCH_RENDER: 'search_render', HOMEPAGE_INITIAL_RENDER: 'homepage_initial_render', REPORT_INITIAL_RENDER: 'report_initial_render', diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 3b9bf2ce79e0..16855ef799e9 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -10,6 +10,7 @@ import ONYXKEYS from '../ONYXKEYS'; import Log from './Log'; import * as CurrencyUtils from './CurrencyUtils'; import isReportMessageAttachment from './isReportMessageAttachment'; +import Timing from './actions/Timing'; const allReports = {}; Onyx.connect({ @@ -426,6 +427,8 @@ function getLinkedTransactionID(reportID, reportActionID) { * @returns {string} */ function getMostRecentReportActionLastModified() { + Timing.start(CONST.TIMING.CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION); + // Start with the oldest date possible let mostRecentReportActionLastModified = new Date(0).toISOString(); @@ -462,6 +465,7 @@ function getMostRecentReportActionLastModified() { mostRecentReportActionLastModified = reportLastVisibleActionLastModified; }); + Timing.end(CONST.TIMING.CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION, 500); return mostRecentReportActionLastModified; } diff --git a/src/libs/actions/Timing.js b/src/libs/actions/Timing.js index 0f6a67d0879e..928cac51d960 100644 --- a/src/libs/actions/Timing.js +++ b/src/libs/actions/Timing.js @@ -2,6 +2,7 @@ import getPlatform from '../getPlatform'; import * as Environment from '../Environment/Environment'; import Firebase from '../Firebase'; import * as API from '../API'; +import Log from '../Log'; let timestampData = {}; @@ -26,8 +27,9 @@ function start(eventName, shouldUseFirebase = false) { * * @param {String} eventName - event name used as timestamp key * @param {String} [secondaryName] - optional secondary event name, passed to grafana + * @param {number} [maxExecutionTime] - optional amount of time to wait before logging an alert */ -function end(eventName, secondaryName = '') { +function end(eventName, secondaryName = '', maxExecutionTime = 0) { if (!timestampData[eventName]) { return; } @@ -51,6 +53,10 @@ function end(eventName, secondaryName = '') { return; } + if (maxExecutionTime && eventTime > maxExecutionTime) { + Log.alert(`${eventName} exceeded max execution time of ${maxExecutionTime}.`, {eventTime, eventName}); + } + API.write('SendPerformanceTiming', { name: grafanaEventName, value: eventTime, From 545efe19a64bb229688882bea62937e50386f353 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Tue, 20 Jun 2023 17:17:00 -0700 Subject: [PATCH 083/171] code correction to not show anything if data is missing --- src/libs/ReportUtils.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index f64b6feece21..ae415ba61f12 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1108,6 +1108,10 @@ function getChatRoomSubtitleLink(report) { if (isThread(report)) { const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); const {rootReportName, workspaceName} = getRootReportAndWorkspaceName(parentReport); + if (_.isEmpty(rootReportName)) { + return ''; + } + let subtitleLink = `${Localize.translateLocal('common.from')} ${rootReportName}`; if (workspaceName && workspaceName !== rootReportName && workspaceName !== Localize.translateLocal('workspace.common.unavailable')) { subtitleLink += ` ${Localize.translateLocal('common.conjunctionIn')} ${workspaceName}`; From f2f010c8431be20358dda5f342616a823e890de9 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Tue, 20 Jun 2023 17:19:35 -0700 Subject: [PATCH 084/171] removing parentReport --- src/components/HeaderWithBackButton.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/HeaderWithBackButton.js b/src/components/HeaderWithBackButton.js index c612b82851f9..bb707b8f8873 100755 --- a/src/components/HeaderWithBackButton.js +++ b/src/components/HeaderWithBackButton.js @@ -96,9 +96,6 @@ const propTypes = { /** Whether we should show an avatar */ shouldShowAvatarWithDisplay: PropTypes.bool, - /** Parent report, if provided it will override props.report for AvatarWithDisplay */ - parentReport: iouReportPropTypes, - /** Report, if we're showing the details for one and using AvatarWithDisplay */ report: iouReportPropTypes, @@ -133,7 +130,6 @@ const defaultProps = { shouldShowBackButton: true, shouldShowAvatarWithDisplay: false, report: null, - parentReport: null, policies: {}, personalDetails: {}, guidesCallTaskID: '', @@ -192,7 +188,7 @@ class HeaderWithBackButton extends Component { )} {this.props.shouldShowAvatarWithDisplay && ( From 325935c9126e472a3d41649bdbbc452b124af5ea Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Tue, 20 Jun 2023 17:24:53 -0700 Subject: [PATCH 085/171] cleanup --- src/libs/ReportUtils.js | 3 ++- src/pages/home/HeaderView.js | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index ae415ba61f12..8274ef1eb12c 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1047,6 +1047,7 @@ function getReportName(report) { /** * Recursively navigates through thread parents to get the root report and workspace name. + * The recursion stops when we find a non thread or money request report, whichever comes first. * @param {Object} report * @returns {Object} */ @@ -1100,7 +1101,7 @@ function getChatRoomSubtitle(report) { } /** - * Get the subtitleLink for the report + * Get the subtitle link for the report * @param {Object} report * @returns {String} */ diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 30c46a7389af..685bd21c2ad6 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -83,11 +83,11 @@ function HeaderView(props) { const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, props.personalDetails); const isMultipleParticipant = participants.length > 1; const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(participantPersonalDetails, isMultipleParticipant); - const isThread = ReportUtils.isChatThread(props.report); + const isChatThread = ReportUtils.isChatThread(props.report); const isChatRoom = ReportUtils.isChatRoom(props.report); const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(props.report); const isTaskReport = ReportUtils.isTaskReport(props.report); - const reportHeaderData = !isTaskReport && !isThread && props.report.parentReportID ? props.parentReport : props.report; + const reportHeaderData = !isTaskReport && !isChatThread && props.report.parentReportID ? props.parentReport : props.report; const title = ReportUtils.getReportName(reportHeaderData); const subtitle = ReportUtils.getChatRoomSubtitle(reportHeaderData); const subtitleLink = ReportUtils.getChatRoomSubtitleLink(reportHeaderData); @@ -175,7 +175,7 @@ function HeaderView(props) { ) : ( )} @@ -185,7 +185,7 @@ function HeaderView(props) { tooltipEnabled numberOfLines={1} textStyles={[styles.headerText, styles.pre]} - shouldUseFullTitle={isChatRoom || isPolicyExpenseChat || isThread || isTaskReport} + shouldUseFullTitle={isChatRoom || isPolicyExpenseChat || isChatThread || isTaskReport} /> {!_.isEmpty(subtitleLink) && ( Date: Tue, 20 Jun 2023 17:29:40 -0700 Subject: [PATCH 086/171] using lodash --- src/components/MoneyRequestHeader.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 87f7c6e1cf87..ad199210fbf2 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -92,8 +92,8 @@ function MoneyRequestHeader(props) { const shouldShowPaypal = Boolean(lodashGet(props.personalDetails, [moneyRequestReport.managerID, 'payPalMeAddress'])); const report = props.report; if (props.isSingleTransactionView) { - report.ownerAccountID = props.parentReport.ownerAccountID; - report.ownerEmail = props.parentReport.ownerEmail; + report.ownerAccountID = lodashGet(props, ['parentReport', 'ownerAccountID'], null); + report.ownerEmail = lodashGet(props, ['parentReport', 'ownerEmail'], ''); } return ( From 25ca03bbb74afff3f8a8b0643f75c33688215d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Wed, 21 Jun 2023 12:06:09 +0200 Subject: [PATCH 087/171] make ref usage more explanatory --- src/components/TextInput/BaseTextInput.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index b3fe23f99511..63fa036d57f9 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -167,7 +167,6 @@ function BaseTextInput(props) { [props.autoGrowHeight, props.multiline], ); - const hasValueRef = useRef(inputValue.length > 0); useEffect(() => { // Handle side effects when the value gets changed programatically from the outside @@ -181,9 +180,15 @@ function BaseTextInput(props) { } }, [activateLabel, inputValue]); - useEffect(() => { - // Activate or deactivate the label when the focus changes + // We capture whether the input has a value or not in a ref. + // It gets updated when the text gets changed. + const hasValueRef = useRef(inputValue.length > 0); + // Activate or deactivate the label when the focus changes: + useEffect(() => { + // We can't use inputValue here directly, as it might contain + // the defaultValue, which doesn't get updated when the text changes. + // We can't use props.value either, as it might be undefined. if (hasValueRef.current || isFocused) { activateLabel(); } else if (!hasValueRef.current && !isFocused) { From 56b54915b82b37fef47834763d990e8915f3f1cf Mon Sep 17 00:00:00 2001 From: Nikhil Vats Date: Wed, 21 Jun 2023 23:23:13 +0530 Subject: [PATCH 088/171] Use useMemo hook --- src/pages/NewChatPage.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 7585c979b2b4..e6f0472241dc 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -1,5 +1,5 @@ import _ from 'underscore'; -import React, {useState, useEffect} from 'react'; +import React, {useState, useEffect, useMemo} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; @@ -61,7 +61,7 @@ function NewChatPage(props) { ); const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(props.personalDetails); - function getSections() { + const sections = useMemo(() => { const sectionsList = []; let indexOffset = 0; @@ -111,7 +111,8 @@ function NewChatPage(props) { } return sectionsList; - } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [filteredPersonalDetails, filteredRecentReports, filteredUserToInvite, maxParticipantsReached, props.isGroupChat, selectedOptions]); const updateOptionsWithSearchTerm = (newSearchTerm = '') => { const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( @@ -184,8 +185,6 @@ function NewChatPage(props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.reports, props.personalDetails]); - const sections = getSections(); - return ( Date: Wed, 21 Jun 2023 16:01:44 -0700 Subject: [PATCH 089/171] using isThread --- src/libs/ReportUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 8274ef1eb12c..67a495c7248f 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -540,7 +540,7 @@ function isThread(report) { * @returns {Boolean} */ function isChatThread(report) { - return Boolean(report && report.parentReportID && report.parentReportActionID && report.type === CONST.REPORT.TYPE.CHAT); + return isThread && report.type === CONST.REPORT.TYPE.CHAT; } /** From edb7a486f15fb4ad14269829e29ed2d29bc78827 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Wed, 21 Jun 2023 16:19:06 -0700 Subject: [PATCH 090/171] rename to parentNavigationSubtitle --- src/components/AvatarWithDisplayName.js | 6 +++--- src/libs/ReportUtils.js | 6 +++--- src/pages/home/HeaderView.js | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index b5de99514b42..152eaadcf709 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -55,12 +55,12 @@ const defaultProps = { function AvatarWithDisplayName(props) { const title = props.isAnonymous ? props.report.displayName : ReportUtils.getDisplayNameForParticipant(props.report.ownerAccountID, true); const subtitle = ReportUtils.getChatRoomSubtitle(props.report); + const parentNavigationSubtitle = ReportUtils.getParentNavigationSubtitle(props.report); const isExpenseReport = ReportUtils.isExpenseReport(props.report); const icons = ReportUtils.getIcons(props.report, props.personalDetails, props.policies); const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs([props.report.ownerAccountID], props.personalDetails); const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(_.values(ownerPersonalDetails), false); const avatarContainerStyle = StyleUtils.getEmptyAvatarStyle(props.size) || styles.emptyAvatar; - const subtitleLink = ReportUtils.getChatRoomSubtitleLink(props.report); return ( {Boolean(props.report && title) && ( @@ -92,7 +92,7 @@ function AvatarWithDisplayName(props) { textStyles={[props.isAnonymous ? styles.headerAnonymousFooter : styles.headerText, styles.pre]} shouldUseFullTitle={isExpenseReport || props.isAnonymous} /> - {!_.isEmpty(subtitleLink) && ( + {!_.isEmpty(parentNavigationSubtitle) && ( { Navigation.navigate(ROUTES.getReportRoute(props.report.parentReportID)); @@ -104,7 +104,7 @@ function AvatarWithDisplayName(props) { style={[styles.optionAlternateText, styles.textLabelSupporting, styles.link]} numberOfLines={1} > - {subtitleLink} + {parentNavigationSubtitle} )} diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 67a495c7248f..4a816731d4aa 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1101,11 +1101,11 @@ function getChatRoomSubtitle(report) { } /** - * Get the subtitle link for the report + * Gets the parent navigation subtitle for the report * @param {Object} report * @returns {String} */ -function getChatRoomSubtitleLink(report) { +function getParentNavigationSubtitle(report) { if (isThread(report)) { const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); const {rootReportName, workspaceName} = getRootReportAndWorkspaceName(parentReport); @@ -2283,7 +2283,7 @@ export { isUserCreatedPolicyRoom, isChatRoom, getChatRoomSubtitle, - getChatRoomSubtitleLink, + getParentNavigationSubtitle, getPolicyName, getPolicyType, isArchivedRoom, diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 685bd21c2ad6..030f4eef5e18 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -90,7 +90,7 @@ function HeaderView(props) { const reportHeaderData = !isTaskReport && !isChatThread && props.report.parentReportID ? props.parentReport : props.report; const title = ReportUtils.getReportName(reportHeaderData); const subtitle = ReportUtils.getChatRoomSubtitle(reportHeaderData); - const subtitleLink = ReportUtils.getChatRoomSubtitleLink(reportHeaderData); + const parentNavigationSubtitle = ReportUtils.getParentNavigationSubtitle(reportHeaderData); const isConcierge = participants.length === 1 && _.contains(participants, CONST.ACCOUNT_ID.CONCIERGE); const isAutomatedExpensifyAccount = participants.length === 1 && ReportUtils.hasAutomatedExpensifyAccountIDs(participants); const guideCalendarLink = lodashGet(props.account, 'guideCalendarLink'); @@ -187,19 +187,19 @@ function HeaderView(props) { textStyles={[styles.headerText, styles.pre]} shouldUseFullTitle={isChatRoom || isPolicyExpenseChat || isChatThread || isTaskReport} /> - {!_.isEmpty(subtitleLink) && ( + {!_.isEmpty(parentNavigationSubtitle) && ( { Navigation.navigate(ROUTES.getReportRoute(props.report.parentReportID)); }} - accessibilityLabel={subtitleLink} + accessibilityLabel={parentNavigationSubtitle} accessibilityRole="link" > - {subtitleLink} + {parentNavigationSubtitle} )} From f09d106fa5fc85d022e226d03c3bb993f65515ea Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Wed, 21 Jun 2023 16:39:24 -0700 Subject: [PATCH 091/171] logic correction --- src/libs/ReportUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index e93804e8eb18..97559e520940 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -540,7 +540,7 @@ function isThread(report) { * @returns {Boolean} */ function isChatThread(report) { - return isThread && report.type === CONST.REPORT.TYPE.CHAT; + return isThread(report) && report.type === CONST.REPORT.TYPE.CHAT; } /** From b5a67763bc70ae89efcb20c6ee44b84be0b88c71 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Wed, 21 Jun 2023 16:56:43 -0700 Subject: [PATCH 092/171] setting parentActionID for task optimistically --- src/libs/actions/Task.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 5e27d7db764d..a03b0e32b856 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -57,6 +57,7 @@ function createTaskAndNavigate(currentUserEmail, currentUserAccountID, parentRep // Create the CreatedReportAction on the task const optimisticTaskCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(optimisticTaskReport.reportID); const optimisticAddCommentReport = ReportUtils.buildOptimisticTaskCommentReportAction(taskReportID, title, assignee, assigneeAccountID, `Created a task: ${title}`, parentReportID); + optimisticTaskReport.parentReportActionID = optimisticAddCommentReport.reportAction.reportActionID; const currentTime = DateUtils.getDBTime(); From c023725c3b7e3572a6ddaae974ae3c56f159b901 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Thu, 22 Jun 2023 14:08:07 -0700 Subject: [PATCH 093/171] parentNavigationSummary translation --- src/languages/en.js | 1 + src/languages/es.js | 1 + src/libs/ReportUtils.js | 15 ++++++--------- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index bdaa0786b627..274bded36110 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -1415,6 +1415,7 @@ export default { lastReply: 'Last reply', replies: 'Replies', reply: 'Reply', + parentNavigationSummary: ({rootReportName, workspaceName}) => `From ${rootReportName} ${workspaceName ? `in ${workspaceName}` : ''}`, }, qrCodes: { copyUrlToClipboard: 'Copy URL to clipboard', diff --git a/src/languages/es.js b/src/languages/es.js index 2bd602694f68..4de7e60a9db4 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -1883,6 +1883,7 @@ export default { lastReply: 'Última respuesta', replies: 'Respuestas', reply: 'Respuesta', + parentNavigationSummary: ({rootReportName, workspaceName}) => `De ${rootReportName} ${workspaceName ? `en ${workspaceName}` : ''}`, }, qrCodes: { copyUrlToClipboard: 'Copiar URL al portapapeles', diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 97559e520940..f847d4332f1d 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -467,9 +467,10 @@ function isArchivedRoom(report) { * @param {String} report.policyID * @param {String} report.oldPolicyName * @param {String} report.policyName + * @param {Boolean} [returnEmptyIfNotFound] * @returns {String} */ -function getPolicyName(report) { +function getPolicyName(report, returnEmptyIfNotFound = false) { if ((!allPolicies || _.size(allPolicies) === 0) && !report.policyName) { return Localize.translateLocal('workspace.common.unavailable'); } @@ -478,7 +479,7 @@ function getPolicyName(report) { // // Public rooms send back the policy name with the reportSummary, // // since they can also be accessed by people who aren't in the workspace - return lodashGet(policy, 'name') || report.policyName || report.oldPolicyName || Localize.translateLocal('workspace.common.unavailable'); + return lodashGet(policy, 'name') || report.policyName || report.oldPolicyName || (returnEmptyIfNotFound ? '' : Localize.translateLocal('workspace.common.unavailable')); } /** @@ -1065,13 +1066,13 @@ function getRootReportAndWorkspaceName(report) { if (isMoneyRequestReport(report)) { return { rootReportName: lodashGet(report, 'displayName', ''), - workspaceName: isIOUReport(report) ? CONST.POLICY.OWNER_EMAIL_FAKE : getPolicyName(report), + workspaceName: isIOUReport(report) ? CONST.POLICY.OWNER_EMAIL_FAKE : getPolicyName(report, true), }; } return { rootReportName: getReportName(report), - workspaceName: getPolicyName(report), + workspaceName: getPolicyName(report, true), }; } @@ -1113,11 +1114,7 @@ function getParentNavigationSubtitle(report) { return ''; } - let subtitleLink = `${Localize.translateLocal('common.from')} ${rootReportName}`; - if (workspaceName && workspaceName !== rootReportName && workspaceName !== Localize.translateLocal('workspace.common.unavailable')) { - subtitleLink += ` ${Localize.translateLocal('common.conjunctionIn')} ${workspaceName}`; - } - return subtitleLink; + return Localize.translateLocal('threads.parentNavigationSummary', {rootReportName, workspaceName}); } return ''; } From 2a5d48d4d176432bb94f89c40b6d5f9a63893efc Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Thu, 22 Jun 2023 14:08:48 -0700 Subject: [PATCH 094/171] removing unused translation --- src/languages/en.js | 2 -- src/languages/es.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index 274bded36110..8ef4c08ace1f 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -146,8 +146,6 @@ export default { km: 'kilometer', copied: 'Copied!', someone: 'Someone', - from: 'From', - conjunctionIn: 'in', }, anonymousReportFooter: { logoTagline: 'Join in on the discussion.', diff --git a/src/languages/es.js b/src/languages/es.js index 4de7e60a9db4..4af03ce4de03 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -145,8 +145,6 @@ export default { km: 'kilómetro', copied: '¡Copiado!', someone: 'Alguien', - from: 'De', - conjunctionIn: 'en', }, anonymousReportFooter: { logoTagline: 'Únete a la discussion.', From d423244e13d1afef51263b2dfaf10b100b27c1b6 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Thu, 22 Jun 2023 14:17:33 -0700 Subject: [PATCH 095/171] moving space into the condition --- src/languages/en.js | 2 +- src/languages/es.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index 8ef4c08ace1f..a58223458350 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -1413,7 +1413,7 @@ export default { lastReply: 'Last reply', replies: 'Replies', reply: 'Reply', - parentNavigationSummary: ({rootReportName, workspaceName}) => `From ${rootReportName} ${workspaceName ? `in ${workspaceName}` : ''}`, + parentNavigationSummary: ({rootReportName, workspaceName}) => `From ${rootReportName}${workspaceName ? ` in ${workspaceName}` : ''}`, }, qrCodes: { copyUrlToClipboard: 'Copy URL to clipboard', diff --git a/src/languages/es.js b/src/languages/es.js index 4af03ce4de03..4729e7a93896 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -1881,7 +1881,7 @@ export default { lastReply: 'Última respuesta', replies: 'Respuestas', reply: 'Respuesta', - parentNavigationSummary: ({rootReportName, workspaceName}) => `De ${rootReportName} ${workspaceName ? `en ${workspaceName}` : ''}`, + parentNavigationSummary: ({rootReportName, workspaceName}) => `De ${rootReportName}${workspaceName ? ` en ${workspaceName}` : ''}`, }, qrCodes: { copyUrlToClipboard: 'Copiar URL al portapapeles', From 05a9634b3542955de53512ac2d46961194c50eef Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 23 Jun 2023 12:07:08 +0800 Subject: [PATCH 096/171] don't strikethorugh the text --- src/pages/ReimbursementAccount/ContinueBankAccountSetup.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js b/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js index 4eca196d328a..592ae2b9a24a 100644 --- a/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js +++ b/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js @@ -60,7 +60,6 @@ function ContinueBankAccountSetup(props) { icon={Illustrations.BankArrow} > Date: Fri, 23 Jun 2023 12:07:37 +0800 Subject: [PATCH 097/171] reduce the opacity when disabled --- src/components/MenuItem.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index eae6069783f3..c96bfaea3f31 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -79,7 +79,7 @@ function MenuItem(props) { props.icon && !_.isArray(props.icon) ? styles.ml3 : undefined, props.shouldShowBasicTitle ? undefined : styles.textStrong, props.shouldShowHeaderTitle ? styles.textHeadlineH1 : undefined, - props.interactive && props.disabled ? {...styles.disabledText, ...styles.userSelectNone} : undefined, + props.interactive && props.disabled ? {...styles.userSelectNone} : undefined, styles.pre, styles.ltr, isDeleted ? styles.offlineFeedback.deleted : undefined, @@ -119,6 +119,7 @@ function MenuItem(props) { StyleUtils.getButtonBackgroundColorStyle(getButtonState(props.focused || hovered, pressed, props.success, props.disabled, props.interactive), true), (hovered || pressed) && props.hoverAndPressStyle, ...(_.isArray(props.wrapperStyle) ? props.wrapperStyle : [props.wrapperStyle]), + props.disabled && styles.buttonOpacityDisabled, ]} disabled={props.disabled} ref={props.forwardedRef} From 950f7d1bf352e8d495d38a23b4e40989757657b5 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Fri, 23 Jun 2023 13:26:58 -0700 Subject: [PATCH 098/171] improved safety check --- src/libs/ReportUtils.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index e056a4ec6ce4..73be4623d929 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -472,6 +472,11 @@ function isArchivedRoom(report) { * @returns {String} */ function getPolicyName(report, returnEmptyIfNotFound = false) { + const noPolicyFound = returnEmptyIfNotFound ? '' : Localize.translateLocal('workspace.common.unavailable'); + if (report === undefined) { + return noPolicyFound; + } + if ((!allPolicies || _.size(allPolicies) === 0) && !report.policyName) { return Localize.translateLocal('workspace.common.unavailable'); } @@ -480,7 +485,7 @@ function getPolicyName(report, returnEmptyIfNotFound = false) { // // Public rooms send back the policy name with the reportSummary, // // since they can also be accessed by people who aren't in the workspace - return lodashGet(policy, 'name') || report.policyName || report.oldPolicyName || (returnEmptyIfNotFound ? '' : Localize.translateLocal('workspace.common.unavailable')); + return lodashGet(policy, 'name') || report.policyName || report.oldPolicyName || noPolicyFound; } /** From b70b37ed60f7dbc3479fdbe0121d5c6f5a68c32a Mon Sep 17 00:00:00 2001 From: oleh Date: Sun, 25 Jun 2023 22:26:34 +0300 Subject: [PATCH 099/171] Merge branch 'main' into 16280_refactor_MoneyRequestParticipantsSplitSelector --- .../MoneyRequestParticipantsSplitSelector.js | 90 ++++++++----------- 1 file changed, 36 insertions(+), 54 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js index 7469c322f7d4..7aef507ac12d 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSplitSelector.js @@ -55,7 +55,7 @@ const defaultProps = { safeAreaPaddingBottomStyle: {}, }; -function MoneyRequestParticipantsSplitSelector(props) { +function MoneyRequestParticipantsSplitSelector({betas, participants, personalDetails, reports, translate, onAddParticipants, onStepComplete, safeAreaPaddingBottomStyle}) { const [searchTerm, setSearchTerm] = useState(''); const [newChatOptions, setNewChatOptions] = useState({ recentReports: [], @@ -63,7 +63,7 @@ function MoneyRequestParticipantsSplitSelector(props) { userToInvite: null, }); - const maxParticipantsReached = props.participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; + const maxParticipantsReached = participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; /** * Returns the sections needed for the OptionsSelector @@ -77,46 +77,43 @@ function MoneyRequestParticipantsSplitSelector(props) { newSections.push({ title: undefined, - data: OptionsListUtils.getParticipantsOptions(props.participants, props.personalDetails), + data: OptionsListUtils.getParticipantsOptions(participants, personalDetails), shouldShow: true, indexOffset, }); - indexOffset += props.participants.length; + indexOffset += participants.length; if (maxParticipantsReached) { return newSections; } - const {recentReports, personalDetails, userToInvite} = newChatOptions; - newSections.push({ - title: props.translate('common.recents'), - data: recentReports, - shouldShow: !_.isEmpty(recentReports), + title: translate('common.recents'), + data: newChatOptions.recentReports, + shouldShow: !_.isEmpty(newChatOptions.recentReports), indexOffset, }); - indexOffset += recentReports.length; + indexOffset += newChatOptions.recentReports.length; newSections.push({ - title: props.translate('common.contacts'), - data: personalDetails, - shouldShow: !_.isEmpty(personalDetails), + title: translate('common.contacts'), + data: newChatOptions.personalDetails, + shouldShow: !_.isEmpty(newChatOptions.personalDetails), indexOffset, }); - indexOffset += personalDetails.length; + indexOffset += newChatOptions.personalDetails.length; - if (userToInvite && !OptionsListUtils.isCurrentUser(userToInvite)) { + if (newChatOptions.userToInvite && !OptionsListUtils.isCurrentUser(newChatOptions.userToInvite)) { newSections.push({ undefined, - data: [userToInvite], + data: [newChatOptions.userToInvite], shouldShow: true, indexOffset, }); } return newSections; - // eslint-disable-next-line react-hooks/exhaustive-deps -- props does not need to be a dependency as it will always exist - }, [maxParticipantsReached, newChatOptions, props.participants, props.personalDetails, props.translate]); + }, [maxParticipantsReached, newChatOptions, participants, personalDetails, translate]); /** * Removes a selected option from list if already selected. If not already selected add this option to the list. @@ -124,38 +121,30 @@ function MoneyRequestParticipantsSplitSelector(props) { */ const toggleOption = useCallback( (option) => { - const isOptionInList = _.some(props.participants, (selectedOption) => selectedOption.accountID === option.accountID); + const isOptionInList = _.some(participants, (selectedOption) => selectedOption.accountID === option.accountID); let newSelectedOptions; if (isOptionInList) { - newSelectedOptions = _.reject(props.participants, (selectedOption) => selectedOption.accountID === option.accountID); + newSelectedOptions = _.reject(participants, (selectedOption) => selectedOption.accountID === option.accountID); } else { - newSelectedOptions = [...props.participants, {accountID: option.accountID, login: option.login, selected: true}]; + newSelectedOptions = [...participants, {accountID: option.accountID, login: option.login, selected: true}]; } - props.onAddParticipants(newSelectedOptions); + onAddParticipants(newSelectedOptions); - const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( - props.reports, - props.personalDetails, - props.betas, - isOptionInList ? searchTerm : '', - newSelectedOptions, - CONST.EXPENSIFY_EMAILS, - ); + const chatOptions = OptionsListUtils.getNewChatOptions(reports, personalDetails, betas, isOptionInList ? searchTerm : '', newSelectedOptions, CONST.EXPENSIFY_EMAILS); setNewChatOptions({ - recentReports, - personalDetails, - userToInvite, + recentReports: chatOptions.recentReports, + personalDetails: chatOptions.personalDetails, + userToInvite: chatOptions.userToInvite, }); if (!isOptionInList) { setSearchTerm(''); } }, - // eslint-disable-next-line react-hooks/exhaustive-deps -- props does not need to be a dependency as it will always exist - [searchTerm, props.participants, props.onAddParticipants, props.reports, props.personalDetails, props.betas, setNewChatOptions, setSearchTerm], + [searchTerm, participants, onAddParticipants, reports, personalDetails, betas, setNewChatOptions, setSearchTerm], ); const headerMessage = OptionsListUtils.getHeaderMessage( @@ -164,40 +153,33 @@ function MoneyRequestParticipantsSplitSelector(props) { searchTerm, maxParticipantsReached, ); - const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(props.personalDetails); + const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails); useEffect(() => { - const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( - props.reports, - props.personalDetails, - props.betas, - searchTerm, - props.participants, - CONST.EXPENSIFY_EMAILS, - ); + const chatOptions = OptionsListUtils.getNewChatOptions(reports, personalDetails, betas, searchTerm, participants, CONST.EXPENSIFY_EMAILS); setNewChatOptions({ - recentReports, - personalDetails, - userToInvite, + recentReports: chatOptions.recentReports, + personalDetails: chatOptions.personalDetails, + userToInvite: chatOptions.userToInvite, }); - }, [props.betas, props.reports, props.participants, props.personalDetails, props.translate, searchTerm]); + }, [betas, reports, participants, personalDetails, translate, searchTerm, setNewChatOptions]); return ( - 0 ? props.safeAreaPaddingBottomStyle : {}]}> + 0 ? safeAreaPaddingBottomStyle : {}]}> From de7ccb5562f4ba6c6a70f83797b267a0d833e6e9 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 26 Jun 2023 11:32:48 +0700 Subject: [PATCH 100/171] Fix shield icon hover effect --- src/pages/ReimbursementAccount/Enable2FAPrompt.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/Enable2FAPrompt.js b/src/pages/ReimbursementAccount/Enable2FAPrompt.js index e27cba2b51a0..7681685b42ef 100644 --- a/src/pages/ReimbursementAccount/Enable2FAPrompt.js +++ b/src/pages/ReimbursementAccount/Enable2FAPrompt.js @@ -29,7 +29,6 @@ function Enable2FAPrompt(props) { icon: Expensicons.Shield, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, - iconFill: themeColors.success, wrapperStyle: [styles.cardMenuItem], link: () => Link.buildOldDotURL(secureYourAccountUrl), }, From 7f92ba647016e26e5ce1f25bebcf46d364811662 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 26 Jun 2023 11:48:14 +0700 Subject: [PATCH 101/171] fix lint --- src/pages/ReimbursementAccount/Enable2FAPrompt.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/Enable2FAPrompt.js b/src/pages/ReimbursementAccount/Enable2FAPrompt.js index 7681685b42ef..829ac9b63848 100644 --- a/src/pages/ReimbursementAccount/Enable2FAPrompt.js +++ b/src/pages/ReimbursementAccount/Enable2FAPrompt.js @@ -8,7 +8,6 @@ import * as Illustrations from '../../components/Icon/Illustrations'; import Section from '../../components/Section'; import * as Link from '../../libs/actions/Link'; import ROUTES from '../../ROUTES'; -import themeColors from '../../styles/themes/default'; const propTypes = { ...withLocalizePropTypes, From 21bf80dedd544577d6c022176ef7ca7876ad0eff Mon Sep 17 00:00:00 2001 From: Davi Rodrigues Date: Mon, 26 Jun 2023 20:58:38 -0300 Subject: [PATCH 102/171] fix: solve missing avatar workspace logo issue --- src/components/Avatar.js | 16 +++++++++------- src/components/SubscriptAvatar.js | 1 - 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/Avatar.js b/src/components/Avatar.js index 66a1b60c3cef..fa7d17d22535 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -73,9 +73,9 @@ function Avatar(props) { const isWorkspace = props.type === CONST.ICON_TYPE_WORKSPACE; const iconSize = StyleUtils.getAvatarSize(props.size); - const imageStyle = props.imageStyles ? [StyleUtils.getAvatarStyle(props.size), ...props.imageStyles, StyleUtils.getAvatarBorderRadius(props.size, props.type)] : undefined; + const imageStyle = props.imageStyles && props.imageStyles.length > 0 ? [StyleUtils.getAvatarStyle(props.size), ...props.imageStyles, StyleUtils.getAvatarBorderRadius(props.size, props.type)] : [StyleUtils.getAvatarStyle(props.size), StyleUtils.getAvatarBorderRadius(props.size, props.type)]; - const iconStyle = props.imageStyles ? [StyleUtils.getAvatarStyle(props.size), styles.bgTransparent, ...props.imageStyles] : undefined; + const iconStyle = props.imageStyles && props.imageStyles.length > 0 ? [StyleUtils.getAvatarStyle(props.size), styles.bgTransparent, ...props.imageStyles] : undefined; const iconFillColor = isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(props.name).fill : props.fill; const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(props.name) : props.fallbackIcon; @@ -101,11 +101,13 @@ function Avatar(props) { /> ) : ( - setImageError(true)} - /> + + setImageError(true)} + /> + )} ); diff --git a/src/components/SubscriptAvatar.js b/src/components/SubscriptAvatar.js index b94bf4dffd25..454d5204d3a4 100644 --- a/src/components/SubscriptAvatar.js +++ b/src/components/SubscriptAvatar.js @@ -68,7 +68,6 @@ function SubscriptAvatar(props) { Date: Tue, 27 Jun 2023 01:08:13 +0100 Subject: [PATCH 103/171] Remove allBetas access for dev environment --- src/libs/Permissions.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libs/Permissions.js b/src/libs/Permissions.js index d3e407260e20..cce0fc984dab 100644 --- a/src/libs/Permissions.js +++ b/src/libs/Permissions.js @@ -1,5 +1,4 @@ import _ from 'underscore'; -import * as Environment from './Environment/Environment'; import CONST from '../CONST'; /** @@ -8,7 +7,7 @@ import CONST from '../CONST'; * @returns {Boolean} */ function canUseAllBetas(betas) { - return Environment.isDevelopment() || _.contains(betas, CONST.BETAS.ALL); + return _.contains(betas, CONST.BETAS.ALL); } /** @@ -75,7 +74,7 @@ function canUseCommentLinking(betas) { * @returns {Boolean} */ function canUsePolicyRooms(betas) { - return _.contains(betas, CONST.BETAS.POLICY_ROOMS) || _.contains(betas, CONST.BETAS.ALL); + return _.contains(betas, CONST.BETAS.POLICY_ROOMS) || canUseAllBetas(betas); } /** @@ -91,7 +90,7 @@ function canUsePolicyExpenseChat(betas) { * @returns {Boolean} */ function canUsePasswordlessLogins(betas) { - return _.contains(betas, CONST.BETAS.PASSWORDLESS) || _.contains(betas, CONST.BETAS.ALL); + return _.contains(betas, CONST.BETAS.PASSWORDLESS) || canUseAllBetas(betas); } /** @@ -99,7 +98,7 @@ function canUsePasswordlessLogins(betas) { * @returns {Boolean} */ function canUseTasks(betas) { - return _.contains(betas, CONST.BETAS.TASKS) || _.contains(betas, CONST.BETAS.ALL); + return _.contains(betas, CONST.BETAS.TASKS) || canUseAllBetas(betas); } export default { From 6a5fffc0c1fb3b1d12d19dfa9e684d4b568f857f Mon Sep 17 00:00:00 2001 From: Fedi Rajhi Date: Tue, 27 Jun 2023 02:22:19 +0100 Subject: [PATCH 104/171] Fix Test , add All betas access --- tests/unit/OptionsListUtilsTest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index 75c7927888eb..f12c367b0038 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -296,7 +296,7 @@ describe('OptionsListUtils', () => { it('getSearchOptions()', () => { // When we filter in the Search view without providing a searchValue - let results = OptionsListUtils.getSearchOptions(REPORTS, PERSONAL_DETAILS, ''); + let results = OptionsListUtils.getSearchOptions(REPORTS, PERSONAL_DETAILS, '', [CONST.BETAS.ALL]); // Then the 2 personalDetails that don't have reports should be returned expect(results.personalDetails.length).toBe(2); From d986308459119b0020c74e0bf1a2e8b511eb2a87 Mon Sep 17 00:00:00 2001 From: ahmedGaber93 Date: Tue, 27 Jun 2023 08:46:02 +0200 Subject: [PATCH 105/171] fix OfflineWithFeedback errorRowStyles is misaligned --- src/pages/settings/Report/ReportSettingsPage.js | 1 + src/pages/workspace/WorkspaceInitialPage.js | 2 +- src/pages/workspace/WorkspacesListPage.js | 2 +- src/styles/styles.js | 4 ---- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/pages/settings/Report/ReportSettingsPage.js b/src/pages/settings/Report/ReportSettingsPage.js index 53f0b99910bb..4c8f82c625cd 100644 --- a/src/pages/settings/Report/ReportSettingsPage.js +++ b/src/pages/settings/Report/ReportSettingsPage.js @@ -119,6 +119,7 @@ class ReportSettingsPage extends Component { Report.clearPolicyRoomNameErrors(this.props.report.reportID)} > {shouldDisableRename ? ( diff --git a/src/pages/workspace/WorkspaceInitialPage.js b/src/pages/workspace/WorkspaceInitialPage.js index 73f659574e71..5728525a12f1 100644 --- a/src/pages/workspace/WorkspaceInitialPage.js +++ b/src/pages/workspace/WorkspaceInitialPage.js @@ -194,7 +194,7 @@ function WorkspaceInitialPage(props) { pendingAction={policy.pendingAction} onClose={() => dismissError(policy.id)} errors={policy.errors} - errorRowStyles={[styles.ph6, styles.pv2]} + errorRowStyles={[styles.ph5, styles.pv2]} > diff --git a/src/pages/workspace/WorkspacesListPage.js b/src/pages/workspace/WorkspacesListPage.js index f88ced6fc9fb..6f11c70a6707 100755 --- a/src/pages/workspace/WorkspacesListPage.js +++ b/src/pages/workspace/WorkspacesListPage.js @@ -156,7 +156,7 @@ class WorkspacesListPage extends Component { diff --git a/src/styles/styles.js b/src/styles/styles.js index f37013eb23b0..0ffe6e579472 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -2837,10 +2837,6 @@ const styles = { errorDot: { marginRight: 12, }, - menuItemErrorPadding: { - paddingLeft: 44, - paddingRight: 20, - }, }, dotIndicatorMessage: { From 87514ea874cacd77e980c57bb7cca5c65a157239 Mon Sep 17 00:00:00 2001 From: s-alves10 Date: Tue, 27 Jun 2023 10:12:50 -0500 Subject: [PATCH 106/171] fix: update selectedOptions when personalDetails or policyMembers changes --- src/pages/workspace/WorkspaceInvitePage.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js index 990320037b6d..104463f85be5 100644 --- a/src/pages/workspace/WorkspaceInvitePage.js +++ b/src/pages/workspace/WorkspaceInvitePage.js @@ -93,10 +93,7 @@ class WorkspaceInvitePage extends React.Component { } componentDidUpdate(prevProps) { - if (!_.isEqual(prevProps.personalDetails, this.props.personalDetails)) { - this.updateOptionsWithSearchTerm(this.props.searchTerm); - } - if (!_.isEqual(prevProps.policyMembers, this.props.policyMembers)) { + if (!_.isEqual(prevProps.personalDetails, this.props.personalDetails) || !_.isEqual(prevProps.policyMembers, this.props.policyMembers)) { this.updateOptionsWithSearchTerm(this.state.searchTerm); } @@ -176,10 +173,23 @@ class WorkspaceInvitePage extends React.Component { updateOptionsWithSearchTerm(searchTerm = '') { const {personalDetails, userToInvite} = OptionsListUtils.getMemberInviteOptions(this.props.personalDetails, this.props.betas, searchTerm, this.getExcludedUsers()); + + // update selectedOptions as well + const detailsMap = {}; + _.forEach(personalDetails, (detail) => (detailsMap[detail.login] = detail)); + const selectedOptions = []; + _.forEach(this.state.selectedOptions, (option) => { + if (!_.has(detailsMap, option.login)) { + return; + } + selectedOptions.push(detailsMap[option.login]); + }); + this.setState({ searchTerm, userToInvite, personalDetails, + selectedOptions, }); } From 87b1c91be49007ac953291521fa0537eb7fd51a7 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 27 Jun 2023 10:49:42 -1000 Subject: [PATCH 107/171] Apply suggested feedback --- src/libs/ReportActionsUtils.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 39f7e9f5530a..506a4d457764 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -442,11 +442,7 @@ function getMostRecentReportActionLastModified() { return; } - let lastModified = action.lastModified; - if (!lastModified) { - lastModified = action.created; - } - + const lastModified = action.lastModified || action.created; if (lastModified < mostRecentReportActionLastModified) { return; } From c256bb419a7fad10b1f7985c191074f59147bc5d Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 27 Jun 2023 13:21:01 -1000 Subject: [PATCH 108/171] Fix --- src/libs/ReportActionsUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 506a4d457764..ca1996e8a764 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -461,7 +461,7 @@ function getMostRecentReportActionLastModified() { mostRecentReportActionLastModified = reportLastVisibleActionLastModified; }); - Timing.end(CONST.TIMING.CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION, 500); + Timing.end(CONST.TIMING.CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION, '', 500); return mostRecentReportActionLastModified; } From 0a83f503eff6d46d2f065b888cd80d717a62b7f8 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 27 Jun 2023 13:32:21 -1000 Subject: [PATCH 109/171] Fix dependency cycle --- src/libs/ReportActionsUtils.js | 4 ---- src/libs/actions/App.js | 3 +++ src/libs/actions/Timing.js | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index ca1996e8a764..d873e9a82c5a 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -10,7 +10,6 @@ import ONYXKEYS from '../ONYXKEYS'; import Log from './Log'; import * as CurrencyUtils from './CurrencyUtils'; import isReportMessageAttachment from './isReportMessageAttachment'; -import Timing from './actions/Timing'; const allReports = {}; Onyx.connect({ @@ -427,8 +426,6 @@ function getLinkedTransactionID(reportID, reportActionID) { * @returns {string} */ function getMostRecentReportActionLastModified() { - Timing.start(CONST.TIMING.CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION); - // Start with the oldest date possible let mostRecentReportActionLastModified = new Date(0).toISOString(); @@ -461,7 +458,6 @@ function getMostRecentReportActionLastModified() { mostRecentReportActionLastModified = reportLastVisibleActionLastModified; }); - Timing.end(CONST.TIMING.CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION, '', 500); return mostRecentReportActionLastModified; } diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 6053bbb81a98..83d0f5469988 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -16,6 +16,7 @@ import * as SessionUtils from '../SessionUtils'; import getCurrentUrl from '../Navigation/currentUrl'; import * as Session from './Session'; import * as ReportActionsUtils from '../ReportActionsUtils'; +import Timing from './Timing'; let currentUserAccountID; Onyx.connect({ @@ -146,7 +147,9 @@ function openApp(isReconnecting = false) { // - We send this to the server so that it can compute which chats are critical for the user to see and return only those as an optimization. const params = {policyIDList: getNonOptimisticPolicyIDs(policies)}; if (isReconnecting) { + Timing.start(CONST.TIMING.CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION); params.mostRecentReportActionLastModified = ReportActionsUtils.getMostRecentReportActionLastModified(); + Timing.end(CONST.TIMING.CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION, '', 500); } Onyx.disconnect(connectionID); diff --git a/src/libs/actions/Timing.js b/src/libs/actions/Timing.js index 30fd8353ad40..e0cfbc3031d4 100644 --- a/src/libs/actions/Timing.js +++ b/src/libs/actions/Timing.js @@ -1,7 +1,6 @@ import getPlatform from '../getPlatform'; import * as Environment from '../Environment/Environment'; import Firebase from '../Firebase'; -// eslint-disable-next-line import/no-cycle import * as API from '../API'; import Log from '../Log'; From faf1a01195f254d1162f3dc9906bd8da1654d633 Mon Sep 17 00:00:00 2001 From: Nikhil Vats Date: Wed, 28 Jun 2023 19:52:44 +0530 Subject: [PATCH 110/171] Remove updateOptionsWithSearchTerm and use useEffect --- src/pages/NewChatPage.js | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index e6f0472241dc..8927088c3f67 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -44,6 +44,8 @@ const defaultProps = { reports: {}, }; +const excludedGroupEmails = _.without(CONST.EXPENSIFY_EMAILS, CONST.EMAIL.CONCIERGE); + function NewChatPage(props) { const [searchTerm, setSearchTerm] = useState(''); const [filteredRecentReports, setFilteredRecentReports] = useState([]); @@ -51,7 +53,6 @@ function NewChatPage(props) { const [filteredUserToInvite, setFilteredUserToInvite] = useState(); const [selectedOptions, setSelectedOptions] = useState([]); - const excludedGroupEmails = _.without(CONST.EXPENSIFY_EMAILS, CONST.EMAIL.CONCIERGE); const maxParticipantsReached = selectedOptions.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; const headerMessage = OptionsListUtils.getHeaderMessage( filteredPersonalDetails.length + filteredRecentReports.length !== 0, @@ -114,21 +115,6 @@ function NewChatPage(props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [filteredPersonalDetails, filteredRecentReports, filteredUserToInvite, maxParticipantsReached, props.isGroupChat, selectedOptions]); - const updateOptionsWithSearchTerm = (newSearchTerm = '') => { - const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( - props.reports, - props.personalDetails, - props.betas, - newSearchTerm, - [], - props.isGroupChat ? excludedGroupEmails : [], - ); - setSearchTerm(newSearchTerm); - setFilteredRecentReports(recentReports); - setFilteredPersonalDetails(personalDetails); - setFilteredUserToInvite(userToInvite); - }; - /** * Removes a selected option from list if already selected. If not already selected add this option to the list. * @param {Object} option @@ -178,12 +164,20 @@ function NewChatPage(props) { }; useEffect(() => { - updateOptionsWithSearchTerm(searchTerm); - // all dependencies are not added below - - // 1. searchTerm - when searchTerm changes updateOptionsWithSearchTerm is called by OptionsSelector's onChangeText prop - // 2. updateOptionsWithSearchTerm - it will change its reference upon each rerender unnecessarily + const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( + props.reports, + props.personalDetails, + props.betas, + searchTerm, + [], + props.isGroupChat ? excludedGroupEmails : [], + ); + setFilteredRecentReports(recentReports); + setFilteredPersonalDetails(personalDetails); + setFilteredUserToInvite(userToInvite); + // props.betas and props.isGroupChat are not added as dependencies since they don't change during the component lifecycle // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.reports, props.personalDetails]); + }, [props.reports, props.personalDetails, searchTerm]); return ( (props.isGroupChat ? toggleOption(option) : createChat(option))} - onChangeText={updateOptionsWithSearchTerm} + onChangeText={setSearchTerm} headerMessage={headerMessage} boldStyle shouldFocusOnSelectRow={props.isGroupChat && !Browser.isMobile()} From d4d97c5cf76faf0d743ff2e55dc0b0214d0336b8 Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Wed, 28 Jun 2023 11:05:55 -0700 Subject: [PATCH 111/171] Make sure we call OpenProfile action even if we're accessing the page directly --- src/libs/actions/App.js | 18 ++---------------- src/libs/actions/IOU.js | 6 ++++++ src/pages/settings/InitialSettingsPage.js | 3 +-- src/pages/settings/Profile/ProfilePage.js | 7 ++++++- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 38638e22a464..d4012829b90c 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -31,18 +31,6 @@ Onyx.connect({ initWithStoredValues: false, }); -let myPersonalDetails; -Onyx.connect({ - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - callback: (val) => { - if (!val || !currentUserAccountID) { - return; - } - - myPersonalDetails = val[currentUserAccountID]; - }, -}); - let allPolicies = []; Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY, @@ -267,8 +255,8 @@ function setUpPoliciesAndNavigate(session) { } } -function openProfile() { - const oldTimezoneData = myPersonalDetails.timezone || {}; +function openProfile(personalDetails) { + const oldTimezoneData = personalDetails.timezone || {}; let newTimezoneData = oldTimezoneData; if (lodashGet(oldTimezoneData, 'automatic', true)) { @@ -308,8 +296,6 @@ function openProfile() { ], }, ); - - Navigation.navigate(ROUTES.SETTINGS_PROFILE); } export {setLocale, setLocaleAndNavigate, setSidebarLoaded, setUpPoliciesAndNavigate, openProfile, openApp, reconnectApp, confirmReadyToOpenApp}; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 1e3a297f08da..dac3b07515a0 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1070,6 +1070,11 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType optimisticData.push(optimisticPersonalDetailListData); } + let reportPreviewAction = ReportActionsUtils.getReportPreviewAction(chatReport.reportID, optimisticIOUReport.reportID); + if (!reportPreviewAction) { + reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport.reportID, optimisticIOUReport.reportID); + } + return { params: { iouReportID: optimisticIOUReport.reportID, @@ -1079,6 +1084,7 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType transactionID: optimisticTransaction.transactionID, newIOUReportDetails, createdReportActionID: isNewChat ? optimisticCreatedAction.reportActionID : 0, + reportPreviewReportActionID: reportPreviewAction.reportActionID, }, optimisticData, successData, diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index b7364ce623fa..97d013f31084 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -21,7 +21,6 @@ import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize import compose from '../../libs/compose'; import CONST from '../../CONST'; import Permissions from '../../libs/Permissions'; -import * as App from '../../libs/actions/App'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes, withCurrentUserPersonalDetailsDefaultProps} from '../../components/withCurrentUserPersonalDetails'; import * as PaymentMethods from '../../libs/actions/PaymentMethods'; import bankAccountPropTypes from '../../components/bankAccountPropTypes'; @@ -200,7 +199,7 @@ class InitialSettingsPage extends React.Component { translationKey: 'common.profile', icon: Expensicons.Profile, action: () => { - App.openProfile(); + Navigation.navigate(ROUTES.SETTINGS_PROFILE); }, brickRoadIndicator: profileBrickRoadIndicator, }, diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index 1fd1e585648d..07db3d0cdffb 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -1,5 +1,5 @@ import lodashGet from 'lodash/get'; -import React from 'react'; +import React, {useEffect} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; @@ -23,6 +23,7 @@ import * as Expensicons from '../../../components/Icon/Expensicons'; import ONYXKEYS from '../../../ONYXKEYS'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; import userPropTypes from '../userPropTypes'; +import * as App from '../../../libs/actions/App'; const propTypes = { /* Onyx Props */ @@ -84,6 +85,10 @@ function ProfilePage(props) { }, ]; + useEffect(() => { + App.openProfile(props.currentUserPersonalDetails); + }, [props.currentUserPersonalDetails]); + return ( Date: Wed, 28 Jun 2023 14:28:18 -0500 Subject: [PATCH 112/171] fix: comment --- src/pages/workspace/WorkspaceInvitePage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js index 104463f85be5..1092579f0bc5 100644 --- a/src/pages/workspace/WorkspaceInvitePage.js +++ b/src/pages/workspace/WorkspaceInvitePage.js @@ -174,7 +174,7 @@ class WorkspaceInvitePage extends React.Component { updateOptionsWithSearchTerm(searchTerm = '') { const {personalDetails, userToInvite} = OptionsListUtils.getMemberInviteOptions(this.props.personalDetails, this.props.betas, searchTerm, this.getExcludedUsers()); - // update selectedOptions as well + // Update selectedOptions with the latest personalDetails and policyMembers information const detailsMap = {}; _.forEach(personalDetails, (detail) => (detailsMap[detail.login] = detail)); const selectedOptions = []; From 76f76ade3dede805b204558506afbbcca98a4212 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 28 Jun 2023 10:19:37 -1000 Subject: [PATCH 113/171] Make requested changes --- src/libs/actions/App.js | 2 +- src/libs/actions/Timing.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 83d0f5469988..56cf911b9848 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -144,7 +144,7 @@ function openApp(isReconnecting = false) { // we have locally. And then only update the user about chats with messages that have occurred after that reportActionID. // // - Look through the local report actions and reports to find the most recently modified report action or report. - // - We send this to the server so that it can compute which chats are critical for the user to see and return only those as an optimization. + // - We send this to the server so that it can compute which new chats the user needs to see and return only those as an optimization. const params = {policyIDList: getNonOptimisticPolicyIDs(policies)}; if (isReconnecting) { Timing.start(CONST.TIMING.CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION); diff --git a/src/libs/actions/Timing.js b/src/libs/actions/Timing.js index e0cfbc3031d4..2be2cdc6fa63 100644 --- a/src/libs/actions/Timing.js +++ b/src/libs/actions/Timing.js @@ -27,7 +27,7 @@ function start(eventName, shouldUseFirebase = false) { * * @param {String} eventName - event name used as timestamp key * @param {String} [secondaryName] - optional secondary event name, passed to grafana - * @param {number} [maxExecutionTime] - optional amount of time to wait before logging an alert + * @param {number} [maxExecutionTime] - optional amount of time (ms) to wait before logging a warn */ function end(eventName, secondaryName = '', maxExecutionTime = 0) { if (!timestampData[eventName]) { @@ -54,7 +54,7 @@ function end(eventName, secondaryName = '', maxExecutionTime = 0) { } if (maxExecutionTime && eventTime > maxExecutionTime) { - Log.alert(`${eventName} exceeded max execution time of ${maxExecutionTime}.`, {eventTime, eventName}); + Log.warn(`${eventName} exceeded max execution time of ${maxExecutionTime}.`, {eventTime, eventName}); } API.read( From b417a915ed2e13fd5df36fa74959378c56164144 Mon Sep 17 00:00:00 2001 From: Puneet Lath Date: Wed, 28 Jun 2023 17:12:50 -0400 Subject: [PATCH 114/171] Remove personalDetails as part of migration --- src/libs/migrations/PersonalDetailsByAccountID.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libs/migrations/PersonalDetailsByAccountID.js b/src/libs/migrations/PersonalDetailsByAccountID.js index 2872e0ed1afe..85fe15318d2d 100644 --- a/src/libs/migrations/PersonalDetailsByAccountID.js +++ b/src/libs/migrations/PersonalDetailsByAccountID.js @@ -225,6 +225,12 @@ export default function () { } }); + // The personalDetails object has been replaced by personalDetailsList + // So if we find an instance of personalDetails we will clear it out + if (oldPersonalDetails) { + onyxData[DEPRECATED_ONYX_KEYS.PERSONAL_DETAILS] = null; + } + return Onyx.multiSet(onyxData); }, ); From c549da29b7334a3cdd7e4a5edf51b56de2ccbeaa Mon Sep 17 00:00:00 2001 From: Puneet Lath Date: Wed, 28 Jun 2023 17:19:22 -0400 Subject: [PATCH 115/171] Add test for removing personalDetails --- .../migrations/PersonalDetailsByAccountID.js | 1 + tests/unit/MigrationTest.js | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/libs/migrations/PersonalDetailsByAccountID.js b/src/libs/migrations/PersonalDetailsByAccountID.js index 85fe15318d2d..34b8ebabc150 100644 --- a/src/libs/migrations/PersonalDetailsByAccountID.js +++ b/src/libs/migrations/PersonalDetailsByAccountID.js @@ -228,6 +228,7 @@ export default function () { // The personalDetails object has been replaced by personalDetailsList // So if we find an instance of personalDetails we will clear it out if (oldPersonalDetails) { + Log.info('[Migrate Onyx] PersonalDetailsByAccountID migration: removing personalDetails'); onyxData[DEPRECATED_ONYX_KEYS.PERSONAL_DETAILS] = null; } diff --git a/tests/unit/MigrationTest.js b/tests/unit/MigrationTest.js index 6162bded793b..6ca3b5b2d516 100644 --- a/tests/unit/MigrationTest.js +++ b/tests/unit/MigrationTest.js @@ -845,5 +845,30 @@ describe('Migrations', () => { }, }); })); + + it('Should succeed in removing the personalDetails object if found in Onyx', () => + Onyx.multiSet({ + [`${DEPRECATED_ONYX_KEYS.PERSONAL_DETAILS}`]: { + 'test1@account.com': { + accountID: 100, + login: 'test1@account.com', + }, + 'test2@account.com': { + accountID: 101, + login: 'test2@account.com', + }, + }, + }) + .then(PersonalDetailsByAccountID) + .then(() => { + expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] PersonalDetailsByAccountID migration: removing personalDetails'); + const connectionID = Onyx.connect({ + key: DEPRECATED_ONYX_KEYS.PERSONAL_DETAILS, + callback: (allPersonalDetails) => { + Onyx.disconnect(connectionID); + expect(allPersonalDetails).toBeNull(); + }, + }); + })); }); }); From 24316ab8230342fdd7cdf07e4bcdfc263da623d1 Mon Sep 17 00:00:00 2001 From: Nishan Chhetri <52850000+nishancx@users.noreply.github.com> Date: Thu, 29 Jun 2023 11:12:28 +0545 Subject: [PATCH 116/171] Use fallback for country --- src/components/AddressSearch/index.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 9699eb9aab94..4f49f64f2b6c 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import {LogBox, ScrollView, View} from 'react-native'; import {GooglePlacesAutocomplete} from 'react-native-google-places-autocomplete'; import lodashGet from 'lodash/get'; +import findKey from 'lodash/findKey'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import styles from '../../styles/styles'; import themeColors from '../../styles/themes/default'; @@ -142,7 +143,15 @@ function AddressSearch(props) { // Make sure that the order of keys remains such that the country is always set above the state. // Refer to https://github.com/Expensify/App/issues/15633 for more information. - const {state: stateAutoCompleteFallback = '', city: cityAutocompleteFallback = ''} = GooglePlacesUtils.getPlaceAutocompleteTerms(autocompleteData.terms); + const { + country: countryAutoCompleteFallback = '', + state: stateAutoCompleteFallback = '', + city: cityAutocompleteFallback = '', + } = GooglePlacesUtils.getPlaceAutocompleteTerms(autocompleteData.terms); + + const autoCompleteFallbackCountryCode = findKey(CONST.ALL_COUNTRIES, (country) => country === countryAutoCompleteFallback); + + const countryWithFallback = country || autoCompleteFallbackCountryCode; const values = { street: `${streetNumber} ${streetName}`.trim(), @@ -162,13 +171,13 @@ function AddressSearch(props) { // If the address is not in the US, use the full length state name since we're displaying the address's // state / province in a TextInput instead of in a picker. - if (country !== CONST.COUNTRY.US) { + if (countryWithFallback !== CONST.COUNTRY.US) { values.state = longStateName; } // UK addresses return countries (e.g. England) in the state field (administrative_area_level_1) // So we use a secondary field (administrative_area_level_2) as a fallback - if (country === CONST.COUNTRY.GB) { + if (countryWithFallback === CONST.COUNTRY.GB) { values.state = stateFallback; } @@ -178,9 +187,10 @@ function AddressSearch(props) { values.street += `, ${subpremise}`; } - const isValidCountryCode = lodashGet(CONST.ALL_COUNTRIES, country); + const isValidCountryCode = lodashGet(CONST.ALL_COUNTRIES, countryWithFallback); + if (isValidCountryCode) { - values.country = country; + values.country = countryWithFallback; } if (props.inputID) { From d1d23ab4259dbf3803d19c0996f4aa23763fc580 Mon Sep 17 00:00:00 2001 From: Puneet Lath Date: Wed, 28 Jun 2023 12:21:12 -0400 Subject: [PATCH 117/171] Fix proptypes in ArchivedReportFooter for accountIDs instead of logins --- src/components/ArchivedReportFooter.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ArchivedReportFooter.js b/src/components/ArchivedReportFooter.js index a742b97fb0a9..1af8cadc80ca 100644 --- a/src/components/ArchivedReportFooter.js +++ b/src/components/ArchivedReportFooter.js @@ -22,11 +22,11 @@ const propTypes = { /** The reason the report was closed */ reason: PropTypes.string.isRequired, - /** (For accountMerged reason only), the email of the previous owner of this report. */ - oldLogin: PropTypes.string, + /** (For accountMerged reason only), the accountID of the previous owner of this report. */ + oldAccountID: PropTypes.number, - /** (For accountMerged reason only), the email of the account the previous owner was merged into */ - newLogin: PropTypes.string, + /** (For accountMerged reason only), the accountID of the account the previous owner was merged into */ + newLogin: PropTypes.number, }).isRequired, }), From 043b9e64339e4c437449183d1a56a7f4f58c900c Mon Sep 17 00:00:00 2001 From: Puneet Lath Date: Thu, 29 Jun 2023 10:02:22 -0400 Subject: [PATCH 118/171] Update src/components/ArchivedReportFooter.js Co-authored-by: Alex Beaman --- src/components/ArchivedReportFooter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ArchivedReportFooter.js b/src/components/ArchivedReportFooter.js index 1af8cadc80ca..d7a138d9a473 100644 --- a/src/components/ArchivedReportFooter.js +++ b/src/components/ArchivedReportFooter.js @@ -26,7 +26,7 @@ const propTypes = { oldAccountID: PropTypes.number, /** (For accountMerged reason only), the accountID of the account the previous owner was merged into */ - newLogin: PropTypes.number, + newAccountID: PropTypes.number, }).isRequired, }), From b895d5efe9318b9dbc61a32bf958c207b0cd2040 Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Thu, 29 Jun 2023 10:21:10 -0700 Subject: [PATCH 119/171] Remove mixed up code --- src/libs/actions/IOU.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index dac3b07515a0..1e3a297f08da 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1070,11 +1070,6 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType optimisticData.push(optimisticPersonalDetailListData); } - let reportPreviewAction = ReportActionsUtils.getReportPreviewAction(chatReport.reportID, optimisticIOUReport.reportID); - if (!reportPreviewAction) { - reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport.reportID, optimisticIOUReport.reportID); - } - return { params: { iouReportID: optimisticIOUReport.reportID, @@ -1084,7 +1079,6 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType transactionID: optimisticTransaction.transactionID, newIOUReportDetails, createdReportActionID: isNewChat ? optimisticCreatedAction.reportActionID : 0, - reportPreviewReportActionID: reportPreviewAction.reportActionID, }, optimisticData, successData, From 1f34ef8d7eb9bb1531eca99a5b64224e014662f7 Mon Sep 17 00:00:00 2001 From: Manjesh yadav Date: Thu, 29 Jun 2023 23:51:10 +0530 Subject: [PATCH 120/171] Fix phone number show twice in tooltip --- src/components/UserDetailsTooltip/index.js | 25 ++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/components/UserDetailsTooltip/index.js b/src/components/UserDetailsTooltip/index.js index 786531801837..7641ff126dd1 100644 --- a/src/components/UserDetailsTooltip/index.js +++ b/src/components/UserDetailsTooltip/index.js @@ -10,9 +10,28 @@ import {propTypes, defaultProps} from './userDetailsTooltipPropTypes'; import styles from '../../styles/styles'; import ONYXKEYS from '../../ONYXKEYS'; import * as UserUtils from '../../libs/UserUtils'; +import * as LocalePhoneNumber from '../../libs/LocalePhoneNumber'; function UserDetailsTooltip(props) { const userDetails = lodashGet(props.personalDetailsList, props.accountID, props.fallbackUserDetails); + + /** + * Converts user login to formatted phone number if login is SMS login + * @param {String} login + * @returns {String} + */ + const formatLoginIdAsDisplayName = useCallback((login) => { + if (!login) { + return null; + } + + if (!Str.isSMSLogin(login)) { + return login; + } + + return LocalePhoneNumber.formatPhoneNumber(Str.removeSMSDomain(login)); + }, []); + const renderTooltipContent = useCallback( () => ( @@ -28,11 +47,13 @@ function UserDetailsTooltip(props) { - {String(userDetails.login || '').trim() && !_.isEqual(userDetails.login, userDetails.displayName) ? Str.removeSMSDomain(userDetails.login) : ''} + {String(userDetails.login || '').trim() && !_.isEqual(formatLoginIdAsDisplayName(String(userDetails.login || '')), userDetails.displayName) + ? Str.removeSMSDomain(userDetails.login) + : ''} ), - [userDetails.avatar, userDetails.displayName, userDetails.login, userDetails.accountID], + [userDetails.avatar, userDetails.displayName, userDetails.login, userDetails.accountID, formatLoginIdAsDisplayName], ); if (!userDetails.displayName && !userDetails.login) { From 7e50eb6a280a4c14f7a6c8f8e4c4d57851ea1768 Mon Sep 17 00:00:00 2001 From: Nishan Chhetri <52850000+nishancx@users.noreply.github.com> Date: Fri, 30 Jun 2023 05:40:54 +0545 Subject: [PATCH 121/171] Fix lint error --- src/components/AddressSearch/index.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 4f49f64f2b6c..1275b26d5836 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -121,7 +121,7 @@ function AddressSearch(props) { postal_code: zipCode, administrative_area_level_1: state, administrative_area_level_2: stateFallback, - country, + country: countryPrimary, } = GooglePlacesUtils.getAddressComponents(addressComponents, { street_number: 'long_name', route: 'long_name', @@ -144,14 +144,14 @@ function AddressSearch(props) { // Make sure that the order of keys remains such that the country is always set above the state. // Refer to https://github.com/Expensify/App/issues/15633 for more information. const { - country: countryAutoCompleteFallback = '', + country: countryFallbackLongName = '', state: stateAutoCompleteFallback = '', city: cityAutocompleteFallback = '', } = GooglePlacesUtils.getPlaceAutocompleteTerms(autocompleteData.terms); - const autoCompleteFallbackCountryCode = findKey(CONST.ALL_COUNTRIES, (country) => country === countryAutoCompleteFallback); + const countryFallback = findKey(CONST.ALL_COUNTRIES, (country) => country === countryFallbackLongName); - const countryWithFallback = country || autoCompleteFallbackCountryCode; + const country = countryPrimary || countryFallback; const values = { street: `${streetNumber} ${streetName}`.trim(), @@ -171,13 +171,13 @@ function AddressSearch(props) { // If the address is not in the US, use the full length state name since we're displaying the address's // state / province in a TextInput instead of in a picker. - if (countryWithFallback !== CONST.COUNTRY.US) { + if (country !== CONST.COUNTRY.US) { values.state = longStateName; } // UK addresses return countries (e.g. England) in the state field (administrative_area_level_1) // So we use a secondary field (administrative_area_level_2) as a fallback - if (countryWithFallback === CONST.COUNTRY.GB) { + if (country === CONST.COUNTRY.GB) { values.state = stateFallback; } @@ -187,10 +187,10 @@ function AddressSearch(props) { values.street += `, ${subpremise}`; } - const isValidCountryCode = lodashGet(CONST.ALL_COUNTRIES, countryWithFallback); + const isValidCountryCode = lodashGet(CONST.ALL_COUNTRIES, countryFallback); if (isValidCountryCode) { - values.country = countryWithFallback; + values.country = country; } if (props.inputID) { From 180edef82d47382a021f336282f4c10c6c3df465 Mon Sep 17 00:00:00 2001 From: Nishan Chhetri <52850000+nishancx@users.noreply.github.com> Date: Fri, 30 Jun 2023 05:43:22 +0545 Subject: [PATCH 122/171] Check if country is valid --- src/components/AddressSearch/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 1275b26d5836..9f7ab913380b 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -187,7 +187,7 @@ function AddressSearch(props) { values.street += `, ${subpremise}`; } - const isValidCountryCode = lodashGet(CONST.ALL_COUNTRIES, countryFallback); + const isValidCountryCode = lodashGet(CONST.ALL_COUNTRIES, country); if (isValidCountryCode) { values.country = country; From 85c81b64fa67e8d74405cb526444df5d4f1fcb9c Mon Sep 17 00:00:00 2001 From: Nishan Chhetri <52850000+nishancx@users.noreply.github.com> Date: Fri, 30 Jun 2023 05:44:23 +0545 Subject: [PATCH 123/171] Cleanup --- src/components/AddressSearch/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 9f7ab913380b..34f96bd8745d 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -188,7 +188,6 @@ function AddressSearch(props) { } const isValidCountryCode = lodashGet(CONST.ALL_COUNTRIES, country); - if (isValidCountryCode) { values.country = country; } From 4047c4b6079aff5f3a1c669b34a122aa296c8acd Mon Sep 17 00:00:00 2001 From: jeet-dhandha Date: Fri, 30 Jun 2023 05:30:39 +0530 Subject: [PATCH 124/171] fix: emoji showing original message for few moments --- src/libs/ReportActionsUtils.js | 15 +++++++++++++++ src/libs/actions/Report.js | 7 ++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index eb871bcf62c1..f79b6b53fc37 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -425,6 +425,20 @@ function getLinkedTransactionID(reportID, reportActionID) { return reportAction.originalMessage.IOUTransactionID; } +/** + * Returns the parentReportAction if the given report is a thread/task. + * + * @param {String} reportID + * @param {String} reportActionID + * @returns {Object} + */ +function getReportAction(reportID, reportActionID) { + if (!reportID || !reportActionID) { + return {}; + } + return lodashGet(allReportActions, [reportID, reportActionID], {}); +} + /** * @param {*} chatReportID * @param {*} iouReportID @@ -492,4 +506,5 @@ export { isMessageDeleted, isWhisperAction, isPendingRemove, + getReportAction, }; diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 2f6f812bdbe3..a05ca26ef0df 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1639,15 +1639,16 @@ function removeEmojiReaction(reportID, originalReportAction, emoji) { * @returns {Promise} */ function toggleEmojiReaction(reportID, reportAction, emoji, paramSkinTone = preferredSkinTone) { - const message = reportAction.message[0]; + const latestReportAction = ReportActionsUtils.getReportAction(reportID, reportAction.reportActionID); + const message = latestReportAction.message[0]; const reactionObject = message.reactions && _.find(message.reactions, (reaction) => reaction.emoji === emoji.name); const skinTone = emoji.types === undefined ? null : paramSkinTone; // only use skin tone if emoji supports it if (reactionObject) { if (hasAccountIDReacted(currentUserAccountID, reactionObject.users, skinTone)) { - return removeEmojiReaction(reportID, reportAction, emoji, skinTone); + return removeEmojiReaction(reportID, latestReportAction, emoji, skinTone); } } - return addEmojiReaction(reportID, reportAction, emoji, skinTone); + return addEmojiReaction(reportID, latestReportAction, emoji, skinTone); } /** From ba65be5b2ddab640f694d76612e35ad793299b76 Mon Sep 17 00:00:00 2001 From: Nishan Chhetri <52850000+nishancx@users.noreply.github.com> Date: Fri, 30 Jun 2023 07:58:23 +0545 Subject: [PATCH 125/171] Add test for getPlaceAutocompleteTerms --- tests/unit/GooglePlacesUtilsTest.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/unit/GooglePlacesUtilsTest.js b/tests/unit/GooglePlacesUtilsTest.js index ef7a4491fec0..1bb27bdd9f2f 100644 --- a/tests/unit/GooglePlacesUtilsTest.js +++ b/tests/unit/GooglePlacesUtilsTest.js @@ -129,6 +129,12 @@ const addressComponents = [ types: ['postal_code'], }, ]; + +const autoCompleteTerms = [ + {offset: 0, value: 'Bangladesh Border Road'}, + {offset: 24, value: 'Bangladesh'}, +]; + describe('GooglePlacesUtilsTest', () => { describe('getAddressComponents', () => { it('should find address components by type', () => { @@ -189,4 +195,14 @@ describe('GooglePlacesUtilsTest', () => { expect(executionTime).toBeLessThan(5.0); }); }); + describe('getPlaceAutocompleteTerms', () => { + it('should find auto complete terms', () => { + expect(GooglePlacesUtils.getPlaceAutocompleteTerms(autoCompleteTerms)).toStrictEqual({ + country: 'Bangladesh', + state: 'Bangladesh Border Road', + city: '', + street: '', + }); + }); + }); }); From d787ee9f7a22aad1d0ad505381e8d33409c84566 Mon Sep 17 00:00:00 2001 From: Manjesh yadav Date: Fri, 30 Jun 2023 09:21:33 +0530 Subject: [PATCH 126/171] remove unnecessary code --- src/components/UserDetailsTooltip/index.js | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/components/UserDetailsTooltip/index.js b/src/components/UserDetailsTooltip/index.js index 7641ff126dd1..42a3495c0e0f 100644 --- a/src/components/UserDetailsTooltip/index.js +++ b/src/components/UserDetailsTooltip/index.js @@ -15,23 +15,6 @@ import * as LocalePhoneNumber from '../../libs/LocalePhoneNumber'; function UserDetailsTooltip(props) { const userDetails = lodashGet(props.personalDetailsList, props.accountID, props.fallbackUserDetails); - /** - * Converts user login to formatted phone number if login is SMS login - * @param {String} login - * @returns {String} - */ - const formatLoginIdAsDisplayName = useCallback((login) => { - if (!login) { - return null; - } - - if (!Str.isSMSLogin(login)) { - return login; - } - - return LocalePhoneNumber.formatPhoneNumber(Str.removeSMSDomain(login)); - }, []); - const renderTooltipContent = useCallback( () => ( @@ -47,13 +30,13 @@ function UserDetailsTooltip(props) { - {String(userDetails.login || '').trim() && !_.isEqual(formatLoginIdAsDisplayName(String(userDetails.login || '')), userDetails.displayName) + {String(userDetails.login || '').trim() && !_.isEqual(LocalePhoneNumber.formatPhoneNumber(userDetails.login || ''), userDetails.displayName) ? Str.removeSMSDomain(userDetails.login) : ''} ), - [userDetails.avatar, userDetails.displayName, userDetails.login, userDetails.accountID, formatLoginIdAsDisplayName], + [userDetails.avatar, userDetails.displayName, userDetails.login, userDetails.accountID], ); if (!userDetails.displayName && !userDetails.login) { From 3e0c8d66fd60881f32e44fba08ef890525686491 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 30 Jun 2023 13:03:47 +0800 Subject: [PATCH 127/171] fix can't open link inside promise in safari --- src/libs/actions/Link.js | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/libs/actions/Link.js b/src/libs/actions/Link.js index 09c040ab47bc..350528bb6086 100644 --- a/src/libs/actions/Link.js +++ b/src/libs/actions/Link.js @@ -67,17 +67,12 @@ function openOldDotLink(url) { // If shortLivedAuthToken is not accessible, fallback to opening the link without the token. // eslint-disable-next-line rulesdir/no-api-side-effects-method - API.makeRequestWithSideEffects('OpenOldDotLink', {}, {}) - .then((response) => { - buildOldDotURL(url, response.shortLivedAuthToken).then((oldDotUrl) => { - Linking.openURL(oldDotUrl); - }); - }) - .catch(() => { - buildOldDotURL(url).then((oldDotUrl) => { - Linking.openURL(oldDotUrl); - }); - }); + asyncOpenURL( + API.makeRequestWithSideEffects('OpenOldDotLink', {}, {}) + .then((response) => buildOldDotURL(url, response.shortLivedAuthToken)) + .catch(() => buildOldDotURL(url)), + (url) => url, + ); } /** From 23c891b34071768ad6d0729e77658ec97b31996e Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 30 Jun 2023 13:12:16 +0800 Subject: [PATCH 128/171] don't submit when pressing enter --- src/pages/workspace/WorkspaceInvitePage.js | 86 ++++++++++------------ 1 file changed, 40 insertions(+), 46 deletions(-) diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js index 990320037b6d..3f4f944db711 100644 --- a/src/pages/workspace/WorkspaceInvitePage.js +++ b/src/pages/workspace/WorkspaceInvitePage.js @@ -13,7 +13,6 @@ import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; import * as Policy from '../../libs/actions/Policy'; import FormAlertWithSubmitButton from '../../components/FormAlertWithSubmitButton'; -import FormSubmit from '../../components/FormSubmit'; import OptionsSelector from '../../components/OptionsSelector'; import * as OptionsListUtils from '../../libs/OptionsListUtils'; import CONST from '../../CONST'; @@ -267,52 +266,47 @@ class WorkspaceInvitePage extends React.Component { shouldShow={_.isEmpty(this.props.policy)} onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} > - - { - this.clearErrors(); - Navigation.goBack(ROUTES.getWorkspaceMembersRoute(this.props.route.params.policyID)); - }} + { + this.clearErrors(); + Navigation.goBack(ROUTES.getWorkspaceMembersRoute(this.props.route.params.policyID)); + }} + /> + + - - - - - - - + + + + ); From 96c4b35f8dc407e3f8486bd808b88e392031fd0f Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 30 Jun 2023 13:23:59 +0800 Subject: [PATCH 129/171] fix lint --- src/libs/actions/Link.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Link.js b/src/libs/actions/Link.js index 350528bb6086..fc29e70de5e0 100644 --- a/src/libs/actions/Link.js +++ b/src/libs/actions/Link.js @@ -66,12 +66,12 @@ function openOldDotLink(url) { } // If shortLivedAuthToken is not accessible, fallback to opening the link without the token. - // eslint-disable-next-line rulesdir/no-api-side-effects-method asyncOpenURL( + // eslint-disable-next-line rulesdir/no-api-side-effects-method API.makeRequestWithSideEffects('OpenOldDotLink', {}, {}) .then((response) => buildOldDotURL(url, response.shortLivedAuthToken)) .catch(() => buildOldDotURL(url)), - (url) => url, + (urlRes) => urlRes, ); } From 47fffd37d1f973b0cd0f2e3bfd8f25740dd02e46 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 30 Jun 2023 13:53:31 +0800 Subject: [PATCH 130/171] renaming --- src/libs/actions/Link.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Link.js b/src/libs/actions/Link.js index fc29e70de5e0..b920cb1c7ee6 100644 --- a/src/libs/actions/Link.js +++ b/src/libs/actions/Link.js @@ -71,7 +71,7 @@ function openOldDotLink(url) { API.makeRequestWithSideEffects('OpenOldDotLink', {}, {}) .then((response) => buildOldDotURL(url, response.shortLivedAuthToken)) .catch(() => buildOldDotURL(url)), - (urlRes) => urlRes, + (oldDotURL) => oldDotURL, ); } From 0142e73e0110e102036095cf14651462c1159fda Mon Sep 17 00:00:00 2001 From: jeet-dhandha Date: Fri, 30 Jun 2023 15:25:19 +0530 Subject: [PATCH 131/171] fix: remove extra check from getReportAction --- src/libs/ReportActionsUtils.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index f79b6b53fc37..7e2a9164746b 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -433,9 +433,6 @@ function getLinkedTransactionID(reportID, reportActionID) { * @returns {Object} */ function getReportAction(reportID, reportActionID) { - if (!reportID || !reportActionID) { - return {}; - } return lodashGet(allReportActions, [reportID, reportActionID], {}); } From 5b030d23c16e2c4ad96cfb6c8b09292314ce6abc Mon Sep 17 00:00:00 2001 From: jeet-dhandha Date: Fri, 30 Jun 2023 15:26:10 +0530 Subject: [PATCH 132/171] fix: add empty check for report --- src/libs/actions/Report.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index a05ca26ef0df..f7f020c960d6 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1640,6 +1640,11 @@ function removeEmojiReaction(reportID, originalReportAction, emoji) { */ function toggleEmojiReaction(reportID, reportAction, emoji, paramSkinTone = preferredSkinTone) { const latestReportAction = ReportActionsUtils.getReportAction(reportID, reportAction.reportActionID); + + if(_.isEmpty(latestReportAction)) { + return; + } + const message = latestReportAction.message[0]; const reactionObject = message.reactions && _.find(message.reactions, (reaction) => reaction.emoji === emoji.name); const skinTone = emoji.types === undefined ? null : paramSkinTone; // only use skin tone if emoji supports it From 1702c675130046af43d46d679bb460cc8dd250fb Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Fri, 30 Jun 2023 15:57:50 +0530 Subject: [PATCH 133/171] fix: replaces ErrorBoundary with try-catch --- tests/utils/LHNTestUtils.js | 42 ++++++++++++------------------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/tests/utils/LHNTestUtils.js b/tests/utils/LHNTestUtils.js index 25df3fb01885..c00c69c3532a 100644 --- a/tests/utils/LHNTestUtils.js +++ b/tests/utils/LHNTestUtils.js @@ -168,37 +168,21 @@ function getAdvancedFakeReport(isArchived, isUserCreatedPolicyRoom, hasAddWorksp * @param {String} [currentReportID] */ function getDefaultRenderedSidebarLinks(currentReportID = '') { - // An ErrorBoundary needs to be added to the rendering so that any errors that happen while the component - // renders are logged to the console. Without an error boundary, Jest only reports the error like "The above error - // occurred in your component", except, there is no "above error". It's just swallowed up by Jest somewhere. - // With the ErrorBoundary, those errors are caught and logged to the console so you can find exactly which error - // might be causing a rendering issue when developing tests. - class ErrorBoundary extends React.Component { - // Error boundaries have to implement this method. It's for providing a fallback UI, but - // we don't need that for unit testing, so this is basically a no-op. - static getDerivedStateFromError(error) { - return {error}; - } + // A try-catch block needs to be added to the rendering so that any errors that happen while the component + // renders are caught and logged to the console. Without the try-catch block, Jest might only report the error + // as "The above error occurred in your component", without providing specific details. By using a try-catch block, + // any errors are caught and logged, allowing you to identify the exact error that might be causing a rendering issue + // when developing tests. - componentDidCatch(error, errorInfo) { - console.error(error, errorInfo); - } - - render() { - // eslint-disable-next-line react/prop-types - return this.props.children; - } + try { + // Wrap the SideBarLinks inside of LocaleContextProvider so that all the locale props + // are passed to the component. If this is not done, then all the locale props are missing + // and there are a lot of render warnings. It needs to be done like this because normally in + // our app (App.js) is when the react application is wrapped in the context providers + render(); + } catch (error) { + console.error(error); } - - // Wrap the SideBarLinks inside of LocaleContextProvider so that all the locale props - // are passed to the component. If this is not done, then all the locale props are missing - // and there are a lot of render warnings. It needs to be done like this because normally in - // our app (App.js) is when the react application is wrapped in the context providers - render( - - - , - ); } /** From fa98bfd9fd9dbf3e386b99e6d114d7e2fbc34789 Mon Sep 17 00:00:00 2001 From: Pujan Date: Fri, 30 Jun 2023 17:39:03 +0530 Subject: [PATCH 134/171] react-native-google-places-autocomplete version update --- package-lock.json | 12 ++++++------ package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 26d228989f42..6a5e7d56ebe8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,7 +69,7 @@ "react-native-fast-image": "^8.6.3", "react-native-fs": "^2.20.0", "react-native-gesture-handler": "2.9.0", - "react-native-google-places-autocomplete": "git+https://github.com/Expensify/react-native-google-places-autocomplete.git#6f436a06a3018cb49750bb110b89df75f6a865d5", + "react-native-google-places-autocomplete": "git+https://github.com/Expensify/react-native-google-places-autocomplete.git#ee87343c3e827ff7818abc71b6bb04fcc1f120e0", "react-native-haptic-feedback": "^1.13.0", "react-native-image-pan-zoom": "^2.1.12", "react-native-image-picker": "^5.1.0", @@ -36665,8 +36665,8 @@ }, "node_modules/react-native-google-places-autocomplete": { "version": "2.5.1", - "resolved": "git+ssh://git@github.com/Expensify/react-native-google-places-autocomplete.git#6f436a06a3018cb49750bb110b89df75f6a865d5", - "integrity": "sha512-7NiBK83VggJ2HQaHGfJoaPyxtiLu1chwP1VqH9te+PZtf0L9p50IuBQciW+4s173cBamt4U2+mvnCt7zfMFeDg==", + "resolved": "git+ssh://git@github.com/Expensify/react-native-google-places-autocomplete.git#ee87343c3e827ff7818abc71b6bb04fcc1f120e0", + "integrity": "sha512-OJWCz4Epj1p8tyNImWNykAqpd/X1MkNCFPY0dSbgiTJGbW4J5T4bC0PIUQ+ExjxWpWjcFaielTLdoSz0HfeIpw==", "license": "MIT", "dependencies": { "lodash.debounce": "^4.0.8", @@ -68460,9 +68460,9 @@ } }, "react-native-google-places-autocomplete": { - "version": "git+ssh://git@github.com/Expensify/react-native-google-places-autocomplete.git#6f436a06a3018cb49750bb110b89df75f6a865d5", - "integrity": "sha512-7NiBK83VggJ2HQaHGfJoaPyxtiLu1chwP1VqH9te+PZtf0L9p50IuBQciW+4s173cBamt4U2+mvnCt7zfMFeDg==", - "from": "react-native-google-places-autocomplete@git+https://github.com/Expensify/react-native-google-places-autocomplete.git#6f436a06a3018cb49750bb110b89df75f6a865d5", + "version": "git+ssh://git@github.com/Expensify/react-native-google-places-autocomplete.git#ee87343c3e827ff7818abc71b6bb04fcc1f120e0", + "integrity": "sha512-OJWCz4Epj1p8tyNImWNykAqpd/X1MkNCFPY0dSbgiTJGbW4J5T4bC0PIUQ+ExjxWpWjcFaielTLdoSz0HfeIpw==", + "from": "react-native-google-places-autocomplete@git+https://github.com/Expensify/react-native-google-places-autocomplete.git#ee87343c3e827ff7818abc71b6bb04fcc1f120e0", "requires": { "lodash.debounce": "^4.0.8", "prop-types": "^15.7.2", diff --git a/package.json b/package.json index 423bbd8bce78..d69efbf2cf75 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "react-native-fast-image": "^8.6.3", "react-native-fs": "^2.20.0", "react-native-gesture-handler": "2.9.0", - "react-native-google-places-autocomplete": "git+https://github.com/Expensify/react-native-google-places-autocomplete.git#6f436a06a3018cb49750bb110b89df75f6a865d5", + "react-native-google-places-autocomplete": "git+https://github.com/Expensify/react-native-google-places-autocomplete.git#ee87343c3e827ff7818abc71b6bb04fcc1f120e0", "react-native-haptic-feedback": "^1.13.0", "react-native-image-pan-zoom": "^2.1.12", "react-native-image-picker": "^5.1.0", From 3004da3c78b40dca621b7f8a9ce99808216e0902 Mon Sep 17 00:00:00 2001 From: Nishan Chhetri <52850000+nishancx@users.noreply.github.com> Date: Fri, 30 Jun 2023 23:07:13 +0545 Subject: [PATCH 135/171] Use underscore --- src/components/AddressSearch/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 34f96bd8745d..795e45c6f892 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -4,7 +4,6 @@ import PropTypes from 'prop-types'; import {LogBox, ScrollView, View} from 'react-native'; import {GooglePlacesAutocomplete} from 'react-native-google-places-autocomplete'; import lodashGet from 'lodash/get'; -import findKey from 'lodash/findKey'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import styles from '../../styles/styles'; import themeColors from '../../styles/themes/default'; @@ -149,7 +148,7 @@ function AddressSearch(props) { city: cityAutocompleteFallback = '', } = GooglePlacesUtils.getPlaceAutocompleteTerms(autocompleteData.terms); - const countryFallback = findKey(CONST.ALL_COUNTRIES, (country) => country === countryFallbackLongName); + const countryFallback = _.findKey(CONST.ALL_COUNTRIES, (country) => country === countryFallbackLongName); const country = countryPrimary || countryFallback; From 09e99d73209ed265d873b71f7dacf9c35dea9995 Mon Sep 17 00:00:00 2001 From: Pujan Date: Fri, 30 Jun 2023 23:22:51 +0530 Subject: [PATCH 136/171] corrected the add pymnt popover vertical position --- src/components/KYCWall/BaseKYCWall.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/KYCWall/BaseKYCWall.js b/src/components/KYCWall/BaseKYCWall.js index 956e1fd425c9..1d5f0f4fe06e 100644 --- a/src/components/KYCWall/BaseKYCWall.js +++ b/src/components/KYCWall/BaseKYCWall.js @@ -69,7 +69,7 @@ class KYCWall extends React.Component { } return { - anchorPositionVertical: domRect.top - 150, + anchorPositionVertical: domRect.top - 8, anchorPositionHorizontal: domRect.left, }; } From 2cc216ac354902db52400351497fa47f8f1f6047 Mon Sep 17 00:00:00 2001 From: 0xmiroslav Date: Fri, 30 Jun 2023 22:29:29 +0200 Subject: [PATCH 137/171] fix electron version diff --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index ead1e5ff9e1f..666ee8c3fb92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -144,7 +144,7 @@ "css-loader": "^6.7.2", "diff-so-fancy": "^1.3.0", "dotenv": "^16.0.3", - "electron": "^22.3.14", + "electron": "22.3.14", "electron-builder": "24.5.0", "eslint": "^7.6.0", "eslint-config-expensify": "^2.0.38", From c606b2e12b84fedf0d72ed869b588bc1293a9cfe Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Fri, 30 Jun 2023 15:29:16 -0700 Subject: [PATCH 138/171] moving its position so its easier to review --- src/libs/ReportUtils.js | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 864a27275b86..870aae1e4390 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -464,6 +464,27 @@ function isArchivedRoom(report) { return lodashGet(report, ['statusNum']) === CONST.REPORT.STATUS.CLOSED && lodashGet(report, ['stateNum']) === CONST.REPORT.STATE_NUM.SUBMITTED; } +/** + * Returns true if report has a parent + * + * @param {Object} report + * @returns {Boolean} + */ +function isThread(report) { + return Boolean(report && report.parentReportID && report.parentReportActionID); +} + +/** + * Returns true if report is of type chat and has a parent and is therefore a Thread. + * + * @param {Object} report + * @returns {Boolean} + */ +function isChatThread(report) { + return isThread(report) && report.type === CONST.REPORT.TYPE.CHAT; +} + + /** * Get the policy name from a given report * @param {Object} report @@ -532,26 +553,6 @@ function isPolicyExpenseChatAdmin(report, policies) { return policyRole === CONST.POLICY.ROLE.ADMIN; } -/** - * Returns true if report has a parent - * - * @param {Object} report - * @returns {Boolean} - */ -function isThread(report) { - return Boolean(report && report.parentReportID && report.parentReportActionID); -} - -/** - * Returns true if report is of type chat and has a parent and is therefore a Thread. - * - * @param {Object} report - * @returns {Boolean} - */ -function isChatThread(report) { - return isThread(report) && report.type === CONST.REPORT.TYPE.CHAT; -} - /** * Returns true if reportAction has a child. * From 268ad5726533a0bcb9b7b2ed87ba9e86af7c5bb9 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Fri, 30 Jun 2023 15:31:30 -0700 Subject: [PATCH 139/171] logic fix --- src/libs/ReportUtils.js | 43 ++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 870aae1e4390..5de8abb15ed6 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -377,6 +377,26 @@ function getBankAccountRoute(report) { return isPolicyExpenseChat(report) ? ROUTES.getBankAccountRoute('', report.policyID) : ROUTES.SETTINGS_ADD_BANK_ACCOUNT; } +/** + * Returns true if report has a parent + * + * @param {Object} report + * @returns {Boolean} + */ +function isThread(report) { + return Boolean(report && report.parentReportID && report.parentReportActionID); +} + +/** + * Returns true if report is of type chat and has a parent and is therefore a Thread. + * + * @param {Object} report + * @returns {Boolean} + */ +function isChatThread(report) { + return isThread(report) && report.type === CONST.REPORT.TYPE.CHAT; +} + /** * Only returns true if this is our main 1:1 DM report with Concierge * @@ -384,7 +404,7 @@ function getBankAccountRoute(report) { * @returns {Boolean} */ function isConciergeChatReport(report) { - return lodashGet(report, 'participantAccountIDs', []).length === 1 && Number(report.participantAccountIDs[0]) === CONST.ACCOUNT_ID.CONCIERGE && !isThread(report); + return lodashGet(report, 'participantAccountIDs', []).length === 1 && Number(report.participantAccountIDs[0]) === CONST.ACCOUNT_ID.CONCIERGE && !isChatThread(report); } /** @@ -464,27 +484,6 @@ function isArchivedRoom(report) { return lodashGet(report, ['statusNum']) === CONST.REPORT.STATUS.CLOSED && lodashGet(report, ['stateNum']) === CONST.REPORT.STATE_NUM.SUBMITTED; } -/** - * Returns true if report has a parent - * - * @param {Object} report - * @returns {Boolean} - */ -function isThread(report) { - return Boolean(report && report.parentReportID && report.parentReportActionID); -} - -/** - * Returns true if report is of type chat and has a parent and is therefore a Thread. - * - * @param {Object} report - * @returns {Boolean} - */ -function isChatThread(report) { - return isThread(report) && report.type === CONST.REPORT.TYPE.CHAT; -} - - /** * Get the policy name from a given report * @param {Object} report From c3089520a60dc7492b50ce3e42d46dc3c52fb396 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 1 Jul 2023 11:59:29 +0800 Subject: [PATCH 140/171] dismiss current screen --- src/libs/actions/Report.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 2f6f812bdbe3..bd62897de68e 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1316,6 +1316,8 @@ function deleteReport(reportID) { * @param {String} reportID The reportID of the policy report (workspace room) */ function navigateToConciergeChatAndDeleteReport(reportID) { + // Dismiss current screen + Navigation.goBack(); navigateToConciergeChat(); deleteReport(reportID); } From 40e752263aa058f558014820d3beeb6c55fc39cd Mon Sep 17 00:00:00 2001 From: Alex D Date: Sat, 1 Jul 2023 14:59:15 +0300 Subject: [PATCH 141/171] Component refactor: WorkspaceNewRoomPage --- src/pages/workspace/WorkspaceNewRoomPage.js | 166 +++++++++----------- 1 file changed, 78 insertions(+), 88 deletions(-) diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index 361610266b42..6b0ccc6b90ee 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useState,useCallback} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; import {withOnyx} from 'react-native-onyx'; @@ -65,43 +65,34 @@ const defaultProps = { allPolicyMembers: {}, }; -class WorkspaceNewRoomPage extends React.Component { - constructor(props) { - super(props); - - this.state = { - visibilityDescription: this.props.translate('newRoomPage.restrictedDescription'), - }; - - this.validate = this.validate.bind(this); - this.submit = this.submit.bind(this); - this.updateVisibilityDescription = this.updateVisibilityDescription.bind(this); - } +function WorkspaceNewRoomPage(props) { + const [visibilityDescription, setVisibilityDescription] = useState(props.translate('newRoomPage.restrictedDescription')); /** * @param {Object} values - form input values passed by the Form component */ - submit(values) { - const policyMembers = _.map(_.keys(this.props.allPolicyMembers[`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${values.policyID}`]), (accountID) => Number(accountID)); + const submit = (values) => { + const policyMembers = _.map(_.keys(props.allPolicyMembers[`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${values.policyID}`]), (accountID) => Number(accountID)); Report.addPolicyReport(values.policyID, values.roomName, values.visibility, policyMembers); - } + }; /** * @param {String} visibility - form input value passed by the Form component */ - updateVisibilityDescription(visibility) { - const visibilityDescription = this.props.translate(`newRoomPage.${visibility}Description`); - if (visibilityDescription === this.state.visibilityDescription) { + const updateVisibilityDescription = useCallback((visibility) => { + const newVisibilityDescription = props.translate(`newRoomPage.${visibility}Description`); + if (newVisibilityDescription === visibilityDescription) { return; } - this.setState({visibilityDescription}); - } + setVisibilityDescription({newVisibilityDescription}); + // eslint-disable-next-line react-hooks/exhaustive-deps + },[visibilityDescription]); /** * @param {Object} values - form input values passed by the Form component * @returns {Boolean} */ - validate(values) { + const validate = useCallback((values) => { const errors = {}; if (!values.roomName || values.roomName === CONST.POLICY.ROOM_PREFIX) { @@ -113,7 +104,7 @@ class WorkspaceNewRoomPage extends React.Component { } else if (ValidationUtils.isReservedRoomName(values.roomName)) { // Certain names are reserved for default rooms and should not be used for policy rooms. ErrorUtils.addErrorMessage(errors, 'roomName', ['newRoomPage.roomNameReservedError', {reservedName: values.roomName}]); - } else if (ValidationUtils.isExistingRoomName(values.roomName, this.props.reports, values.policyID)) { + } else if (ValidationUtils.isExistingRoomName(values.roomName, props.reports, values.policyID)) { // Certain names are reserved for default rooms and should not be used for policy rooms. ErrorUtils.addErrorMessage(errors, 'roomName', 'newRoomPage.roomAlreadyExistsError'); } @@ -123,78 +114,77 @@ class WorkspaceNewRoomPage extends React.Component { } return errors; - } + },[props.reports]); - render() { - if (!Permissions.canUsePolicyRooms(this.props.betas)) { - Log.info('Not showing create Policy Room page since user is not on policy rooms beta'); - Navigation.dismissModal(); - return null; - } + if (!Permissions.canUsePolicyRooms(props.betas)) { + Log.info('Not showing create Policy Room page since user is not on policy rooms beta'); + Navigation.dismissModal(); + return null; + } - // Workspaces are policies with type === 'free' - const workspaceOptions = _.map( - _.filter(this.props.policies, (policy) => policy && policy.type === CONST.POLICY.TYPE.FREE), - (policy) => ({label: policy.name, key: policy.id, value: policy.id}), - ); - - const visibilityOptions = _.map( - _.filter(_.values(CONST.REPORT.VISIBILITY), (visibilityOption) => visibilityOption !== CONST.REPORT.VISIBILITY.PUBLIC_ANNOUNCE), - (visibilityOption) => ({ - label: this.props.translate(`newRoomPage.visibilityOptions.${visibilityOption}`), - value: visibilityOption, - description: this.props.translate(`newRoomPage.${visibilityOption}Description`), - }), - ); - - return ( - policy && policy.type === CONST.POLICY.TYPE.FREE), + (policy) => ({label: policy.name, key: policy.id, value: policy.id}), + ); + + const visibilityOptions = _.map( + _.filter(_.values(CONST.REPORT.VISIBILITY), (visibilityOption) => visibilityOption !== CONST.REPORT.VISIBILITY.PUBLIC_ANNOUNCE), + (visibilityOption) => ({ + label: props.translate(`newRoomPage.visibilityOptions.${visibilityOption}`), + value: visibilityOption, + description: props.translate(`newRoomPage.${visibilityOption}Description`), + }), + ); + + return ( + + +
- - - - - - - - - - - - {this.state.visibilityDescription} - -
- ); - } + + + + + + + + + + {visibilityDescription} + +
+ ); } WorkspaceNewRoomPage.propTypes = propTypes; WorkspaceNewRoomPage.defaultProps = defaultProps; +WorkspaceNewRoomPage.displayName = 'WorkspaceNewRoomPage'; export default compose( withOnyx({ From 874ad668e9307f56126608b77811bbbc5bd2d599 Mon Sep 17 00:00:00 2001 From: Alex D Date: Sat, 1 Jul 2023 16:04:09 +0300 Subject: [PATCH 142/171] Fix: changes after prettier run --- src/pages/workspace/WorkspaceNewRoomPage.js | 70 +++++++++++---------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index 6b0ccc6b90ee..b69ef0e2b09d 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -1,4 +1,4 @@ -import React, {useState,useCallback} from 'react'; +import React, {useState, useCallback} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; import {withOnyx} from 'react-native-onyx'; @@ -79,42 +79,48 @@ function WorkspaceNewRoomPage(props) { /** * @param {String} visibility - form input value passed by the Form component */ - const updateVisibilityDescription = useCallback((visibility) => { - const newVisibilityDescription = props.translate(`newRoomPage.${visibility}Description`); - if (newVisibilityDescription === visibilityDescription) { - return; - } - setVisibilityDescription({newVisibilityDescription}); - // eslint-disable-next-line react-hooks/exhaustive-deps - },[visibilityDescription]); + const updateVisibilityDescription = useCallback( + (visibility) => { + const newVisibilityDescription = props.translate(`newRoomPage.${visibility}Description`); + if (newVisibilityDescription === visibilityDescription) { + return; + } + setVisibilityDescription({newVisibilityDescription}); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, + [visibilityDescription], + ); /** * @param {Object} values - form input values passed by the Form component * @returns {Boolean} */ - const validate = useCallback((values) => { - const errors = {}; - - if (!values.roomName || values.roomName === CONST.POLICY.ROOM_PREFIX) { - // We error if the user doesn't enter a room name or left blank - ErrorUtils.addErrorMessage(errors, 'roomName', 'newRoomPage.pleaseEnterRoomName'); - } else if (values.roomName !== CONST.POLICY.ROOM_PREFIX && !ValidationUtils.isValidRoomName(values.roomName)) { - // We error if the room name has invalid characters - ErrorUtils.addErrorMessage(errors, 'roomName', 'newRoomPage.roomNameInvalidError'); - } else if (ValidationUtils.isReservedRoomName(values.roomName)) { - // Certain names are reserved for default rooms and should not be used for policy rooms. - ErrorUtils.addErrorMessage(errors, 'roomName', ['newRoomPage.roomNameReservedError', {reservedName: values.roomName}]); - } else if (ValidationUtils.isExistingRoomName(values.roomName, props.reports, values.policyID)) { - // Certain names are reserved for default rooms and should not be used for policy rooms. - ErrorUtils.addErrorMessage(errors, 'roomName', 'newRoomPage.roomAlreadyExistsError'); - } - - if (!values.policyID) { - errors.policyID = 'newRoomPage.pleaseSelectWorkspace'; - } - - return errors; - },[props.reports]); + const validate = useCallback( + (values) => { + const errors = {}; + + if (!values.roomName || values.roomName === CONST.POLICY.ROOM_PREFIX) { + // We error if the user doesn't enter a room name or left blank + ErrorUtils.addErrorMessage(errors, 'roomName', 'newRoomPage.pleaseEnterRoomName'); + } else if (values.roomName !== CONST.POLICY.ROOM_PREFIX && !ValidationUtils.isValidRoomName(values.roomName)) { + // We error if the room name has invalid characters + ErrorUtils.addErrorMessage(errors, 'roomName', 'newRoomPage.roomNameInvalidError'); + } else if (ValidationUtils.isReservedRoomName(values.roomName)) { + // Certain names are reserved for default rooms and should not be used for policy rooms. + ErrorUtils.addErrorMessage(errors, 'roomName', ['newRoomPage.roomNameReservedError', {reservedName: values.roomName}]); + } else if (ValidationUtils.isExistingRoomName(values.roomName, props.reports, values.policyID)) { + // Certain names are reserved for default rooms and should not be used for policy rooms. + ErrorUtils.addErrorMessage(errors, 'roomName', 'newRoomPage.roomAlreadyExistsError'); + } + + if (!values.policyID) { + errors.policyID = 'newRoomPage.pleaseSelectWorkspace'; + } + + return errors; + }, + [props.reports], + ); if (!Permissions.canUsePolicyRooms(props.betas)) { Log.info('Not showing create Policy Room page since user is not on policy rooms beta'); From 8f5236317d9c2db612ce206ce1131a76e70563b2 Mon Sep 17 00:00:00 2001 From: Alex D Date: Sat, 1 Jul 2023 16:32:50 +0300 Subject: [PATCH 143/171] Fix lint issue after prettier run --- src/pages/workspace/WorkspaceNewRoomPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index b69ef0e2b09d..688bbac02495 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -86,8 +86,8 @@ function WorkspaceNewRoomPage(props) { return; } setVisibilityDescription({newVisibilityDescription}); - // eslint-disable-next-line react-hooks/exhaustive-deps }, + // eslint-disable-next-line react-hooks/exhaustive-deps [visibilityDescription], ); From b6b43c62e374a4e529fdde927889ebccfcca2c25 Mon Sep 17 00:00:00 2001 From: Davi Rodrigues Date: Sat, 1 Jul 2023 20:57:35 -0300 Subject: [PATCH 144/171] fix: change subscript avatar border styles --- src/components/Avatar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Avatar.js b/src/components/Avatar.js index fa7d17d22535..48d24744438f 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -73,7 +73,7 @@ function Avatar(props) { const isWorkspace = props.type === CONST.ICON_TYPE_WORKSPACE; const iconSize = StyleUtils.getAvatarSize(props.size); - const imageStyle = props.imageStyles && props.imageStyles.length > 0 ? [StyleUtils.getAvatarStyle(props.size), ...props.imageStyles, StyleUtils.getAvatarBorderRadius(props.size, props.type)] : [StyleUtils.getAvatarStyle(props.size), StyleUtils.getAvatarBorderRadius(props.size, props.type)]; + const imageStyle = props.imageStyles && props.imageStyles.length > 0 ? [StyleUtils.getAvatarStyle(props.size), ...props.imageStyles, StyleUtils.getAvatarBorderRadius(props.size, props.type)] : [StyleUtils.getAvatarStyle(props.size), { borderRadius: 0 }]; const iconStyle = props.imageStyles && props.imageStyles.length > 0 ? [StyleUtils.getAvatarStyle(props.size), styles.bgTransparent, ...props.imageStyles] : undefined; @@ -101,7 +101,7 @@ function Avatar(props) { />
) : ( - + Date: Sun, 2 Jul 2023 17:45:12 +0800 Subject: [PATCH 145/171] update comment --- 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 bd62897de68e..60e83f6ebec5 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1316,7 +1316,7 @@ function deleteReport(reportID) { * @param {String} reportID The reportID of the policy report (workspace room) */ function navigateToConciergeChatAndDeleteReport(reportID) { - // Dismiss current screen + // Dismiss the current report screen and replace it with Concierge Chat Navigation.goBack(); navigateToConciergeChat(); deleteReport(reportID); From 8db7706a8f1bb04eda424478c08e975d4e9545cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Sun, 2 Jul 2023 13:54:31 +0200 Subject: [PATCH 146/171] Repeat: https://github.com/Expensify/App/commit/f71a1f7c8bda15eab014f88d7575048819971a77 --- src/components/TextInput/BaseTextInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index a2893347be52..85d2b1b992c1 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -28,7 +28,7 @@ function BaseTextInput(props) { const [isFocused, setIsFocused] = useState(false); const [passwordHidden, setPasswordHidden] = useState(props.secureTextEntry); const [textInputWidth, setTextInputWidth] = useState(0); - const [textInputHeight, setTextInputHeight] = useState(); + const [textInputHeight, setTextInputHeight] = useState(0); const [prefixWidth, setPrefixWidth] = useState(0); const [height, setHeight] = useState(variables.componentSizeLarge); const [width, setWidth] = useState(); From 74dc4d006f6499af8c217b9e591cfc2907acd950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Sun, 2 Jul 2023 13:55:27 +0200 Subject: [PATCH 147/171] Repeat: https://github.com/Expensify/App/commit/a63eee4b177d4f57754baeab47fb58836592fb76#diff-511655d817607a5a2b12918a659da08129dbf4e74dc693f9a93f8ac9df7eb5ef --- src/components/TextInput/BaseTextInput.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 85d2b1b992c1..2522798bcc8f 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -350,6 +350,7 @@ function BaseTextInput(props) { style={styles.textInputIconContainer} onPress={togglePasswordVisibility} onMouseDown={(e) => e.preventDefault()} + accessibilityLabel={props.translate('common.visible')} > Date: Sun, 2 Jul 2023 11:29:37 -0300 Subject: [PATCH 148/171] fix: remove inline styles --- src/components/Avatar.js | 2 +- src/styles/styles.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/Avatar.js b/src/components/Avatar.js index 48d24744438f..3bec55586444 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -73,7 +73,7 @@ function Avatar(props) { const isWorkspace = props.type === CONST.ICON_TYPE_WORKSPACE; const iconSize = StyleUtils.getAvatarSize(props.size); - const imageStyle = props.imageStyles && props.imageStyles.length > 0 ? [StyleUtils.getAvatarStyle(props.size), ...props.imageStyles, StyleUtils.getAvatarBorderRadius(props.size, props.type)] : [StyleUtils.getAvatarStyle(props.size), { borderRadius: 0 }]; + const imageStyle = props.imageStyles && props.imageStyles.length > 0 ? [StyleUtils.getAvatarStyle(props.size), ...props.imageStyles, StyleUtils.getAvatarBorderRadius(props.size, props.type)] : [StyleUtils.getAvatarStyle(props.size), styles.br0]; const iconStyle = props.imageStyles && props.imageStyles.length > 0 ? [StyleUtils.getAvatarStyle(props.size), styles.bgTransparent, ...props.imageStyles] : undefined; diff --git a/src/styles/styles.js b/src/styles/styles.js index 74066af5e20a..2c9928bd1a6f 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -742,6 +742,10 @@ const styles = { borderColor: themeColors.border, }, + br0: { + borderRadius: 0, + }, + borderColorFocus: { borderColor: themeColors.borderFocus, }, From 2d281be897b05e8fe64a91b8294f31919c8b6a3a Mon Sep 17 00:00:00 2001 From: Aleksei Dvoretskii Date: Sun, 2 Jul 2023 13:51:07 -0700 Subject: [PATCH 149/171] Update src/pages/workspace/WorkspaceNewRoomPage.js Co-authored-by: Fedi Rajhi --- src/pages/workspace/WorkspaceNewRoomPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index 688bbac02495..3e303e8cb376 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -153,7 +153,7 @@ function WorkspaceNewRoomPage(props) { formID={ONYXKEYS.FORMS.NEW_ROOM_FORM} submitButtonText={props.translate('newRoomPage.createRoom')} scrollContextEnabled - style={[styles.mh5, styles.mt5, styles.flexGrow1]} + style={[styles.mh5, styles.flexGrow1]} validate={validate} onSubmit={submit} enabledWhenOffline From 1aa2452b1c9e47ef12a9947d2a2b9c3f2abb1a38 Mon Sep 17 00:00:00 2001 From: Alex D Date: Mon, 3 Jul 2023 01:08:45 +0300 Subject: [PATCH 150/171] Add useLocalize, useMemo, minor fixes --- src/pages/workspace/WorkspaceNewRoomPage.js | 107 +++++++++----------- 1 file changed, 48 insertions(+), 59 deletions(-) diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index 3e303e8cb376..c05430300b76 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -1,11 +1,10 @@ -import React, {useState, useCallback} from 'react'; +import React, {useState, useCallback, useMemo} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; import * as Report from '../../libs/actions/Report'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; -import compose from '../../libs/compose'; +import useLocalize from '../../hooks/useLocalize'; import HeaderWithBackButton from '../../components/HeaderWithBackButton'; import Navigation from '../../libs/Navigation/Navigation'; import ScreenWrapper from '../../components/ScreenWrapper'; @@ -55,8 +54,6 @@ const propTypes = { /** A collection of objects for all policies which key policy member objects by accountIDs */ allPolicyMembers: PropTypes.objectOf(PropTypes.objectOf(policyMemberPropType)), - - ...withLocalizePropTypes, }; const defaultProps = { betas: [], @@ -66,7 +63,9 @@ const defaultProps = { }; function WorkspaceNewRoomPage(props) { - const [visibilityDescription, setVisibilityDescription] = useState(props.translate('newRoomPage.restrictedDescription')); + const {translate} = useLocalize(); + const [visibility, setVisibility] = useState(CONST.REPORT.VISIBILITY.RESTRICTED); + const visibilityDescription = useMemo(() => translate(`newRoomPage.${visibility}Description`), [translate, visibility]); /** * @param {Object} values - form input values passed by the Form component @@ -76,21 +75,6 @@ function WorkspaceNewRoomPage(props) { Report.addPolicyReport(values.policyID, values.roomName, values.visibility, policyMembers); }; - /** - * @param {String} visibility - form input value passed by the Form component - */ - const updateVisibilityDescription = useCallback( - (visibility) => { - const newVisibilityDescription = props.translate(`newRoomPage.${visibility}Description`); - if (newVisibilityDescription === visibilityDescription) { - return; - } - setVisibilityDescription({newVisibilityDescription}); - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [visibilityDescription], - ); - /** * @param {Object} values - form input values passed by the Form component * @returns {Boolean} @@ -122,36 +106,44 @@ function WorkspaceNewRoomPage(props) { [props.reports], ); + // Workspaces are policies with type === 'free' + const workspaceOptions = useMemo( + () => + _.map( + _.filter(props.policies, (policy) => policy && policy.type === CONST.POLICY.TYPE.FREE), + (policy) => ({label: policy.name, key: policy.id, value: policy.id}), + ), + [props.policies], + ); + + const visibilityOptions = useMemo( + () => + _.map( + _.filter(_.values(CONST.REPORT.VISIBILITY), (visibilityOption) => visibilityOption !== CONST.REPORT.VISIBILITY.PUBLIC_ANNOUNCE), + (visibilityOption) => ({ + label: translate(`newRoomPage.visibilityOptions.${visibilityOption}`), + value: visibilityOption, + description: translate(`newRoomPage.${visibilityOption}Description`), + }), + ), + [translate], + ); + if (!Permissions.canUsePolicyRooms(props.betas)) { Log.info('Not showing create Policy Room page since user is not on policy rooms beta'); Navigation.dismissModal(); return null; } - // Workspaces are policies with type === 'free' - const workspaceOptions = _.map( - _.filter(props.policies, (policy) => policy && policy.type === CONST.POLICY.TYPE.FREE), - (policy) => ({label: policy.name, key: policy.id, value: policy.id}), - ); - - const visibilityOptions = _.map( - _.filter(_.values(CONST.REPORT.VISIBILITY), (visibilityOption) => visibilityOption !== CONST.REPORT.VISIBILITY.PUBLIC_ANNOUNCE), - (visibilityOption) => ({ - label: props.translate(`newRoomPage.visibilityOptions.${visibilityOption}`), - value: visibilityOption, - description: props.translate(`newRoomPage.${visibilityOption}Description`), - }), - ); - return ( - +
@@ -192,20 +184,17 @@ WorkspaceNewRoomPage.propTypes = propTypes; WorkspaceNewRoomPage.defaultProps = defaultProps; WorkspaceNewRoomPage.displayName = 'WorkspaceNewRoomPage'; -export default compose( - withOnyx({ - betas: { - key: ONYXKEYS.BETAS, - }, - policies: { - key: ONYXKEYS.COLLECTION.POLICY, - }, - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, - allPolicyMembers: { - key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, - }, - }), - withLocalize, -)(WorkspaceNewRoomPage); +export default withOnyx({ + betas: { + key: ONYXKEYS.BETAS, + }, + policies: { + key: ONYXKEYS.COLLECTION.POLICY, + }, + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + allPolicyMembers: { + key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, + }, +})(WorkspaceNewRoomPage); From 208cce7029c5979c63293c9664300d981ac7cf63 Mon Sep 17 00:00:00 2001 From: Aleksei Dvoretskii Date: Sun, 2 Jul 2023 17:56:53 -0700 Subject: [PATCH 151/171] Update src/pages/workspace/WorkspaceNewRoomPage.js Co-authored-by: Fedi Rajhi --- src/pages/workspace/WorkspaceNewRoomPage.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index c05430300b76..255ffb5b9e3a 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -129,9 +129,15 @@ function WorkspaceNewRoomPage(props) { [translate], ); - if (!Permissions.canUsePolicyRooms(props.betas)) { + useEffect(() => { + if (Permissions.canUsePolicyRooms(props.betas)) { + return; + } Log.info('Not showing create Policy Room page since user is not on policy rooms beta'); Navigation.dismissModal(); + }, [props.betas]); + + if (!Permissions.canUsePolicyRooms(props.betas)) { return null; } From a1848cbaf395db9a4b8d32c33a2af6a2c2e9edaa Mon Sep 17 00:00:00 2001 From: Alex D Date: Mon, 3 Jul 2023 04:01:13 +0300 Subject: [PATCH 152/171] Fix missing import --- src/pages/workspace/WorkspaceNewRoomPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index 255ffb5b9e3a..c59b6687a809 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -1,4 +1,4 @@ -import React, {useState, useCallback, useMemo} from 'react'; +import React, {useState, useCallback, useMemo, useEffect} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; import {withOnyx} from 'react-native-onyx'; From 190c0308314300b2fa2f3a377ba0467064353fce Mon Sep 17 00:00:00 2001 From: Hans Date: Mon, 3 Jul 2023 17:44:53 +0700 Subject: [PATCH 153/171] prevent user with no permission from flag comment --- src/libs/ReportUtils.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 4ddbf032867c..a3c8d859aa05 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -20,6 +20,7 @@ import isReportMessageAttachment from './isReportMessageAttachment'; import * as defaultWorkspaceAvatars from '../components/Icon/WorkspaceDefaultAvatars'; import * as CurrencyUtils from './CurrencyUtils'; import * as UserUtils from './UserUtils'; +import * as ReportUtils from './ReportUtils'; let currentUserEmail; let currentUserAccountID; @@ -218,14 +219,16 @@ function canEditReportAction(reportAction) { * - It's an ADDCOMMENT that is not an attachment * * @param {Object} reportAction + * @param {number} reportID * @returns {Boolean} */ -function canFlagReportAction(reportAction) { +function canFlagReportAction(reportAction, reportID) { return ( !loginList.includes(reportAction.actorEmail) && reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && !ReportActionsUtils.isDeletedAction(reportAction) && - !ReportActionsUtils.isCreatedTaskReportAction(reportAction) + !ReportActionsUtils.isCreatedTaskReportAction(reportAction) && + ReportUtils.isAllowedToComment(ReportUtils.getReport(reportID)) ); } From 08f3d3a9192592cab17d5a02eae830f7d8c1dea1 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Mon, 3 Jul 2023 14:15:11 +0000 Subject: [PATCH 154/171] Update version to 1.3.36-0 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 4 ++-- ios/NewExpensifyTests/Info.plist | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 52e983a1ce82..98f2f103b119 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -106,8 +106,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001033505 - versionName "1.3.35-5" + versionCode 1001033600 + versionName "1.3.36-0" } splits { diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 7ae3b26627ee..8258d8c03b61 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3.35 + 1.3.36 CFBundleSignature ???? CFBundleURLTypes @@ -32,7 +32,7 @@ CFBundleVersion - 1.3.35.5 + 1.3.36.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 035f8ebde8d6..ca3b9da2e100 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.3.35 + 1.3.36 CFBundleSignature ???? CFBundleVersion - 1.3.35.5 + 1.3.36.0 diff --git a/package-lock.json b/package-lock.json index 67d2cd51360d..4f0ec5f929b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.35-5", + "version": "1.3.36-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.35-5", + "version": "1.3.36-0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 5188fbc1e20b..89b19f59fab2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.35-5", + "version": "1.3.36-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 8b7a65dcd36fa2b47ffb39c69f2cb294b94a7171 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Mon, 3 Jul 2023 14:54:30 +0000 Subject: [PATCH 155/171] Update version to 1.3.36-1 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 98f2f103b119..cdba607eb552 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -106,8 +106,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001033600 - versionName "1.3.36-0" + versionCode 1001033601 + versionName "1.3.36-1" } splits { diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 8258d8c03b61..b1b1af68a12d 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -32,7 +32,7 @@ CFBundleVersion - 1.3.36.0 + 1.3.36.1 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index ca3b9da2e100..6a08952a8c6b 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.3.36.0 + 1.3.36.1 diff --git a/package-lock.json b/package-lock.json index 4f0ec5f929b4..00792ab49fe4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.36-0", + "version": "1.3.36-1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.36-0", + "version": "1.3.36-1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 89b19f59fab2..4fd81037181a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.36-0", + "version": "1.3.36-1", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 310ca01ddfd7dac66797c288e0eaf0de2c7fb7c0 Mon Sep 17 00:00:00 2001 From: Hans Date: Mon, 3 Jul 2023 22:23:07 +0700 Subject: [PATCH 156/171] fix lint error --- src/libs/ReportUtils.js | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index a3c8d859aa05..7e38dd80f4e6 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -20,7 +20,6 @@ import isReportMessageAttachment from './isReportMessageAttachment'; import * as defaultWorkspaceAvatars from '../components/Icon/WorkspaceDefaultAvatars'; import * as CurrencyUtils from './CurrencyUtils'; import * as UserUtils from './UserUtils'; -import * as ReportUtils from './ReportUtils'; let currentUserEmail; let currentUserAccountID; @@ -212,26 +211,6 @@ function canEditReportAction(reportAction) { ); } -/** - * Can only flag if: - * - * - It was written by someone else - * - It's an ADDCOMMENT that is not an attachment - * - * @param {Object} reportAction - * @param {number} reportID - * @returns {Boolean} - */ -function canFlagReportAction(reportAction, reportID) { - return ( - !loginList.includes(reportAction.actorEmail) && - reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && - !ReportActionsUtils.isDeletedAction(reportAction) && - !ReportActionsUtils.isCreatedTaskReportAction(reportAction) && - ReportUtils.isAllowedToComment(ReportUtils.getReport(reportID)) - ); -} - /** * Whether the Money Request report is settled * @@ -2359,6 +2338,26 @@ function getOriginalReportID(reportID, reportAction) { return isThreadFirstChat(reportAction, reportID) ? lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, 'parentReportID']) : reportID; } +/** + * Can only flag if: + * + * - It was written by someone else + * - It's an ADDCOMMENT that is not an attachment + * + * @param {Object} reportAction + * @param {number} reportID + * @returns {Boolean} + */ +function canFlagReportAction(reportAction, reportID) { + return ( + !loginList.includes(reportAction.actorEmail) && + reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && + !ReportActionsUtils.isDeletedAction(reportAction) && + !ReportActionsUtils.isCreatedTaskReportAction(reportAction) && + isAllowedToComment(getReport(reportID)) + ); +} + export { getReportParticipantsTitle, isReportMessageAttachment, From 5cc07e007396b41d63f5b7703213a78fd929fa1d Mon Sep 17 00:00:00 2001 From: Hans Date: Mon, 3 Jul 2023 22:33:58 +0700 Subject: [PATCH 157/171] fix lint --- src/libs/ReportUtils.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 7e38dd80f4e6..2c3998e23b34 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2070,6 +2070,26 @@ function chatIncludesChronos(report) { return report.participantAccountIDs && _.contains(report.participantAccountIDs, CONST.ACCOUNT_ID.CHRONOS); } +/** + * Can only flag if: + * + * - It was written by someone else + * - It's an ADDCOMMENT that is not an attachment + * + * @param {Object} reportAction + * @param {number} reportID + * @returns {Boolean} + */ +function canFlagReportAction(reportAction, reportID) { + return ( + !loginList.includes(reportAction.actorEmail) && + reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && + !ReportActionsUtils.isDeletedAction(reportAction) && + !ReportActionsUtils.isCreatedTaskReportAction(reportAction) && + isAllowedToComment(getReport(reportID)) + ); +} + /** * Whether flag comment page should show * @@ -2338,26 +2358,6 @@ function getOriginalReportID(reportID, reportAction) { return isThreadFirstChat(reportAction, reportID) ? lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, 'parentReportID']) : reportID; } -/** - * Can only flag if: - * - * - It was written by someone else - * - It's an ADDCOMMENT that is not an attachment - * - * @param {Object} reportAction - * @param {number} reportID - * @returns {Boolean} - */ -function canFlagReportAction(reportAction, reportID) { - return ( - !loginList.includes(reportAction.actorEmail) && - reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && - !ReportActionsUtils.isDeletedAction(reportAction) && - !ReportActionsUtils.isCreatedTaskReportAction(reportAction) && - isAllowedToComment(getReport(reportID)) - ); -} - export { getReportParticipantsTitle, isReportMessageAttachment, From 96abeac37511f83fbb4aa90895e6f69c20667583 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Mon, 3 Jul 2023 18:41:32 +0200 Subject: [PATCH 158/171] Revert "fix: add outline to checkbox when focused via button" --- web/index.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/web/index.html b/web/index.html index ec1250b23b08..90011c87e7a9 100644 --- a/web/index.html +++ b/web/index.html @@ -60,10 +60,6 @@ outline: 0; box-shadow: inset 0px 0px 0px 1px #5AB0FF; } - div[role="checkbox"]:focus { - outline: 0; - box-shadow: inset 0px 0px 0px 1px #5AB0FF; - } input:focus-visible, input:focus[data-focusvisible-polyfill], select:focus-visible, select:focus[data-focusvisible-polyfill] { box-shadow: none; From f8306e3e058d3da8846de059e02e682aec99cc5a Mon Sep 17 00:00:00 2001 From: OSBotify Date: Mon, 3 Jul 2023 17:18:59 +0000 Subject: [PATCH 159/171] Update version to 1.3.36-2 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index cdba607eb552..12f144c4daa8 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -106,8 +106,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001033601 - versionName "1.3.36-1" + versionCode 1001033602 + versionName "1.3.36-2" } splits { diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index b1b1af68a12d..867e9778ddba 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -32,7 +32,7 @@ CFBundleVersion - 1.3.36.1 + 1.3.36.2 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 6a08952a8c6b..e9f95bc6a1dc 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.3.36.1 + 1.3.36.2 diff --git a/package-lock.json b/package-lock.json index 00792ab49fe4..317ea0548706 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.36-1", + "version": "1.3.36-2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.36-1", + "version": "1.3.36-2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 4fd81037181a..0006964c3a80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.36-1", + "version": "1.3.36-2", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 3b6900cf77d66c8722cd16e6e68e8133ed321249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 3 Jul 2023 19:29:12 +0200 Subject: [PATCH 160/171] Update src/components/TextInput/BaseTextInput.js Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- src/components/TextInput/BaseTextInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 2522798bcc8f..c9c61207f81e 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -252,7 +252,7 @@ function BaseTextInput(props) { style={[props.autoGrowHeight && styles.autoGrowHeightInputContainer(textInputHeight, maxHeight), !isMultiline && styles.componentHeightLarge, ...props.containerStyles]} > Date: Mon, 3 Jul 2023 23:00:19 +0530 Subject: [PATCH 161/171] fix: requested changes --- src/libs/ReportActionsUtils.js | 7 ------- src/libs/actions/Report.js | 14 +++++++------- .../home/report/ContextMenu/ContextMenuActions.js | 2 +- src/pages/home/report/ReportActionItem.js | 2 +- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 7e2a9164746b..fc6ac92dadb5 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -425,13 +425,6 @@ function getLinkedTransactionID(reportID, reportActionID) { return reportAction.originalMessage.IOUTransactionID; } -/** - * Returns the parentReportAction if the given report is a thread/task. - * - * @param {String} reportID - * @param {String} reportActionID - * @returns {Object} - */ function getReportAction(reportID, reportActionID) { return lodashGet(allReportActions, [reportID, reportActionID], {}); } diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index f7f020c960d6..aabec697e1aa 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1633,27 +1633,27 @@ function removeEmojiReaction(reportID, originalReportAction, emoji) { /** * Calls either addEmojiReaction or removeEmojiReaction depending on if the current user has reacted to the report action. * @param {String} reportID - * @param {Object} reportAction + * @param {String} reportActionID * @param {Object} emoji * @param {number} paramSkinTone * @returns {Promise} */ -function toggleEmojiReaction(reportID, reportAction, emoji, paramSkinTone = preferredSkinTone) { - const latestReportAction = ReportActionsUtils.getReportAction(reportID, reportAction.reportActionID); +function toggleEmojiReaction(reportID, reportActionID, emoji, paramSkinTone = preferredSkinTone) { + const reportAction = ReportActionsUtils.getReportAction(reportID, reportActionID); - if(_.isEmpty(latestReportAction)) { + if(_.isEmpty(reportAction)) { return; } - const message = latestReportAction.message[0]; + const message = reportAction.message[0]; const reactionObject = message.reactions && _.find(message.reactions, (reaction) => reaction.emoji === emoji.name); const skinTone = emoji.types === undefined ? null : paramSkinTone; // only use skin tone if emoji supports it if (reactionObject) { if (hasAccountIDReacted(currentUserAccountID, reactionObject.users, skinTone)) { - return removeEmojiReaction(reportID, latestReportAction, emoji, skinTone); + return removeEmojiReaction(reportID, reportAction, emoji, skinTone); } } - return addEmojiReaction(reportID, latestReportAction, emoji, skinTone); + return addEmojiReaction(reportID, reportAction, emoji, skinTone); } /** diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 0a0388e414c7..b05c29804d19 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -58,7 +58,7 @@ export default [ }; const onEmojiSelected = (emoji) => { - Report.toggleEmojiReaction(reportID, reportAction, emoji); + Report.toggleEmojiReaction(reportID, reportAction.reportActionID, emoji); closeContextMenu(); }; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 926774dfc755..c8cf023a6138 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -207,7 +207,7 @@ function ReportActionItem(props) { const toggleReaction = useCallback( (emoji) => { - Report.toggleEmojiReaction(props.report.reportID, props.action, emoji); + Report.toggleEmojiReaction(props.report.reportID, props.action.reportActionID, emoji); }, [props.report, props.action], ); From e9040559e7f35f12ca26d4cacd8cb40ca237e42e Mon Sep 17 00:00:00 2001 From: Davi Rodrigues Date: Mon, 3 Jul 2023 14:39:43 -0300 Subject: [PATCH 162/171] fix(lint): add prettier changes --- src/components/Avatar.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/Avatar.js b/src/components/Avatar.js index 3bec55586444..740e21bf5cce 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -73,7 +73,10 @@ function Avatar(props) { const isWorkspace = props.type === CONST.ICON_TYPE_WORKSPACE; const iconSize = StyleUtils.getAvatarSize(props.size); - const imageStyle = props.imageStyles && props.imageStyles.length > 0 ? [StyleUtils.getAvatarStyle(props.size), ...props.imageStyles, StyleUtils.getAvatarBorderRadius(props.size, props.type)] : [StyleUtils.getAvatarStyle(props.size), styles.br0]; + const imageStyle = + props.imageStyles && props.imageStyles.length > 0 + ? [StyleUtils.getAvatarStyle(props.size), ...props.imageStyles, StyleUtils.getAvatarBorderRadius(props.size, props.type)] + : [StyleUtils.getAvatarStyle(props.size), styles.br0]; const iconStyle = props.imageStyles && props.imageStyles.length > 0 ? [StyleUtils.getAvatarStyle(props.size), styles.bgTransparent, ...props.imageStyles] : undefined; From e835c839affb69ac36c614c61b2663339c6ac07c Mon Sep 17 00:00:00 2001 From: Davi Rodrigues Date: Mon, 3 Jul 2023 14:41:34 -0300 Subject: [PATCH 163/171] fix: remove greater than zero comparison --- src/components/Avatar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Avatar.js b/src/components/Avatar.js index 740e21bf5cce..941b6365f206 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -74,11 +74,11 @@ function Avatar(props) { const iconSize = StyleUtils.getAvatarSize(props.size); const imageStyle = - props.imageStyles && props.imageStyles.length > 0 + props.imageStyles && props.imageStyles.length ? [StyleUtils.getAvatarStyle(props.size), ...props.imageStyles, StyleUtils.getAvatarBorderRadius(props.size, props.type)] : [StyleUtils.getAvatarStyle(props.size), styles.br0]; - const iconStyle = props.imageStyles && props.imageStyles.length > 0 ? [StyleUtils.getAvatarStyle(props.size), styles.bgTransparent, ...props.imageStyles] : undefined; + const iconStyle = props.imageStyles && props.imageStyles.length ? [StyleUtils.getAvatarStyle(props.size), styles.bgTransparent, ...props.imageStyles] : undefined; const iconFillColor = isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(props.name).fill : props.fill; const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(props.name) : props.fallbackIcon; From 035d7f3ed90d86d1be69574a1d9ba7ba7f36b26a Mon Sep 17 00:00:00 2001 From: jeet-dhandha Date: Mon, 3 Jul 2023 23:12:47 +0530 Subject: [PATCH 164/171] fix: requested changes in tests --- tests/actions/ReportTest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index e98a5249187f..5ebffff507b8 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -659,7 +659,7 @@ describe('actions/Report', () => { const resultAction = _.first(_.values(reportActions)); // Add a reaction to the comment - Report.toggleEmojiReaction(REPORT_ID, resultAction, EMOJI); + Report.toggleEmojiReaction(REPORT_ID, resultAction.reportActionID, EMOJI); return waitForPromisesToResolve(); }) .then(() => { @@ -668,7 +668,7 @@ describe('actions/Report', () => { // Now we toggle the reaction while the skin tone has changed. // As the emoji doesn't support skin tones, the emoji // should get removed instead of added again. - Report.toggleEmojiReaction(REPORT_ID, resultAction, EMOJI, 2); + Report.toggleEmojiReaction(REPORT_ID, resultAction.reportActionID, EMOJI, 2); return waitForPromisesToResolve(); }) .then(() => { From 0f268552cb558915fd50f595efdb0017e56788e2 Mon Sep 17 00:00:00 2001 From: Davi Rodrigues Date: Mon, 3 Jul 2023 14:43:00 -0300 Subject: [PATCH 165/171] fix: update no border radius style --- src/components/Avatar.js | 2 +- src/styles/styles.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/Avatar.js b/src/components/Avatar.js index 941b6365f206..b59a8902eb13 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -76,7 +76,7 @@ function Avatar(props) { const imageStyle = props.imageStyles && props.imageStyles.length ? [StyleUtils.getAvatarStyle(props.size), ...props.imageStyles, StyleUtils.getAvatarBorderRadius(props.size, props.type)] - : [StyleUtils.getAvatarStyle(props.size), styles.br0]; + : [StyleUtils.getAvatarStyle(props.size), styles.noBorderRadius]; const iconStyle = props.imageStyles && props.imageStyles.length ? [StyleUtils.getAvatarStyle(props.size), styles.bgTransparent, ...props.imageStyles] : undefined; diff --git a/src/styles/styles.js b/src/styles/styles.js index 2c9928bd1a6f..f26f39adefba 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -551,6 +551,10 @@ const styles = { marginVertical: 1, }, + noBorderRadius: { + borderRadius: 0, + }, + noRightBorderRadius: { borderTopRightRadius: 0, borderBottomRightRadius: 0, @@ -742,10 +746,6 @@ const styles = { borderColor: themeColors.border, }, - br0: { - borderRadius: 0, - }, - borderColorFocus: { borderColor: themeColors.borderFocus, }, From 3449e3400d01ec32211f758ee126594a7daf9057 Mon Sep 17 00:00:00 2001 From: jeet-dhandha Date: Mon, 3 Jul 2023 23:14:38 +0530 Subject: [PATCH 166/171] fix: added description --- src/libs/ReportActionsUtils.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index fc6ac92dadb5..78dea645e18e 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -425,6 +425,12 @@ function getLinkedTransactionID(reportID, reportActionID) { return reportAction.originalMessage.IOUTransactionID; } +/** + * + * @param {String} reportID + * @param {String} reportActionID + * @returns {Object} + */ function getReportAction(reportID, reportActionID) { return lodashGet(allReportActions, [reportID, reportActionID], {}); } From 035c101836d0203117682199c02a162b752304cd Mon Sep 17 00:00:00 2001 From: jeet-dhandha Date: Mon, 3 Jul 2023 23:18:59 +0530 Subject: [PATCH 167/171] fix: lint issues fixed --- src/libs/ReportActionsUtils.js | 2 +- src/libs/actions/Report.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 78dea645e18e..8ce7240f7bfb 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -426,7 +426,7 @@ function getLinkedTransactionID(reportID, reportActionID) { } /** - * + * * @param {String} reportID * @param {String} reportActionID * @returns {Object} diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index aabec697e1aa..55eaee691ad9 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1640,11 +1640,11 @@ function removeEmojiReaction(reportID, originalReportAction, emoji) { */ function toggleEmojiReaction(reportID, reportActionID, emoji, paramSkinTone = preferredSkinTone) { const reportAction = ReportActionsUtils.getReportAction(reportID, reportActionID); - - if(_.isEmpty(reportAction)) { + + if (_.isEmpty(reportAction)) { return; } - + const message = reportAction.message[0]; const reactionObject = message.reactions && _.find(message.reactions, (reaction) => reaction.emoji === emoji.name); const skinTone = emoji.types === undefined ? null : paramSkinTone; // only use skin tone if emoji supports it From 675add432c5de5813e4c373d48774c008afb1b43 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Mon, 3 Jul 2023 15:24:11 -0400 Subject: [PATCH 168/171] Fix invited user disappearing --- src/pages/workspace/WorkspaceInvitePage.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js index 1092579f0bc5..5162c07b1343 100644 --- a/src/pages/workspace/WorkspaceInvitePage.js +++ b/src/pages/workspace/WorkspaceInvitePage.js @@ -179,10 +179,7 @@ class WorkspaceInvitePage extends React.Component { _.forEach(personalDetails, (detail) => (detailsMap[detail.login] = detail)); const selectedOptions = []; _.forEach(this.state.selectedOptions, (option) => { - if (!_.has(detailsMap, option.login)) { - return; - } - selectedOptions.push(detailsMap[option.login]); + selectedOptions.push(_.has(detailsMap, option.login) ? detailsMap[option.login] : option); }); this.setState({ From 2341952673f7237037b7b5f28ddb7480a7d98348 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Mon, 3 Jul 2023 13:02:16 -1000 Subject: [PATCH 169/171] prettier --- src/libs/ReportActionsUtils.js | 66 +++++++++++++++++----------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 376eb2273b55..86096b300c12 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -457,39 +457,39 @@ function getReportAction(reportID, reportActionID) { * @returns {string} */ function getMostRecentReportActionLastModified() { - // Start with the oldest date possible - let mostRecentReportActionLastModified = new Date(0).toISOString(); - - // Flatten all the actions - // Loop over them all to find the one that is the most recent - const flatReportActions = _.flatten(_.map(allReportActions, (actions) => _.values(actions))); - _.each(flatReportActions, (action) => { - // Pending actions should not be counted here as a user could create a comment or some other action while offline and the server might know about - // messages they have not seen yet. - if (!_.isEmpty(action.pendingAction)) { - return; - } - - const lastModified = action.lastModified || action.created; - if (lastModified < mostRecentReportActionLastModified) { - return; - } - - mostRecentReportActionLastModified = lastModified; - }); - - // We might not have actions so we also look at the report objects to see if any have a lastVisibleActionLastModified that is more recent. We don't need to get - // any reports that have been updated before either a recently updated report or reportAction as we should be up to date on these - _.each(allReports, (report) => { - const reportLastVisibleActionLastModified = report.lastVisibleActionLastModified || report.lastVisibleActionCreated; - if (!reportLastVisibleActionLastModified || reportLastVisibleActionLastModified < mostRecentReportActionLastModified) { - return; - } - - mostRecentReportActionLastModified = reportLastVisibleActionLastModified; - }); - - return mostRecentReportActionLastModified; + // Start with the oldest date possible + let mostRecentReportActionLastModified = new Date(0).toISOString(); + + // Flatten all the actions + // Loop over them all to find the one that is the most recent + const flatReportActions = _.flatten(_.map(allReportActions, (actions) => _.values(actions))); + _.each(flatReportActions, (action) => { + // Pending actions should not be counted here as a user could create a comment or some other action while offline and the server might know about + // messages they have not seen yet. + if (!_.isEmpty(action.pendingAction)) { + return; + } + + const lastModified = action.lastModified || action.created; + if (lastModified < mostRecentReportActionLastModified) { + return; + } + + mostRecentReportActionLastModified = lastModified; + }); + + // We might not have actions so we also look at the report objects to see if any have a lastVisibleActionLastModified that is more recent. We don't need to get + // any reports that have been updated before either a recently updated report or reportAction as we should be up to date on these + _.each(allReports, (report) => { + const reportLastVisibleActionLastModified = report.lastVisibleActionLastModified || report.lastVisibleActionCreated; + if (!reportLastVisibleActionLastModified || reportLastVisibleActionLastModified < mostRecentReportActionLastModified) { + return; + } + + mostRecentReportActionLastModified = reportLastVisibleActionLastModified; + }); + + return mostRecentReportActionLastModified; } /** From 824b7b25b817a4ccb2c3145b62d19c4a81e3c0da Mon Sep 17 00:00:00 2001 From: Hans Date: Tue, 4 Jul 2023 17:49:24 +0700 Subject: [PATCH 170/171] update canFlagReportAction params --- src/libs/ReportUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index a3c8d859aa05..1f19ba16483f 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2101,7 +2101,7 @@ function chatIncludesChronos(report) { function shouldShowFlagComment(reportAction, report) { return ( - canFlagReportAction(reportAction) && + canFlagReportAction(reportAction, report.reportID) && !isArchivedRoom(report) && !chatIncludesChronos(report) && !isConciergeChatReport(report.reportID) && From 3e02ef4a050fb498eac9db6063d96df417ddef8b Mon Sep 17 00:00:00 2001 From: OSBotify Date: Tue, 4 Jul 2023 11:09:46 +0000 Subject: [PATCH 171/171] Update version to 1.3.36-3 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 12f144c4daa8..1c7e0fccd618 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -106,8 +106,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001033602 - versionName "1.3.36-2" + versionCode 1001033603 + versionName "1.3.36-3" } splits { diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 867e9778ddba..1ceaea0004b8 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -32,7 +32,7 @@ CFBundleVersion - 1.3.36.2 + 1.3.36.3 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index e9f95bc6a1dc..3827a7228527 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.3.36.2 + 1.3.36.3 diff --git a/package-lock.json b/package-lock.json index f4405f777091..03d6d2ddeeb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.36-2", + "version": "1.3.36-3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.36-2", + "version": "1.3.36-3", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 220ada0e5100..9b4a7acc4de9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.36-2", + "version": "1.3.36-3", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",