diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js index 39495911b8dc..23625e2b5d4e 100644 --- a/src/pages/workspace/WorkspaceInvitePage.js +++ b/src/pages/workspace/WorkspaceInvitePage.js @@ -66,7 +66,7 @@ function WorkspaceInvitePage(props) { const [searchTerm, setSearchTerm] = useState(''); const [selectedOptions, setSelectedOptions] = useState([]); const [personalDetails, setPersonalDetails] = useState([]); - const [userToInvite, setUserToInvite] = useState(null); + const [usersToInvite, setUsersToInvite] = useState([]); const openWorkspaceInvitePage = () => { const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(props.policyMembers, props.personalDetails); Policy.openWorkspaceInvitePage(props.route.params.policyID, _.keys(policyMemberEmailsToAccountIDs)); @@ -83,19 +83,52 @@ function WorkspaceInvitePage(props) { const excludedUsers = useMemo(() => PolicyUtils.getIneligibleInvitees(props.policyMembers, props.personalDetails), [props.policyMembers, props.personalDetails]); useEffect(() => { - const inviteOptions = OptionsListUtils.getMemberInviteOptions(props.personalDetails, props.betas, searchTerm, excludedUsers); - - // Update selectedOptions with the latest personalDetails and policyMembers information - const detailsMap = {}; - _.forEach(inviteOptions.personalDetails, (detail) => (detailsMap[detail.login] = OptionsListUtils.formatMemberForList(detail))); - const newSelectedOptions = []; - _.forEach(selectedOptions, (option) => { - newSelectedOptions.push(_.has(detailsMap, option.login) ? {...detailsMap[option.login], isSelected: true} : option); + const emails = _.compact( + searchTerm + .trim() + .replace(/\s*,\s*/g, ',') + .split(','), + ); + + const newUsersToInviteDict = {}; + const newPersonalDetailsDict = {}; + const newSelectedOptionsDict = {}; + + _.each(emails, (email) => { + const inviteOptions = OptionsListUtils.getMemberInviteOptions(props.personalDetails, props.betas, email, excludedUsers); + + // Update selectedOptions with the latest personalDetails and policyMembers information + const detailsMap = {}; + _.each(inviteOptions.personalDetails, (detail) => (detailsMap[detail.login] = OptionsListUtils.formatMemberForList(detail))); + + const newSelectedOptions = []; + _.each(selectedOptions, (option) => { + newSelectedOptions.push(_.has(detailsMap, option.login) ? {...detailsMap[option.login], isSelected: true} : option); + }); + + const userToInvite = inviteOptions.userToInvite; + + // Only add the user to the invites list if it is valid + if (userToInvite) { + newUsersToInviteDict[userToInvite.accountID] = userToInvite; + } + + // Add all personal details to the new dict + _.each(inviteOptions.personalDetails, (details) => { + newPersonalDetailsDict[details.accountID] = details; + }); + + // Add all selected options to the new dict + _.each(newSelectedOptions, (option) => { + newSelectedOptionsDict[option.accountID] = option; + }); }); - setUserToInvite(inviteOptions.userToInvite); - setPersonalDetails(inviteOptions.personalDetails); - setSelectedOptions(newSelectedOptions); + // Strip out dictionary keys and update arrays + setUsersToInvite(_.values(newUsersToInviteDict)); + setPersonalDetails(_.values(newPersonalDetailsDict)); + setSelectedOptions(_.values(newSelectedOptionsDict)); + // eslint-disable-next-line react-hooks/exhaustive-deps -- we don't want to recalculate when selectedOptions change }, [props.personalDetails, props.policyMembers, props.betas, searchTerm, excludedUsers]); @@ -115,7 +148,6 @@ function WorkspaceInvitePage(props) { const selectedLogins = _.map(selectedOptions, ({login}) => login); const personalDetailsWithoutSelected = _.filter(personalDetails, ({login}) => !_.contains(selectedLogins, login)); const personalDetailsFormatted = _.map(personalDetailsWithoutSelected, OptionsListUtils.formatMemberForList); - const hasUnselectedUserToInvite = userToInvite && !_.contains(selectedLogins, userToInvite.login); sections.push({ title: translate('common.contacts'), @@ -125,14 +157,18 @@ function WorkspaceInvitePage(props) { }); indexOffset += personalDetailsFormatted.length; - if (hasUnselectedUserToInvite) { - sections.push({ - title: undefined, - data: [OptionsListUtils.formatMemberForList(userToInvite)], - shouldShow: true, - indexOffset, - }); - } + _.each(usersToInvite, (userToInvite) => { + const hasUnselectedUserToInvite = !_.contains(selectedLogins, userToInvite.login); + + if (hasUnselectedUserToInvite) { + sections.push({ + title: undefined, + data: [OptionsListUtils.formatMemberForList(userToInvite)], + shouldShow: true, + indexOffset: indexOffset++, + }); + } + }); return sections; }; @@ -187,14 +223,14 @@ function WorkspaceInvitePage(props) { const headerMessage = useMemo(() => { const searchValue = searchTerm.trim().toLowerCase(); - if (!userToInvite && CONST.EXPENSIFY_EMAILS.includes(searchValue)) { + if (usersToInvite.length === 0 && CONST.EXPENSIFY_EMAILS.includes(searchValue)) { return translate('messages.errorMessageInvalidEmail'); } - if (!userToInvite && excludedUsers.includes(searchValue)) { + if (usersToInvite.length === 0 && excludedUsers.includes(searchValue)) { return translate('messages.userIsAlreadyMemberOfWorkspace', {login: searchValue, workspace: policyName}); } - return OptionsListUtils.getHeaderMessage(personalDetails.length !== 0, Boolean(userToInvite), searchValue); - }, [excludedUsers, translate, searchTerm, policyName, userToInvite, personalDetails]); + return OptionsListUtils.getHeaderMessage(personalDetails.length !== 0, usersToInvite.length > 0, searchValue); + }, [excludedUsers, translate, searchTerm, policyName, usersToInvite, personalDetails]); return (