diff --git a/src/CONST.ts b/src/CONST.ts
index db3f479aa466..b5d23702bd17 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -523,6 +523,7 @@ const CONST = {
TASKCOMPLETED: 'TASKCOMPLETED',
TASKEDITED: 'TASKEDITED',
TASKREOPENED: 'TASKREOPENED',
+ ACTIONABLEMENTIONWHISPER: 'ACTIONABLEMENTIONWHISPER',
POLICYCHANGELOG: {
ADD_APPROVER_RULE: 'POLICYCHANGELOG_ADD_APPROVER_RULE',
ADD_BUDGET: 'POLICYCHANGELOG_ADD_BUDGET',
@@ -596,6 +597,10 @@ const CONST = {
},
THREAD_DISABLED: ['CREATED'],
},
+ ACTIONABLE_MENTION_WHISPER_RESOLUTION: {
+ INVITE: 'invited',
+ NOTHING: 'nothing',
+ },
ARCHIVE_REASON: {
DEFAULT: 'default',
ACCOUNT_CLOSED: 'accountClosed',
diff --git a/src/components/ReportActionItem/ActionableItemButtons.tsx b/src/components/ReportActionItem/ActionableItemButtons.tsx
new file mode 100644
index 000000000000..d1f169d2f409
--- /dev/null
+++ b/src/components/ReportActionItem/ActionableItemButtons.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import {View} from 'react-native';
+import Button from '@components/Button';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import type {TranslationPaths} from '@src/languages/types';
+
+type ActionableItem = {
+ isPrimary?: boolean;
+ key: string;
+ onPress: () => void;
+ text: TranslationPaths;
+};
+
+type ActionableItemButtonsProps = {
+ items: ActionableItem[];
+};
+
+function ActionableItemButtons(props: ActionableItemButtonsProps) {
+ const styles = useThemeStyles();
+ const {translate} = useLocalize();
+
+ return (
+
+ {props.items?.map((item) => (
+
+ ))}
+
+ );
+}
+
+ActionableItemButtons.displayName = 'ActionableItemButtton';
+
+export default ActionableItemButtons;
+export type {ActionableItem};
diff --git a/src/languages/en.ts b/src/languages/en.ts
index b38c5b42f569..cb12c4ae9e78 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -1933,6 +1933,10 @@ export default {
levelTwoResult: 'Message hidden from channel, plus anonymous warning and message is reported for review.',
levelThreeResult: 'Message removed from channel plus anonymous warning and message is reported for review.',
},
+ actionableMentionWhisperOptions: {
+ invite: 'Invite them',
+ nothing: 'Do nothing',
+ },
teachersUnitePage: {
teachersUnite: 'Teachers Unite',
joinExpensifyOrg: 'Join Expensify.org in eliminating injustice around the world and help teachers split their expenses for classrooms in need!',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 42461e766b29..c0ae99273bcf 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -2397,6 +2397,10 @@ export default {
copy: 'Copiar',
copied: '¡Copiado!',
},
+ actionableMentionWhisperOptions: {
+ invite: 'Invitar',
+ nothing: 'No hacer nada',
+ },
moderation: {
flagDescription: 'Todos los mensajes marcados se enviarán a un moderador para su revisión.',
chooseAReason: 'Elige abajo un motivo para reportarlo:',
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index 01c35d3088e1..0bdff9574f58 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -336,6 +336,7 @@ function addActions(reportID: string, text = '', file?: File) {
reportComment?: string;
file?: File;
timezone?: string;
+ shouldAllowActionableMentionWhispers?: boolean;
clientCreatedTime?: string;
};
@@ -345,6 +346,7 @@ function addActions(reportID: string, text = '', file?: File) {
commentReportActionID: file && reportCommentAction ? reportCommentAction.reportActionID : null,
reportComment: reportCommentText,
file,
+ shouldAllowActionableMentionWhispers: true,
clientCreatedTime: file ? attachmentAction?.created : reportCommentAction?.created,
};
@@ -2546,6 +2548,60 @@ function clearNewRoomFormError() {
});
}
+function resolveActionableMentionWhisper(reportId: string, reportAction: OnyxEntry, resolution: ValueOf) {
+ const message = reportAction?.message?.[0];
+ if (!message) {
+ return;
+ }
+
+ const updatedMessage: Message = {
+ ...message,
+ resolution,
+ };
+
+ const optimisticData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportId}`,
+ value: {
+ [reportAction.reportActionID]: {
+ message: [updatedMessage],
+ originalMessage: {
+ resolution,
+ },
+ },
+ },
+ },
+ ];
+
+ const failureData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportId}`,
+ value: {
+ [reportAction.reportActionID]: {
+ message: [message],
+ originalMessage: {
+ resolution: null,
+ },
+ },
+ },
+ },
+ ];
+
+ type ResolveActionableMentionWhisperParams = {
+ reportActionID: string;
+ resolution: ValueOf;
+ };
+
+ const parameters: ResolveActionableMentionWhisperParams = {
+ reportActionID: reportAction.reportActionID,
+ resolution,
+ };
+
+ API.write('ResolveActionableMentionWhisper', parameters, {optimisticData, failureData});
+}
+
export {
searchInServer,
addComment,
@@ -2610,4 +2666,5 @@ export {
getDraftPrivateNote,
updateLastVisitTime,
clearNewRoomFormError,
+ resolveActionableMentionWhisper,
};
diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js
index a7972b97f5c1..08d5447067f3 100644
--- a/src/pages/home/report/ReportActionItem.js
+++ b/src/pages/home/report/ReportActionItem.js
@@ -17,6 +17,7 @@ import PressableWithSecondaryInteraction from '@components/PressableWithSecondar
import EmojiReactionsPropTypes from '@components/Reactions/EmojiReactionsPropTypes';
import ReportActionItemEmojiReactions from '@components/Reactions/ReportActionItemEmojiReactions';
import RenderHTML from '@components/RenderHTML';
+import ActionableItemButtons from '@components/ReportActionItem/ActionableItemButtons';
import ChronosOOOListActions from '@components/ReportActionItem/ChronosOOOListActions';
import MoneyReportView from '@components/ReportActionItem/MoneyReportView';
import MoneyRequestAction from '@components/ReportActionItem/MoneyRequestAction';
@@ -301,6 +302,25 @@ function ReportActionItem(props) {
[props.report, props.action, toggleContextMenuFromActiveReportAction],
);
+ const actionableItemButtons = useMemo(() => {
+ if (!(props.action.actionName === CONST.REPORT.ACTIONS.TYPE.ACTIONABLEMENTIONWHISPER && !lodashGet(props.action, 'originalMessage.resolution', null))) {
+ return [];
+ }
+ return [
+ {
+ text: 'actionableMentionWhisperOptions.invite',
+ key: `${props.action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE}`,
+ onPress: () => Report.resolveActionableMentionWhisper(props.report.reportID, props.action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE),
+ isPrimary: true,
+ },
+ {
+ text: 'actionableMentionWhisperOptions.nothing',
+ key: `${props.action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING}`,
+ onPress: () => Report.resolveActionableMentionWhisper(props.report.reportID, props.action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING),
+ },
+ ];
+ }, [props.action, props.report]);
+
/**
* Get the content of ReportActionItem
* @param {Boolean} hovered whether the ReportActionItem is hovered
@@ -461,6 +481,17 @@ function ReportActionItem(props) {
)}
+ {/**
+ These are the actionable buttons that appear at the bottom of a Concierge message
+ for example: Invite a user mentioned but not a member of the room
+ https://github.com/Expensify/App/issues/32741
+ */}
+ {actionableItemButtons.length > 0 && (
+
+ )}
) : (
;
type OriginalMessageApproved = {
actionName: typeof CONST.REPORT.ACTIONS.TYPE.APPROVED;
@@ -109,6 +110,18 @@ type OriginalMessageAddComment = {
};
};
+type OriginalMessageActionableMentionWhisper = {
+ actionName: typeof CONST.REPORT.ACTIONS.TYPE.ACTIONABLEMENTIONWHISPER;
+ originalMessage: {
+ inviteeAccountIDs: number[];
+ inviteeEmails: string;
+ lastModified: string;
+ reportID: number;
+ resolution?: ValueOf | null;
+ whisperedTo?: number[];
+ };
+};
+
type OriginalMessageSubmitted = {
actionName: typeof CONST.REPORT.ACTIONS.TYPE.SUBMITTED;
originalMessage: unknown;
@@ -239,6 +252,7 @@ type OriginalMessage =
| OriginalMessageApproved
| OriginalMessageIOU
| OriginalMessageAddComment
+ | OriginalMessageActionableMentionWhisper
| OriginalMessageSubmitted
| OriginalMessageClosed
| OriginalMessageCreated
diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts
index a881b63fbb95..ea987a903b1b 100644
--- a/src/types/onyx/ReportAction.ts
+++ b/src/types/onyx/ReportAction.ts
@@ -52,6 +52,9 @@ type Message = {
/** ID of a task report */
taskReportID?: string;
+
+ /** resolution for actionable mention whisper */
+ resolution?: ValueOf | null;
};
type ImageMetadata = {