diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index d9d78da742481..0a872e764b39e 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -3331,6 +3331,12 @@ function getSubmittedTo(action: OnyxEntry): string | undefined { return getOriginalMessage(action)?.to; } +function isSystemUserMentioned(action: OnyxInputOrEntry>): boolean { + const mentionedUsers = getOriginalMessage(action)?.inviteeAccountIDs; + const systemAccountIDs = new Set(Object.values(CONST.ACCOUNT_ID)); + return mentionedUsers?.some((accountID) => systemAccountIDs.has(accountID)) ?? false; +} + export { doesReportHaveVisibleActions, extractLinksFromMessageHtml, @@ -3511,6 +3517,7 @@ export { isPendingHide, filterOutDeprecatedReportActions, getActionableCardFraudAlertMessage, + isSystemUserMentioned, }; export type {LastVisibleMessage}; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 12484f9a5d6e0..d8a495edc178d 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3853,6 +3853,13 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: InvitedEmails API.write(WRITE_COMMANDS.INVITE_TO_ROOM, parameters, {optimisticData, successData, failureData}); } +/** Invites people to a room via concierge whisper */ +function inviteToRoomAction(reportID: string, ancestors: Ancestor[], inviteeEmailsToAccountIDs: InvitedEmailsToAccountIDs, timezoneParam: Timezone) { + const inviteeEmails = Object.keys(inviteeEmailsToAccountIDs); + + addComment(reportID, reportID, ancestors, inviteeEmails.map((login) => `@${login}`).join(' '), timezoneParam, false); +} + function clearAddRoomMemberError(reportID: string, invitedAccountID: string) { const reportMetadata = getReportMetadata(reportID); Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { @@ -6082,6 +6089,7 @@ export { inviteToGroupChat, buildInviteToRoomOnyxData, inviteToRoom, + inviteToRoomAction, joinRoom, leaveGroupChat, leaveRoom, diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index 3740053d382e8..01eca5ddfae76 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -12,12 +12,14 @@ import InviteMemberListItem from '@components/SelectionListWithSections/InviteMe import type {Section} from '@components/SelectionListWithSections/types'; import withNavigationTransitionEnd from '@components/withNavigationTransitionEnd'; import type {WithNavigationTransitionEndProps} from '@components/withNavigationTransitionEnd'; +import useAncestors from '@hooks/useAncestors'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useReportIsArchived from '@hooks/useReportIsArchived'; import useThemeStyles from '@hooks/useThemeStyles'; -import {inviteToRoom, searchInServer} from '@libs/actions/Report'; +import {inviteToRoomAction, searchInServer} from '@libs/actions/Report'; import {clearUserSearchPhrase, updateUserSearchPhrase} from '@libs/actions/RoomMembersUserSearchPhrase'; import {READ_COMMANDS} from '@libs/API/types'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; @@ -55,13 +57,14 @@ function RoomInvitePage({ }, }: RoomInvitePageProps) { const styles = useThemeStyles(); - const {translate, formatPhoneNumber} = useLocalize(); + const {translate} = useLocalize(); const [userSearchPhrase] = useOnyx(ONYXKEYS.ROOM_MEMBERS_USER_SEARCH_PHRASE, {canBeMissing: true}); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(userSearchPhrase ?? ''); const [selectedOptions, setSelectedOptions] = useState([]); const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); const isReportArchived = useReportIsArchived(report.reportID); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const [nvpDismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); const {options, areOptionsInitialized} = useOptionsList(); @@ -192,6 +195,9 @@ function RoomInvitePage({ return reportID && (!isPolicyEmployee || isReportArchived ? ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID, backTo) : ROUTES.ROOM_MEMBERS.getRoute(reportID, backTo)); }, [isPolicyEmployee, reportID, backTo, isReportArchived]); const reportName = useMemo(() => getReportName(report), [report]); + + const ancestors = useAncestors(report); + const inviteUsers = useCallback(() => { HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_REPORTS); @@ -208,11 +214,15 @@ function RoomInvitePage({ invitedEmailsToAccountIDs[login] = Number(accountID); } if (reportID) { - inviteToRoom(reportID, invitedEmailsToAccountIDs, formatPhoneNumber); + inviteToRoomAction(reportID, ancestors, invitedEmailsToAccountIDs, currentUserPersonalDetails.timezone ?? CONST.DEFAULT_TIME_ZONE); + clearUserSearchPhrase(); + if (backTo) { + Navigation.goBack(backTo); + } else { + Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(reportID)); + } } - clearUserSearchPhrase(); - Navigation.goBack(backRoute); - }, [selectedOptions, backRoute, reportID, validate, formatPhoneNumber]); + }, [validate, selectedOptions, ancestors, reportID, currentUserPersonalDetails.timezone, backTo]); const goBack = useCallback(() => { Navigation.goBack(backRoute); diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 26f1de18ce522..edf25009ca696 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -58,7 +58,7 @@ import {isReportMessageAttachment} from '@libs/isReportMessageAttachment'; import Navigation from '@libs/Navigation/Navigation'; import Permissions from '@libs/Permissions'; import {getDisplayNameOrDefault} from '@libs/PersonalDetailsUtils'; -import {getCleanedTagName, getPersonalPolicy, isPolicyAdmin, isPolicyOwner} from '@libs/PolicyUtils'; +import {getCleanedTagName, getPersonalPolicy, isPolicyAdmin, isPolicyOwner, isPolicyUser} from '@libs/PolicyUtils'; import { extractLinksFromMessageHtml, getActionableCardFraudAlertMessage, @@ -131,6 +131,7 @@ import { isRenamedAction, isResolvedConciergeCategoryOptions, isSplitBillAction as isSplitBillActionReportActionsUtils, + isSystemUserMentioned, isTagModificationAction, isTaskAction, isTrackExpenseAction as isTrackExpenseActionReportActionsUtils, @@ -905,7 +906,10 @@ function PureReportActionItem({ const actionableMentionWhisperOptions = []; const isReportInPolicy = !!report?.policyID && report.policyID !== CONST.POLICY.ID_FAKE && getPersonalPolicy()?.id !== report.policyID; - if (isReportInPolicy && (isPolicyAdmin(policy) || isPolicyOwner(policy, currentUserAccountID))) { + // Show the invite to submit expense button even if one of the mentioned users is a not a policy member + const hasMentionedPolicyMembers = getOriginalMessage(action)?.inviteeEmails?.every((login) => isPolicyUser(policy, login)) ?? false; + + if ((isPolicyAdmin(policy) || isPolicyOwner(policy, currentUserAccountID)) && isReportInPolicy && !isSystemUserMentioned(action) && !hasMentionedPolicyMembers) { actionableMentionWhisperOptions.push({ text: 'actionableMentionWhisperOptions.inviteToSubmitExpense', key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE_TO_SUBMIT_EXPENSE}`,