diff --git a/src/CONST.ts b/src/CONST.ts index feae7c07c66a..67f3772b129c 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -65,6 +65,21 @@ const chatTypes = { SYSTEM: 'system', } as const; +const ONBOARDING_ACCOUNTING_MAPPING = { + quickbooksOnline: 'QuickBooks Online', + xero: 'Xero', + netsuite: 'NetSuite', + netsuiteQuickStart: 'NSQS', + intacct: 'Sage Intacct', + quickbooksDesktop: 'QuickBooks Desktop', +}; + +const connectionsVideoPaths = { + [ONBOARDING_ACCOUNTING_MAPPING.quickbooksOnline]: 'videos/walkthrough-connect_to_qbo-v2.mp4', + [ONBOARDING_ACCOUNTING_MAPPING.xero]: 'videos/walkthrough-connect_to_xero-v2.mp4', + [ONBOARDING_ACCOUNTING_MAPPING.netsuite]: 'videos/walkthrough-connect_to_netsuite-v2.mp4', +}; + // Explicit type annotation is required const cardActiveStates: number[] = [2, 3, 4, 7]; @@ -105,6 +120,7 @@ const signupQualifiers = { const selfGuidedTourTask: OnboardingTask = { type: 'viewTour', autoCompleted: false, + mediaAttributes: {}, title: 'Take a 2-minute tour', description: ({navatticURL}) => `[Take a self-guided product tour](${navatticURL}) and learn about everything Expensify has to offer.`, }; @@ -112,6 +128,7 @@ const selfGuidedTourTask: OnboardingTask = { const createWorkspaceTask: OnboardingTask = { type: 'createWorkspace', autoCompleted: true, + mediaAttributes: {}, title: 'Create a workspace', description: ({workspaceSettingsLink}) => '*Create a workspace* to track expenses, scan receipts, chat, and more.\n' + @@ -127,6 +144,9 @@ const createWorkspaceTask: OnboardingTask = { const setupCategoriesTask: OnboardingTask = { type: 'setupCategories', autoCompleted: false, + mediaAttributes: { + [`${CLOUDFRONT_URL}/videos/walkthrough-categories-v2.mp4`]: `data-expensify-thumbnail-url="${CLOUDFRONT_URL}/images/walkthrough-categories.png" data-expensify-width="1920" data-expensify-height="1080"`, + }, title: 'Set up categories', description: ({workspaceCategoriesLink}) => '*Set up categories* so your team can code expenses for easy reporting.\n' + @@ -140,7 +160,9 @@ const setupCategoriesTask: OnboardingTask = { "5. Disable any categories you don't need.\n" + '6. Add your own categories in the top right.\n' + '\n' + - `[Take me to workspace category settings](${workspaceCategoriesLink}).`, + `[Take me to workspace category settings](${workspaceCategoriesLink}).\n` + + '\n' + + `![Set up categories](${CLOUDFRONT_URL}/videos/walkthrough-categories-v2.mp4)`, }; const onboardingEmployerOrSubmitMessage: OnboardingMessage = { @@ -150,6 +172,7 @@ const onboardingEmployerOrSubmitMessage: OnboardingMessage = { { type: 'submitExpense', autoCompleted: false, + mediaAttributes: {}, title: 'Submit an expense', description: '*Submit an expense* by entering an amount or scanning a receipt.\n' + @@ -173,6 +196,7 @@ const combinedTrackSubmitOnboardingEmployerOrSubmitMessage: OnboardingMessage = { type: 'submitExpense', autoCompleted: false, + mediaAttributes: {}, title: 'Submit an expense', description: '*Submit an expense* by entering an amount or scanning a receipt.\n' + @@ -197,6 +221,7 @@ const onboardingPersonalSpendMessage: OnboardingMessage = { { type: 'trackExpense', autoCompleted: false, + mediaAttributes: {}, title: 'Track an expense', description: '*Track an expense* in any currency, whether you have a receipt or not.\n' + @@ -220,6 +245,7 @@ const combinedTrackSubmitOnboardingPersonalSpendMessage: OnboardingMessage = { { type: 'trackExpense', autoCompleted: false, + mediaAttributes: {}, title: 'Track an expense', description: '*Track an expense* in any currency, whether you have a receipt or not.\n' + @@ -262,6 +288,7 @@ type OnboardingInvite = ValueOf; type OnboardingTask = { type: string; autoCompleted: boolean; + mediaAttributes: Record; title: | string | (( @@ -1760,6 +1787,7 @@ const CONST = { REPORT: 'r', NOTE: 'n', SEARCH: 's', + ONBOARDING: 'o', }, IMAGE_HIGH_RESOLUTION_THRESHOLD: 7000, @@ -5201,14 +5229,7 @@ const CONST = { ONBOARDING_INVITE_TYPES: {...onboardingInviteTypes}, ONBOARDING_COMPANY_SIZE: {...onboardingCompanySize}, ACTIONABLE_TRACK_EXPENSE_WHISPER_MESSAGE: 'What would you like to do with this expense?', - ONBOARDING_ACCOUNTING_MAPPING: { - quickbooksOnline: 'QuickBooks Online', - xero: 'Xero', - netsuite: 'NetSuite', - netsuiteQuickStart: 'NSQS', - intacct: 'Sage Intacct', - quickbooksDesktop: 'QuickBooks Desktop', - }, + ONBOARDING_ACCOUNTING_MAPPING, ONBOARDING_MESSAGES: { [onboardingChoices.EMPLOYER]: onboardingEmployerOrSubmitMessage, [onboardingChoices.SUBMIT]: onboardingEmployerOrSubmitMessage, @@ -5220,6 +5241,7 @@ const CONST = { { type: 'setupCategoriesAndTags', autoCompleted: false, + mediaAttributes: {}, title: 'Set up categories and tags', description: ({workspaceCategoriesLink, workspaceAccountingLink}) => '*Set up categories and tags* so your team can code expenses for easy reporting.\n' + @@ -5231,6 +5253,9 @@ const CONST = { type: 'setupTags', autoCompleted: false, title: 'Set up tags', + mediaAttributes: { + [`${CLOUDFRONT_URL}/videos/walkthrough-tags-v2.mp4`]: `data-expensify-thumbnail-url="${CLOUDFRONT_URL}/images/walkthrough-tags.png" data-expensify-width="1920" data-expensify-height="1080"`, + }, description: ({workspaceMoreFeaturesLink}) => 'Tags can be used if you want more details with every expense. Use tags for projects, clients, locations, departments, and more. If you need multiple levels of tags, you can upgrade to the Control plan.\n' + '\n' + @@ -5244,11 +5269,16 @@ const CONST = { '6. Navigate to *Tags* in the workspace editor.\n' + '7. Click *+ Add tag* to make your own.\n' + '\n' + - `[Take me to more features](${workspaceMoreFeaturesLink}).`, + `[Take me to more features](${workspaceMoreFeaturesLink}).\n` + + '\n' + + `![Set up tags](${CLOUDFRONT_URL}/videos/walkthrough-tags-v2.mp4)`, }, { type: 'addExpenseApprovals', autoCompleted: false, + mediaAttributes: { + [`${CLOUDFRONT_URL}/videos/walkthrough-approvals-v2.mp4`]: `data-expensify-thumbnail-url="${CLOUDFRONT_URL}/images/walkthrough-approvals.png" data-expensify-width="1920" data-expensify-height="1080"`, + }, title: 'Add expense approvals', description: ({workspaceMoreFeaturesLink}) => '*Add expense approvals* to review your team’s spend and keep it under control.\n' + @@ -5264,11 +5294,16 @@ const CONST = { '7. Enable *Add approvals*.\n' + '8. You’ll be set as the expense approver. You can change this to any admin once you invite your team.\n' + '\n' + - `[Take me to more features](${workspaceMoreFeaturesLink}).`, + `[Take me to more features](${workspaceMoreFeaturesLink}).\n` + + '\n' + + `![Add expense approvals](${CLOUDFRONT_URL}/videos/walkthrough-approvals-v2.mp4)`, }, { type: 'inviteTeam', autoCompleted: false, + mediaAttributes: { + [`${CLOUDFRONT_URL}/videos/walkthrough-invite_members-v2.mp4`]: `data-expensify-thumbnail-url="${CLOUDFRONT_URL}/images/walkthrough-invite_members.png" data-expensify-width="1920" data-expensify-height="1080"`, + }, title: 'Invite your team', description: ({workspaceMembersLink}) => '*Invite your team* to Expensify so they can start tracking expenses today.\n' + @@ -5282,11 +5317,24 @@ const CONST = { '5. Enter emails or phone numbers. \n' + '6. Add a custom invite message if you’d like!\n' + '\n' + - `[Take me to workspace members](${workspaceMembersLink}).`, + `[Take me to workspace members](${workspaceMembersLink}).\n` + + '\n' + + `![Invite your team](${CLOUDFRONT_URL}/videos/walkthrough-invite_members-v2.mp4)`, }, { type: 'addAccountingIntegration', autoCompleted: false, + mediaAttributes: { + [`${CLOUDFRONT_URL}/${ + connectionsVideoPaths[ONBOARDING_ACCOUNTING_MAPPING.netsuite] + }`]: `data-expensify-thumbnail-url="${CLOUDFRONT_URL}/images/walkthrough-connect_to_netsuite.png" data-expensify-width="1920" data-expensify-height="1080"`, + [`${CLOUDFRONT_URL}/${ + connectionsVideoPaths[ONBOARDING_ACCOUNTING_MAPPING.quickbooksOnline] + }`]: `data-expensify-thumbnail-url="${CLOUDFRONT_URL}/images/walkthrough-connect_to_qbo.png" data-expensify-width="1920" data-expensify-height="1080"`, + [`${CLOUDFRONT_URL}/${ + connectionsVideoPaths[ONBOARDING_ACCOUNTING_MAPPING.xero] + }`]: `data-expensify-thumbnail-url="${CLOUDFRONT_URL}/images/walkthrough-connect_to_xero.png" data-expensify-width="1920" data-expensify-height="1080"`, + }, title: ({integrationName}) => `Connect to ${integrationName}`, description: ({integrationName, workspaceAccountingLink}) => `Connect to ${integrationName} for automatic expense coding and syncing that makes month-end close a breeze.\n` + @@ -5300,7 +5348,11 @@ const CONST = { `5. Find ${integrationName}.\n` + '6. Click *Connect*.\n' + '\n' + - `[Take me to accounting](${workspaceAccountingLink}).`, + `${ + integrationName && connectionsVideoPaths[integrationName] + ? `[Take me to accounting](${workspaceAccountingLink}).\n\n![Connect to ${integrationName}](${CLOUDFRONT_URL}/${connectionsVideoPaths[integrationName]})` + : `[Take me to accounting](${workspaceAccountingLink}).` + }`, }, ], }, @@ -5319,6 +5371,7 @@ const CONST = { { type: 'inviteAccountant', autoCompleted: false, + mediaAttributes: {}, title: 'Invite your accountant', description: ({workspaceMembersLink}) => '*Invite your accountant* to Expensify and share your expenses with them to make tax time easier.\n' + @@ -5347,6 +5400,7 @@ const CONST = { { type: 'startChat', autoCompleted: false, + mediaAttributes: {}, title: 'Start a chat', description: '*Start a chat* with a friend or group using their email or phone number.\n' + @@ -5364,6 +5418,7 @@ const CONST = { { type: 'splitExpense', autoCompleted: false, + mediaAttributes: {}, title: 'Split an expense', description: '*Split an expense* right in your chat with one or more friends.\n' + @@ -5386,6 +5441,7 @@ const CONST = { { type: 'reviewWorkspaceSettings', autoCompleted: false, + mediaAttributes: {}, title: 'Review your workspace settings', description: "Here's how to review and update your workspace settings:" + @@ -5398,6 +5454,7 @@ const CONST = { { type: 'submitExpense', autoCompleted: false, + mediaAttributes: {}, title: 'Submit an expense', description: '*Submit an expense* by entering an amount or scanning a receipt.\n' + diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachments.ts b/src/components/Attachments/AttachmentCarousel/extractAttachments.ts index 62660980f56f..1d4f8828400c 100644 --- a/src/components/Attachments/AttachmentCarousel/extractAttachments.ts +++ b/src/components/Attachments/AttachmentCarousel/extractAttachments.ts @@ -2,9 +2,9 @@ import {Parser as HtmlParser} from 'htmlparser2'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {Attachment} from '@components/Attachments/types'; -import * as FileUtils from '@libs/fileDownload/FileUtils'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getFileName, splitExtensionFromFileName} from '@libs/fileDownload/FileUtils'; +import {getReportActionHtml, getReportActionMessage, getSortedReportActions, isMoneyRequestAction, shouldReportActionBeVisible} from '@libs/ReportActionsUtils'; +import {canUserPerformWriteAction} from '@libs/ReportUtils'; import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import CONST from '@src/CONST'; import type {Report, ReportAction, ReportActions} from '@src/types/onyx'; @@ -24,8 +24,9 @@ function extractAttachments( }: {privateNotes?: Record; accountID?: number; parentReportAction?: OnyxEntry; reportActions?: OnyxEntry; report: OnyxEntry}, ) { const targetNote = privateNotes?.[Number(accountID)]?.note ?? ''; + const description = report?.description ?? ''; const attachments: Attachment[] = []; - const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); + const canUserPerformAction = canUserPerformWriteAction(report); // We handle duplicate image sources by considering the first instance as original. Selecting any duplicate // and navigating back (<) shows the image preceding the first instance, not the selected duplicate's position. @@ -45,7 +46,7 @@ function extractAttachments( } uniqueSourcesAndLinks.add(source); - const fileName = attribs[CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE] || FileUtils.getFileName(`${source}`); + const fileName = attribs[CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE] || getFileName(`${source}`); attachments.unshift({ source: tryResolveUrlFromApiRoot(attribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]), isAuthTokenRequired: !!attribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE], @@ -69,7 +70,7 @@ function extractAttachments( uniqueSourcesAndLinks.add(sourceLinkKey); - let fileName = attribs[CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE] || FileUtils.getFileName(`${source}`); + let fileName = attribs[CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE] || getFileName(`${source}`); const width = (attribs['data-expensify-width'] && parseInt(attribs['data-expensify-width'], 10)) || undefined; const height = (attribs['data-expensify-height'] && parseInt(attribs['data-expensify-height'], 10)) || undefined; @@ -77,7 +78,7 @@ function extractAttachments( // Public image URLs might lack a file extension in the source URL, without an extension our // AttachmentView fails to recognize them as images and renders fallback content instead. // We apply this small hack to add an image extension and ensure AttachmentView renders the image. - const fileInfo = FileUtils.splitExtensionFromFileName(fileName); + const fileInfo = splitExtensionFromFileName(fileName); if (!fileInfo.fileExtension) { fileName = `${fileInfo.fileName || 'image'}.jpg`; } @@ -112,15 +113,22 @@ function extractAttachments( return attachments.reverse(); } - const actions = [...(parentReportAction ? [parentReportAction] : []), ...ReportActionsUtils.getSortedReportActions(Object.values(reportActions ?? {}))]; + if (type === CONST.ATTACHMENT_TYPE.ONBOARDING) { + htmlParser.write(description); + htmlParser.end(); + + return attachments.reverse(); + } + + const actions = [...(parentReportAction ? [parentReportAction] : []), ...getSortedReportActions(Object.values(reportActions ?? {}))]; actions.forEach((action, key) => { - if (!ReportActionsUtils.shouldReportActionBeVisible(action, key, canUserPerformWriteAction) || ReportActionsUtils.isMoneyRequestAction(action)) { + if (!shouldReportActionBeVisible(action, key, canUserPerformAction) || isMoneyRequestAction(action)) { return; } - const decision = ReportActionsUtils.getReportActionMessage(action)?.moderationDecision?.decision; + const decision = getReportActionMessage(action)?.moderationDecision?.decision; const hasBeenFlagged = decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_HIDE || decision === CONST.MODERATION.MODERATOR_DECISION_HIDDEN; - const html = ReportActionsUtils.getReportActionHtml(action).replace('/>', `data-flagged="${hasBeenFlagged}" data-id="${action.reportActionID}"/>`); + const html = getReportActionHtml(action).replace('/>', `data-flagged="${hasBeenFlagged}" data-id="${action.reportActionID}"/>`); htmlParser.write(html); }); htmlParser.end(); diff --git a/src/components/Attachments/AttachmentCarousel/index.native.tsx b/src/components/Attachments/AttachmentCarousel/index.native.tsx index 6156654486f2..0cfa95c8393d 100644 --- a/src/components/Attachments/AttachmentCarousel/index.native.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.native.tsx @@ -35,6 +35,8 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi let newAttachments: Attachment[] = []; if (type === CONST.ATTACHMENT_TYPE.NOTE && accountID) { newAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.NOTE, {privateNotes: report.privateNotes, accountID, report}); + } else if (type === CONST.ATTACHMENT_TYPE.ONBOARDING) { + newAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.ONBOARDING, {parentReportAction, reportActions: reportActions ?? undefined, report}); } else { newAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.REPORT, {parentReportAction, reportActions, report}); } diff --git a/src/components/Attachments/AttachmentCarousel/index.tsx b/src/components/Attachments/AttachmentCarousel/index.tsx index 6fb20e66df4f..b38a45a1bf63 100644 --- a/src/components/Attachments/AttachmentCarousel/index.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.tsx @@ -90,6 +90,8 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi let newAttachments: Attachment[] = []; if (type === CONST.ATTACHMENT_TYPE.NOTE && accountID) { newAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.NOTE, {privateNotes: report.privateNotes, accountID, report}); + } else if (type === CONST.ATTACHMENT_TYPE.ONBOARDING) { + newAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.ONBOARDING, {parentReportAction, reportActions: reportActions ?? undefined, report}); } else { newAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.REPORT, {parentReportAction, reportActions: reportActions ?? undefined, report}); } diff --git a/src/components/ReportActionItem/TaskView.tsx b/src/components/ReportActionItem/TaskView.tsx index decb20f8212a..bc12663e5170 100644 --- a/src/components/ReportActionItem/TaskView.tsx +++ b/src/components/ReportActionItem/TaskView.tsx @@ -1,6 +1,7 @@ -import React, {useEffect} from 'react'; +import React, {useEffect, useMemo} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; +import {AttachmentContext} from '@components/AttachmentContext'; import Checkbox from '@components/Checkbox'; import Hoverable from '@components/Hoverable'; import Icon from '@components/Icon'; @@ -11,6 +12,7 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {usePersonalDetails} from '@components/OnyxProvider'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; import RenderHTML from '@components/RenderHTML'; +import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; @@ -53,128 +55,145 @@ function TaskView({report}: TaskViewProps) { const disableState = !canModifyTask; const isDisableInteractive = !canModifyTask || !isOpen; const {translate} = useLocalize(); + const accountID = currentUserPersonalDetails?.accountID ?? CONST.DEFAULT_NUMBER_ID; + const contextValue = useMemo( + () => ({ + anchor: null, + report, + reportNameValuePairs: undefined, + action: undefined, + transactionThreadReport: undefined, + checkIfContextMenuActive: () => {}, + isDisabled: true, + }), + [report], + ); + + const attachmentContextValue = useMemo(() => ({type: CONST.ATTACHMENT_TYPE.ONBOARDING, accountID}), [accountID]); return ( - - clearTaskErrors(report?.reportID)} - errorRowStyles={styles.ph5} - > - - {(hovered) => ( - { - if (isDisableInteractive) { - return; - } - if (e && e.type === 'click') { - (e.currentTarget as HTMLElement).blur(); - } + + + clearTaskErrors(report?.reportID)} + errorRowStyles={styles.ph5} + > + + {(hovered) => ( + { + if (isDisableInteractive) { + return; + } + if (e && e.type === 'click') { + (e.currentTarget as HTMLElement).blur(); + } - Navigation.navigate(ROUTES.TASK_TITLE.getRoute(report?.reportID, Navigation.getReportRHPActiveRoute())); - })} - style={({pressed}) => [ - styles.ph5, - styles.pv2, - StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, false, disableState, !isDisableInteractive), true), - isDisableInteractive && styles.cursorDefault, - ]} - accessibilityLabel={taskTitle || translate('task.task')} - disabled={isDisableInteractive} - > - {({pressed}) => ( - - {translate('task.title')} - - { - // If we're already navigating to these task editing pages, early return not to mark as completed, otherwise we would have not found page. - if (isActiveTaskEditRoute(report?.reportID)) { - return; - } - if (isCompleted) { - reopenTask(report); - } else { - completeTask(report); - } - })} - isChecked={isCompleted} - style={styles.taskMenuItemCheckbox} - containerSize={24} - containerBorderRadius={8} - caretSize={16} - accessibilityLabel={taskTitle || translate('task.task')} - disabled={!canActionTask} - /> + Navigation.navigate(ROUTES.TASK_TITLE.getRoute(report?.reportID, Navigation.getReportRHPActiveRoute())); + })} + style={({pressed}) => [ + styles.ph5, + styles.pv2, + StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, false, disableState, !isDisableInteractive), true), + isDisableInteractive && styles.cursorDefault, + ]} + accessibilityLabel={taskTitle || translate('task.task')} + disabled={isDisableInteractive} + > + {({pressed}) => ( + + {translate('task.title')} - - - {!isDisableInteractive && ( - - + { + // If we're already navigating to these task editing pages, early return not to mark as completed, otherwise we would have not found page. + if (isActiveTaskEditRoute(report?.reportID)) { + return; + } + if (isCompleted) { + reopenTask(report); + } else { + completeTask(report); + } + })} + isChecked={isCompleted} + style={styles.taskMenuItemCheckbox} + containerSize={24} + containerBorderRadius={8} + caretSize={16} + accessibilityLabel={taskTitle || translate('task.task')} + disabled={!canActionTask} + /> + + - )} - - - )} - - )} - - - Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report?.reportID, Navigation.getReportRHPActiveRoute()))} - shouldShowRightIcon={!isDisableInteractive} - disabled={disableState} - wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} - shouldGreyOutWhenDisabled={false} - numberOfLinesTitle={0} - interactive={!isDisableInteractive} - shouldUseDefaultCursorWhenDisabled - /> - - - {report?.managerID ? ( - Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report?.reportID, Navigation.getReportRHPActiveRoute()))} - shouldShowRightIcon={!isDisableInteractive} - disabled={disableState} - wrapperStyle={[styles.pv2]} - isSmallAvatarSubscriptMenu - shouldGreyOutWhenDisabled={false} - interactive={!isDisableInteractive} - titleWithTooltips={assigneeTooltipDetails} - shouldUseDefaultCursorWhenDisabled - /> - ) : ( + {!isDisableInteractive && ( + + + + )} + + + )} + + )} + + Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report?.reportID, Navigation.getReportRHPActiveRoute()))} + shouldRenderAsHTML + description={translate('task.description')} + title={report?.description ?? ''} + onPress={() => Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report?.reportID, Navigation.getReportRHPActiveRoute()))} shouldShowRightIcon={!isDisableInteractive} disabled={disableState} - wrapperStyle={[styles.pv2]} + wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} shouldGreyOutWhenDisabled={false} + numberOfLinesTitle={0} interactive={!isDisableInteractive} shouldUseDefaultCursorWhenDisabled /> - )} + + + {report?.managerID ? ( + Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report?.reportID, Navigation.getReportRHPActiveRoute()))} + shouldShowRightIcon={!isDisableInteractive} + disabled={disableState} + wrapperStyle={[styles.pv2]} + isSmallAvatarSubscriptMenu + shouldGreyOutWhenDisabled={false} + interactive={!isDisableInteractive} + titleWithTooltips={assigneeTooltipDetails} + shouldUseDefaultCursorWhenDisabled + /> + ) : ( + Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report?.reportID, Navigation.getReportRHPActiveRoute()))} + shouldShowRightIcon={!isDisableInteractive} + disabled={disableState} + wrapperStyle={[styles.pv2]} + shouldGreyOutWhenDisabled={false} + interactive={!isDisableInteractive} + shouldUseDefaultCursorWhenDisabled + /> + )} + - - + + ); } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index d69ffd1d113b..7983703533fb 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4825,7 +4825,7 @@ function completeShortMention(text: string): string { * For comments shorter than or equal to 10k chars, convert the comment from MD into HTML because that's how it is stored in the database * For longer comments, skip parsing, but still escape the text, and display plaintext for performance reasons. It takes over 40s to parse a 100k long string!! */ -function getParsedComment(text: string, parsingDetails?: ParsingDetails): string { +function getParsedComment(text: string, parsingDetails?: ParsingDetails, mediaAttributes?: Record): string { let isGroupPolicyReport = false; if (parsingDetails?.reportID) { const currentReport = getReportOrDraftReport(parsingDetails?.reportID); @@ -4842,7 +4842,11 @@ function getParsedComment(text: string, parsingDetails?: ParsingDetails): string const textWithMention = completeShortMention(text); return text.length <= CONST.MAX_MARKUP_LENGTH - ? Parser.replace(textWithMention, {shouldEscapeText: parsingDetails?.shouldEscapeText, disabledRules: isGroupPolicyReport ? [] : ['reportMentions']}) + ? Parser.replace(textWithMention, { + shouldEscapeText: parsingDetails?.shouldEscapeText, + disabledRules: isGroupPolicyReport ? [] : ['reportMentions'], + extras: {mediaAttributeCache: mediaAttributes}, + }) : lodashEscape(text); } @@ -6706,6 +6710,7 @@ function buildOptimisticTaskReport( description?: string, policyID: string = CONST.POLICY.OWNER_EMAIL_FAKE, notificationPreference: NotificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + mediaAttributes?: Record, ): OptimisticTaskReport { const participants: Participants = { [ownerAccountID]: { @@ -6720,7 +6725,7 @@ function buildOptimisticTaskReport( return { reportID: generateReportID(), reportName: getParsedComment(title ?? ''), - description: getParsedComment(description ?? ''), + description: getParsedComment(description ?? '', {}, mediaAttributes), ownerAccountID, participants, managerID: assigneeAccountID, diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index df85a6119e1c..f24d9b182ec6 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3764,6 +3764,7 @@ function prepareOnboardingOnyxData( taskDescription, targetChatPolicyID, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + task.mediaAttributes, ); const emailCreatingAction = engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM ? allPersonalDetails?.[actorAccountID]?.login ?? CONST.EMAIL.CONCIERGE : CONST.EMAIL.CONCIERGE;