Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 70 additions & 13 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand Down Expand Up @@ -105,13 +120,15 @@ 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.`,
};

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' +
Expand All @@ -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' +
Expand All @@ -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 = {
Expand All @@ -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' +
Expand All @@ -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' +
Expand All @@ -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' +
Expand All @@ -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' +
Expand Down Expand Up @@ -262,6 +288,7 @@ type OnboardingInvite = ValueOf<typeof onboardingInviteTypes>;
type OnboardingTask = {
type: string;
autoCompleted: boolean;
mediaAttributes: Record<string, string>;
title:
| string
| ((
Expand Down Expand Up @@ -1760,6 +1787,7 @@ const CONST = {
REPORT: 'r',
NOTE: 'n',
SEARCH: 's',
ONBOARDING: 'o',
},

IMAGE_HIGH_RESOLUTION_THRESHOLD: 7000,
Expand Down Expand Up @@ -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,
Expand All @@ -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' +
Expand All @@ -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' +
Expand All @@ -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' +
Expand All @@ -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' +
Expand All @@ -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` +
Expand All @@ -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}).`
}`,
},
],
},
Expand All @@ -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' +
Expand Down Expand Up @@ -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' +
Expand All @@ -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' +
Expand All @@ -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:" +
Expand All @@ -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' +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -24,8 +24,9 @@ function extractAttachments(
}: {privateNotes?: Record<number, Note>; accountID?: number; parentReportAction?: OnyxEntry<ReportAction>; reportActions?: OnyxEntry<ReportActions>; report: OnyxEntry<Report>},
) {
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.
Expand All @@ -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],
Expand All @@ -69,15 +70,15 @@ 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;

// 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`;
}
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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});
}
Expand Down
2 changes: 2 additions & 0 deletions src/components/Attachments/AttachmentCarousel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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});
}
Expand Down
Loading