From 6fef245267a6c3b777dcd7fbe0c3af4485222d6a Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Sat, 29 Jul 2023 12:54:21 +0200 Subject: [PATCH 001/398] add throttle --- src/components/EmojiPicker/EmojiPickerMenu/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js index 13c7e5516f37..55d191e571fe 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js @@ -75,7 +75,7 @@ class EmojiPickerMenu extends Component { // prevent auto focus when open picker for mobile device this.shouldFocusInputOnScreenFocus = canFocusInputOnScreenFocus(); - this.filterEmojis = _.debounce(this.filterEmojis.bind(this), 300); + this.filterEmojis = _.throttle(this.filterEmojis.bind(this), 150); this.highlightAdjacentEmoji = this.highlightAdjacentEmoji.bind(this); this.scrollToHighlightedIndex = this.scrollToHighlightedIndex.bind(this); this.setupEventHandlers = this.setupEventHandlers.bind(this); From b6eae290a3870b53e3b3251739a45874142d7b83 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Sat, 29 Jul 2023 12:57:59 +0200 Subject: [PATCH 002/398] adjust a timeout --- src/components/EmojiPicker/EmojiPickerMenu/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js index 55d191e571fe..845c7fcf81a7 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js @@ -18,6 +18,7 @@ import getOperatingSystem from '../../../libs/getOperatingSystem'; import * as User from '../../../libs/actions/User'; import EmojiSkinToneList from '../EmojiSkinToneList'; import * as EmojiUtils from '../../../libs/EmojiUtils'; +import * as Browser from '../../../libs/Browser'; import CategoryShortcutBar from '../CategoryShortcutBar'; import TextInput from '../../TextInput'; import isEnterWhileComposition from '../../../libs/KeyboardShortcut/isEnterWhileComposition'; @@ -43,6 +44,7 @@ const defaultProps = { forwardedRef: () => {}, preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE, }; +const throttleTime = Browser.isMobile() ? 200 : 50; class EmojiPickerMenu extends Component { constructor(props) { @@ -75,7 +77,7 @@ class EmojiPickerMenu extends Component { // prevent auto focus when open picker for mobile device this.shouldFocusInputOnScreenFocus = canFocusInputOnScreenFocus(); - this.filterEmojis = _.throttle(this.filterEmojis.bind(this), 150); + this.filterEmojis = _.throttle(this.filterEmojis.bind(this), throttleTime); this.highlightAdjacentEmoji = this.highlightAdjacentEmoji.bind(this); this.scrollToHighlightedIndex = this.scrollToHighlightedIndex.bind(this); this.setupEventHandlers = this.setupEventHandlers.bind(this); From 55ae86acf61114125b47ce76573cd8e7139039f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Fri, 25 Aug 2023 17:19:31 +0200 Subject: [PATCH 003/398] forward reportID to ReportActionItemMessageEdit --- src/pages/home/report/ReportActionItemMessageEdit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index e2c50b288fe5..36302b9d54ec 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -409,6 +409,7 @@ function ReportActionItemMessageEdit(props) { setHasExceededMaxCommentLength(hasExceeded)} /> From 8217aa58c18fc8eba650d474a53aa47309fc799e Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Wed, 20 Sep 2023 15:18:14 -0700 Subject: [PATCH 004/398] make room change logs render with muted text, rename muted text => alert text --- src/CONST.ts | 7 +++++++ src/components/FormAlertWrapper.js | 2 +- .../HTMLEngineProvider/BaseHTMLEngineProvider.js | 6 +++++- src/libs/ReportActionsUtils.js | 2 +- src/pages/NewChatSelectorPage.js | 2 +- src/pages/home/report/ReportActionItem.js | 2 +- 6 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 5c8cd1b8f038..4fe2e93776b8 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -507,6 +507,8 @@ const CONST = { DELETE_TAG: 'POLICYCHANGELOG_DELETE_TAG', IMPORT_CUSTOM_UNIT_RATES: 'POLICYCHANGELOG_IMPORT_CUSTOM_UNIT_RATES', IMPORT_TAGS: 'POLICYCHANGELOG_IMPORT_TAGS', + INVITE_TO_ROOM: 'POLICYCHANGELOG_INVITETOROOM', + REMOVE_FROM_ROOM: 'POLICYCHANGELOG_REMOVEFROMROOM', SET_AUTOREIMBURSEMENT: 'POLICYCHANGELOG_SET_AUTOREIMBURSEMENT', SET_AUTO_JOIN: 'POLICYCHANGELOG_SET_AUTO_JOIN', SET_CATEGORY_NAME: 'POLICYCHANGELOG_SET_CATEGORY_NAME', @@ -541,6 +543,11 @@ const CONST = { UPDATE_TIME_ENABLED: 'POLICYCHANGELOG_UPDATE_TIME_ENABLED', UPDATE_TIME_RATE: 'POLICYCHANGELOG_UPDATE_TIME_RATE', }, + ROOMCHANGELOG: { + INVITE_TO_ROOM: 'INVITETOROOM', + REMOVE_FROM_ROOM: 'REMOVEFROMROOM', + JOIN_ROOM: 'JOINROOM', + }, }, }, ARCHIVE_REASON: { diff --git a/src/components/FormAlertWrapper.js b/src/components/FormAlertWrapper.js index 704d9b5a241c..67e031ce6ab6 100644 --- a/src/components/FormAlertWrapper.js +++ b/src/components/FormAlertWrapper.js @@ -66,7 +66,7 @@ function FormAlertWrapper(props) { ); } else if (props.isMessageHtml) { - children = ${props.message}`} />; + children = ${props.message}`} />; } return ( diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js index 4b61d55ae228..ce8eb7460f25 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js @@ -29,9 +29,13 @@ const customHTMLElementModels = { edited: defaultHTMLElementModels.span.extend({ tagName: 'edited', }), + 'alert-text': defaultHTMLElementModels.div.extend({ + tagName: 'alert-text', + mixedUAStyles: {...styles.formError, ...styles.mb0}, + }), 'muted-text': defaultHTMLElementModels.div.extend({ tagName: 'muted-text', - mixedUAStyles: {...styles.formError, ...styles.mb0}, + mixedUAStyles: {...styles.colorMuted, ...styles.mb0}, }), comment: defaultHTMLElementModels.div.extend({ tagName: 'comment', diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index cda1f39d3087..79bc736f78d2 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -332,7 +332,7 @@ function shouldReportActionBeVisible(reportAction, key) { } // Filter out any unsupported reportAction types - if (!Object.values(CONST.REPORT.ACTIONS.TYPE).includes(reportAction.actionName) && !Object.values(CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG).includes(reportAction.actionName)) { + if (!Object.values(CONST.REPORT.ACTIONS.TYPE).includes(reportAction.actionName) && !Object.values(CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG).includes(reportAction.actionName) && !Object.values(CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG).includes(reportAction.actionName)) { return false; } diff --git a/src/pages/NewChatSelectorPage.js b/src/pages/NewChatSelectorPage.js index 89a3fd1adc72..dc87c67118eb 100755 --- a/src/pages/NewChatSelectorPage.js +++ b/src/pages/NewChatSelectorPage.js @@ -35,7 +35,7 @@ function NewChatSelectorPage(props) { title={props.translate('sidebarScreen.fabNewChat')} onBackButtonPress={Navigation.dismissModal} /> - {Permissions.canUsePolicyRooms(props.betas) ? ( + {true ? ( ( diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 8cf8fd78371d..9b6c5bc16b92 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -365,7 +365,7 @@ function ReportActionItem(props) { > {!props.draftMessage ? ( - Date: Wed, 20 Sep 2023 15:18:44 -0700 Subject: [PATCH 005/398] style --- src/libs/ReportActionsUtils.js | 6 +++++- src/pages/home/report/ReportActionItem.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 79bc736f78d2..d505191b6549 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -332,7 +332,11 @@ function shouldReportActionBeVisible(reportAction, key) { } // Filter out any unsupported reportAction types - if (!Object.values(CONST.REPORT.ACTIONS.TYPE).includes(reportAction.actionName) && !Object.values(CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG).includes(reportAction.actionName) && !Object.values(CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG).includes(reportAction.actionName)) { + if ( + !Object.values(CONST.REPORT.ACTIONS.TYPE).includes(reportAction.actionName) && + !Object.values(CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG).includes(reportAction.actionName) && + !Object.values(CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG).includes(reportAction.actionName) + ) { return false; } diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 9b6c5bc16b92..8cf8fd78371d 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -365,7 +365,7 @@ function ReportActionItem(props) { > {!props.draftMessage ? ( - Date: Wed, 20 Sep 2023 15:32:26 -0700 Subject: [PATCH 006/398] replace baseURLs in policy change logs --- src/libs/ReportActionsUtils.js | 19 ++++++++++++++++++- .../home/report/ReportActionItemFragment.js | 1 - 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index d505191b6549..2b583a6847a5 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -9,6 +9,7 @@ import CONST from '../CONST'; import ONYXKEYS from '../ONYXKEYS'; import Log from './Log'; import isReportMessageAttachment from './isReportMessageAttachment'; +import { getEnvironmentURL } from './Environment/Environment'; const allReports = {}; Onyx.connect({ @@ -377,6 +378,21 @@ function shouldReportActionBeVisibleAsLastAction(reportAction) { ); } +/** + * For invite to room and remove from room policy change logs, report URLs are generated in the server, + * which includes a baseURL placeholder that's replaced in the client. + * @param {*} reportAction + * @returns + */ +function replaceBaseURL(reportAction) { + if (reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.REMOVEFROMROOM) { + return reportAction; + } + + reportAction.message[0].html = _.replace(reportAction.message[0].html, '%baseURL', getEnvironmentURL()); + return reportAction; +} + /** * @param {String} reportID * @param {Object} [actionsToMerge] @@ -453,7 +469,8 @@ function filterOutDeprecatedReportActions(reportActions) { */ function getSortedReportActionsForDisplay(reportActions) { const filteredReportActions = _.filter(reportActions, (reportAction, key) => shouldReportActionBeVisible(reportAction, key)); - return getSortedReportActions(filteredReportActions, true); + const baseURLAdjustedReportActions = _.map(filteredReportActions, (reportAction) => replaceBaseURL(reportAction)); + return getSortedReportActions(baseURLAdjustedReportActions, true); } /** diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 1f61b44841bc..775abe7bc29f 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -116,7 +116,6 @@ function ReportActionItemFragment(props) { // Threaded messages display "[Deleted message]" instead of being hidden altogether. // While offline we display the previous message with a strikethrough style. Once online we want to // immediately display "[Deleted message]" while the delete action is pending. - if ((!props.network.isOffline && props.isThreadParentMessage && props.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) || props.fragment.isDeletedParentAction) { return ${props.translate('parentReportAction.deletedMessage')}`} />; } From 0ca3c280d037292d6910b3ce9003d0a0e6694c9f Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Wed, 20 Sep 2023 15:38:58 -0700 Subject: [PATCH 007/398] get environment URL correctly --- src/libs/Environment/Environment.js | 7 +++++-- src/libs/ReportActionsUtils.js | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libs/Environment/Environment.js b/src/libs/Environment/Environment.js index c039b49d33aa..40f3c736f0b0 100644 --- a/src/libs/Environment/Environment.js +++ b/src/libs/Environment/Environment.js @@ -41,9 +41,12 @@ function isInternalTestBuild() { * * @returns {Promise} */ -function getEnvironmentURL() { +function getEnvironmentURL(environment) { + if (environment) { + return ENVIRONMENT_URLS[environment]; + } return new Promise((resolve) => { - getEnvironment().then((environment) => resolve(ENVIRONMENT_URLS[environment])); + getEnvironment().then((env) => resolve(ENVIRONMENT_URLS[env])); }); } diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 2b583a6847a5..6ea42364c025 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -43,6 +43,9 @@ Onyx.connect({ callback: (val) => (isNetworkOffline = lodashGet(val, 'isOffline', false)), }); +let environmentURL; +getEnvironmentURL().then((url) => environmentURL = url); + /** * @param {Object} reportAction * @returns {Boolean} @@ -389,7 +392,7 @@ function replaceBaseURL(reportAction) { return reportAction; } - reportAction.message[0].html = _.replace(reportAction.message[0].html, '%baseURL', getEnvironmentURL()); + reportAction.message[0].html = reportAction.message[0].html.replace('%baseURL', environmentURL); return reportAction; } From edf6e3e1af7089a22ff26e4606474a004fd6c652 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Wed, 20 Sep 2023 15:39:29 -0700 Subject: [PATCH 008/398] style --- src/libs/ReportActionsUtils.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 6ea42364c025..51fee71f38fe 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -9,7 +9,7 @@ import CONST from '../CONST'; import ONYXKEYS from '../ONYXKEYS'; import Log from './Log'; import isReportMessageAttachment from './isReportMessageAttachment'; -import { getEnvironmentURL } from './Environment/Environment'; +import {getEnvironmentURL} from './Environment/Environment'; const allReports = {}; Onyx.connect({ @@ -44,7 +44,7 @@ Onyx.connect({ }); let environmentURL; -getEnvironmentURL().then((url) => environmentURL = url); +getEnvironmentURL().then((url) => (environmentURL = url)); /** * @param {Object} reportAction @@ -384,8 +384,8 @@ function shouldReportActionBeVisibleAsLastAction(reportAction) { /** * For invite to room and remove from room policy change logs, report URLs are generated in the server, * which includes a baseURL placeholder that's replaced in the client. - * @param {*} reportAction - * @returns + * @param {*} reportAction + * @returns */ function replaceBaseURL(reportAction) { if (reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.REMOVEFROMROOM) { From 08916dca19c0092d527fc59a118dc9d4ed7a2834 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Thu, 21 Sep 2023 14:51:50 -0700 Subject: [PATCH 009/398] setup navigation --- src/ROUTES.ts | 4 ++++ .../AppNavigator/ModalStackNavigators.js | 24 +++++++++++++++++++ src/libs/Navigation/linkingConfig.js | 10 ++++++++ 3 files changed, 38 insertions(+) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 2c37116db395..e5bb2d704a3d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -150,6 +150,10 @@ export default { }, REPORT_PARTICIPANTS: 'r/:reportID/participants', getReportParticipantsRoute: (reportID: string) => `r/${reportID}/participants`, + ROOM_MEMBERS: 'r/:reportID/members', + getRoomMembersRoute: (reportID: string) => `r/${reportID}/members`, + ROOM_INVITE: 'r/:reportID/invite', + getRoomInviteRoute: (reportID: string) => `r/${reportID}/invite`, REPORT_WITH_ID_DETAILS: 'r/:reportID/details', getReportDetailsRoute: (reportID: string) => `r/${reportID}/details`, REPORT_SETTINGS: 'r/:reportID/settings', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index 5c110264e034..87657da2e0dd 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -263,6 +263,28 @@ const ReportParticipantsModalStackNavigator = createModalStackNavigator([ }, ]); + +const RoomMembersModalStackNavigator = createModalStackNavigator([ + { + getComponent: () => { + const RoomMembersPage = require('../../../pages/RoomMembersPage').default; + return RoomMembersPage; + }, + name: 'RoomMembers_Root', + }, +]); + + +// const RoomInviteModalStackNavigator = createModalStackNavigator([ +// { +// getComponent: () => { +// const RoomInvitePage = require('../../../pages/RoomInvitePage').default; +// return RoomInvitePage; +// }, +// name: 'RoomInvite_Root', +// }, +// ]); + const SearchModalStackNavigator = createModalStackNavigator([ { getComponent: () => { @@ -810,4 +832,6 @@ export { PrivateNotesModalStackNavigator, NewTeachersUniteNavigator, SignInModalStackNavigator, + RoomMembersModalStackNavigator, + // RoomInviteModalStackNavigator, }; diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index f4420330fbd9..55c07ed8d6bb 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -299,6 +299,16 @@ export default { ReportParticipants_Root: ROUTES.REPORT_PARTICIPANTS, }, }, + Room_Invite: { + screens: { + RoomInvite_Root: ROUTES.ROOM_INVITE, + }, + }, + Room_Members: { + screens: { + RoomMembers_Root: ROUTES.ROOM_MEMBERS, + }, + }, MoneyRequest: { screens: { Money_Request: { From 0f9bfb1d6ee7c5aaebd9b1d200f6bc4e05c53406 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Thu, 21 Sep 2023 15:09:57 -0700 Subject: [PATCH 010/398] setup navigation --- src/CONST.ts | 1 + src/libs/Navigation/AppNavigator/ModalStackNavigators.js | 1 - .../AppNavigator/Navigators/RightModalNavigator.js | 8 ++++++++ src/libs/Navigation/linkingConfig.js | 4 ++-- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index eed1b98ae551..5101369c7e87 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1347,6 +1347,7 @@ const CONST = { REPORT_DETAILS_MENU_ITEM: { SHARE_CODE: 'shareCode', MEMBERS: 'member', + INVITE: 'invite', SETTINGS: 'settings', LEAVE_ROOM: 'leaveRoom', WELCOME_MESSAGE: 'welcomeMessage', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index 87657da2e0dd..722790a3083d 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -263,7 +263,6 @@ const ReportParticipantsModalStackNavigator = createModalStackNavigator([ }, ]); - const RoomMembersModalStackNavigator = createModalStackNavigator([ { getComponent: () => { diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js index 27a15fa3d763..e83b104827cc 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js @@ -64,6 +64,14 @@ function RightModalNavigator(props) { name="Participants" component={ModalStackNavigators.ReportParticipantsModalStackNavigator} /> + + {/* */} Date: Thu, 21 Sep 2023 16:15:53 -0700 Subject: [PATCH 011/398] setup RoomMembersPage skeleton --- src/libs/PolicyUtils.js | 9 ++ src/libs/actions/Report.js | 45 ++++++ src/pages/ReportDetailsPage.js | 19 ++- src/pages/RoomMembersPage.js | 267 +++++++++++++++++++++++++++++++++ 4 files changed, 338 insertions(+), 2 deletions(-) create mode 100644 src/pages/RoomMembersPage.js diff --git a/src/libs/PolicyUtils.js b/src/libs/PolicyUtils.js index 5847452537ef..fc70ea422424 100644 --- a/src/libs/PolicyUtils.js +++ b/src/libs/PolicyUtils.js @@ -119,6 +119,14 @@ function isExpensifyGuideTeam(email) { */ const isPolicyAdmin = (policy) => lodashGet(policy, 'role') === CONST.POLICY.ROLE.ADMIN; +/** + * + * @param {String} policyID + * @param {Object} policies + * @returns {Boolean} + */ +const isPolicyMember = (policyID, policies) => _.reduce(policies, (isMember, policy) => isMember || policy.id === policyID, false) + /** * @param {Object} policyMembers * @param {Object} personalDetails @@ -180,4 +188,5 @@ export { isPolicyAdmin, getMemberAccountIDsForWorkspace, getIneligibleInvitees, + isPolicyMember, }; diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index aa0d4b432da4..47b016b2fc8c 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1829,6 +1829,50 @@ function leaveRoom(reportID) { navigateToConciergeChat(); } +/** + * Removes people from a room + * + * @param {String} reportID + * @param {Array} targetAccountIDs + */ +function removeFromRoom(reportID, targetAccountIDs, targetEmails) { + const report = lodashGet(allReports, [reportID], {}); + + const {participants, participantAccountIDs} = report; + const participantsAfterRemoval = _.filter(participants, email => !targetEmails.includes(email)); + const participantAccountIDsAfterRemoval = _.filter(participantAccountIDs, accountID => !targetAccountIDs.includes(accountID)); + + API.write( + 'RemoveFromRoom', + { + reportID, + targetAccountIDs, + }, + { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: { + participants: participantsAfterRemoval, + participantAccountIDs: participantAccountIDsAfterRemoval, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: { + participants, + participantAccountIDs, + }, + }, + ], + }, + ); +} + /** * @param {String} reportID */ @@ -2106,6 +2150,7 @@ export { hasAccountIDEmojiReacted, shouldShowReportActionNotification, leaveRoom, + removeFromRoom, getCurrentUserAccountID, setLastOpenedPublicRoom, flagComment, diff --git a/src/pages/ReportDetailsPage.js b/src/pages/ReportDetailsPage.js index 3a9e0f5c2eb8..3b3bb3f51afd 100644 --- a/src/pages/ReportDetailsPage.js +++ b/src/pages/ReportDetailsPage.js @@ -60,6 +60,7 @@ const defaultProps = { function ReportDetailsPage(props) { const policy = useMemo(() => props.policies[`${ONYXKEYS.COLLECTION.POLICY}${props.report.policyID}`], [props.policies, props.report.policyID]); const isPolicyAdmin = useMemo(() => PolicyUtils.isPolicyAdmin(policy), [policy]); + const isPolicyMember = useMemo(() => PolicyUtils.isPolicyMember(props.report.policyID, props.policies), [props.report, props.policies]); const shouldDisableSettings = useMemo(() => ReportUtils.shouldDisableSettings(props.report), [props.report]); const shouldUseFullTitle = !shouldDisableSettings || ReportUtils.isTaskReport(props.report); const isChatRoom = useMemo(() => ReportUtils.isChatRoom(props.report), [props.report]); @@ -87,7 +88,7 @@ function ReportDetailsPage(props) { return items; } - if (participants.length) { + if ((!isUserCreatedPolicyRoom && participants.length) || (isUserCreatedPolicyRoom && isPolicyMember)) { items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.MEMBERS, translationKey: 'common.members', @@ -95,7 +96,21 @@ function ReportDetailsPage(props) { subtitle: participants.length, isAnonymousAction: false, action: () => { - Navigation.navigate(ROUTES.getReportParticipantsRoute(props.report.reportID)); + if (isUserCreatedPolicyRoom) { + Navigation.navigate(ROUTES.getRoomMembersRoute(props.report.reportID)); + } else { + Navigation.navigate(ROUTES.getReportParticipantsRoute(props.report.reportID)); + } + }, + }); + } else if (!participants.length && isUserCreatedPolicyRoom) { + items.push({ + key: CONST.REPORT_DETAILS_MENU_ITEM.INVITE, + translationKey: 'common.invite', + icon: Expensicons.Users, + isAnonymousAction: false, + action: () => { + Navigation.navigate(ROUTES.getRoomInviteRoute(props.report.reportID)); }, }); } diff --git a/src/pages/RoomMembersPage.js b/src/pages/RoomMembersPage.js new file mode 100644 index 000000000000..376a47947ed2 --- /dev/null +++ b/src/pages/RoomMembersPage.js @@ -0,0 +1,267 @@ +import React, {useMemo, useState} from 'react'; +import _ from 'underscore'; +import lodashGet from 'lodash/get'; +import PropTypes from 'prop-types'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import styles from '../styles/styles'; +import compose from '../libs/compose'; +import CONST from '../CONST'; +import ONYXKEYS from '../ONYXKEYS'; +import ScreenWrapper from '../components/ScreenWrapper'; +import FullPageNotFoundView from '../components/BlockingViews/FullPageNotFoundView'; +import HeaderWithBackButton from '../components/HeaderWithBackButton'; +import ConfirmModal from '../components/ConfirmModal'; +import Button from '../components/Button'; +import SelectionList from '../components/SelectionList'; +import withWindowDimensions, {windowDimensionsPropTypes} from '../components/withWindowDimensions'; +import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; +import {withNetwork} from '../components/OnyxProvider'; +import withReportOrNotFound from './home/report/withReportOrNotFound'; +import personalDetailsPropType from './personalDetailsPropType'; +import reportPropTypes from './reportPropTypes'; +import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '../components/withCurrentUserPersonalDetails'; +import networkPropTypes from '../components/networkPropTypes'; +import * as PolicyUtils from '../libs/PolicyUtils'; +import * as OptionsListUtils from '../libs/OptionsListUtils'; + +const propTypes = { + /** All personal details asssociated with user */ + personalDetails: PropTypes.objectOf(personalDetailsPropType), + + /** The report currently being looked at */ + report: reportPropTypes.isRequired, + + /** The policies which the user has access to and which the report could be tied to */ + policies: PropTypes.shape({ + /** Name of the policy */ + name: PropTypes.string, + }), + + /** URL Route params */ + route: PropTypes.shape({ + /** Params from the URL path */ + params: PropTypes.shape({ + /** reportID passed via route: /workspace/:reportID/members */ + reportID: PropTypes.string, + }), + }).isRequired, + + /** Session info for the currently logged in user. */ + session: PropTypes.shape({ + /** Currently logged in user accountID */ + accountID: PropTypes.number, + }), + + isLoadingReportData: PropTypes.bool, + ...withLocalizePropTypes, + ...windowDimensionsPropTypes, + ...withCurrentUserPersonalDetailsPropTypes, + network: networkPropTypes.isRequired, +}; + +const defaultProps = { + personalDetails: {}, + session: { + accountID: 0, + }, + isLoadingReportData: true, + report: {}, + policies: {}, + ...withCurrentUserPersonalDetailsDefaultProps, +}; + +function RoomMembersPage(props) { + const [selectedMembers, setSelectedMembers] = useState([]); + const [removeMembersConfirmModalVisible, setRemoveMembersConfirmModalVisible] = useState(false); + const [errors, setErrors] = useState({}); + const [searchValue, setSearchValue] = useState(''); + + + /** + * Open the modal to invite a user + */ + const inviteUser = () => { + setSearchValue(''); + Navigation.navigate(ROUTES.getRoomInviteRoute(props.report.reportID)); + }; + + /** + * Remove selected users from the room + */ + const removeUsers = () => { + if (!_.isEmpty(errors)) { + return; + } + + Report.removeFromRoom(accountIDsToRemove, props.route.params.policyID); + setSelectedMembers([]); + setRemoveMembersConfirmModalVisible(false); + }; + + /** + * Show the modal to confirm removal of the selected members + */ + const askForConfirmationToRemove = () => { + if (!_.isEmpty(errors)) { + return; + } + setRemoveMembersConfirmModalVisible(true); + }; + + const getMemberOptions = () => { + let result = []; + + _.each(props.participantAccountIDs, (accountID) => { + const details = props.personalDetails[accountID]; + + if (!details) { + Log.hmmm(`[RoomMembersPage] no personal details found for room member with accountID: ${accountID}`); + return; + } + + // If search value is provided, filter out members that don't match the search value + if (searchValue.trim()) { + let memberDetails = ''; + if (details.login) { + memberDetails += ` ${details.login.toLowerCase()}`; + } + if (details.firstName) { + memberDetails += ` ${details.firstName.toLowerCase()}`; + } + if (details.lastName) { + memberDetails += ` ${details.lastName.toLowerCase()}`; + } + if (details.displayName) { + memberDetails += ` ${details.displayName.toLowerCase()}`; + } + if (details.phoneNumber) { + memberDetails += ` ${details.phoneNumber.toLowerCase()}`; + } + + if (!OptionsListUtils.isSearchStringMatch(searchValue.trim(), memberDetails)) { + return; + } + } + + result.push({ + keyForList: accountID, + accountID: Number(accountID), + isSelected: _.contains(selectedEmployees, Number(accountID)), + isDisabled: accountID === props.session.accountID, + text: props.formatPhoneNumber(details.displayName), + alternateText: props.formatPhoneNumber(details.login), + rightElement: isAdmin ? ( + + {props.translate('common.admin')} + + ) : null, + avatar: { + source: UserUtils.getAvatar(details.avatar, accountID), + name: details.login, + type: CONST.ICON_TYPE_AVATAR, + }, + // errors: policyMember.errors, + // pendingAction: policyMember.pendingAction, + }); + }); + + result = _.sortBy(result, (value) => value.text.toLowerCase()); + + return result; + }; + + const isPolicyMember = useMemo(() => PolicyUtils.isPolicyMember(props.report.policyID, props.policies)); + const data = getMemberOptions(); + const headerMessage = searchValue.trim() && !data.length ? props.translate('workspace.common.memberNotFound') : ''; + return ( + + Navigation.goBack(ROUTES.getReportDetailsRoute(reportID))} + > + { + setSearchValue(''); + Navigation.goBack(ROUTES.getReportDetailsRoute(reportID)); + }} + /> + setRemoveMembersConfirmModalVisible(false)} + prompt={props.translate('workspace.people.removeMembersPrompt')} + confirmText={props.translate('common.remove')} + cancelText={props.translate('common.cancel')} + /> + + +