diff --git a/android/app/build.gradle b/android/app/build.gradle index bcac489f6828..afe24fc37700 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -90,8 +90,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001037402 - versionName "1.3.74-2" + versionCode 1001037403 + versionName "1.3.74-3" } flavorDimensions "default" diff --git a/docs/articles/expensify-classic/get-paid-back/expenses/Upload-Receipts.md b/docs/articles/expensify-classic/get-paid-back/expenses/Upload-Receipts.md index b71fd1a3c8bf..29380dab5a5b 100644 --- a/docs/articles/expensify-classic/get-paid-back/expenses/Upload-Receipts.md +++ b/docs/articles/expensify-classic/get-paid-back/expenses/Upload-Receipts.md @@ -1,5 +1,36 @@ --- -title: Upload Receipts -description: Upload Receipts +title: Upload-Receipts.md +description: This article shows you all the ways that you can upload your receipts to Expensify! --- -## Resource Coming Soon! + + +# About +Need to get paid? Check out this guide to see all the ways that you can upload your receipts to Expensify - whether it’s by SmartScanning them by forwarding via email or manually by taking a picture of a receipt, we’ll cover it here! + +# How-to Upload Receipts +## SmartScan +The easiest way to upload your receipts to Expensify is to SmartScan them with Expensify’s mobile app or forward a receipt from your email inbox! + +When you SmartScan a receipt, we’ll read the Merchant, Date and Amount of the transaction, create an expense, and add it to your Expensify account automatically. The best practice is to take a picture of the receipt at the time of purchase or forward it to your Expensify account from the point of sale system. If you have a credit card connected and you upload a receipt that matches a card expense, the SmartScanned receipt will automatically merge with the imported card expense instead. + +## Email Receipts +To SmartScan a receipt on your mobile app, tap the green camera button, point and shoot! You can also forward your digital receipts (or photos of receipts) to receipts@expensify.com from the email address associated with your Expensify account, and they’ll be SmartScanned. This may take a few minutes because Expensify aims to have the most accurate OCR. + +## Manually Upload +To upload receipts on the web, simply navigate to the Expenses page and click on **New Expense**. Select **Scan Receipt** and choose the file you would like to upload, or drag-and-drop your image directly into the Expenses page, and that will start the SmartScanning process! + +# FAQ +## How do you SmartScan multiple receipts? +You can utilize the Rapid Fire Mode to quickly SmartScan multiple receipts at once! + +To activate it, tap on the green camera button in the mobile app and then tap on the camera icon on the bottom right. When you see the little fire icon on the camera, Rapid Fire Mode has been activated - tap the camera icon again to disable Rapid Fire Mode. + +## How do you create an expense from an email address that is different from your Expensify login? +You can email a receipt from a different email address by adding it as a Secondary Login to your Expensify account - this ensures that any receipts sent from this email to receipts@expensify.com will be associated with your current Expensify account. + +Once that email address has been added as a Secondary Login, simply forward your receipt image or emails to receipts@expensify.com. + +## How do you crop or rotate a receipt image? +You can crop and rotate a receipt image on the web app, and you can only edit one expense at a time. + +Navigate to your Expenses page and locate the expense whose receipt image you'd like to edit, then click the expense to open the Edit screen. If there is an image file associated with the receipt, you will see the Rotate and Crop buttons. Alternatively, you can also navigate to your Reports page, click on a report, and locate the individual expense. diff --git a/docs/articles/expensify-classic/get-paid-back/reports/Reimbursements.md b/docs/articles/expensify-classic/get-paid-back/reports/Reimbursements.md index c2cc25b32373..a31c0a582fd7 100644 --- a/docs/articles/expensify-classic/get-paid-back/reports/Reimbursements.md +++ b/docs/articles/expensify-classic/get-paid-back/reports/Reimbursements.md @@ -1,5 +1,42 @@ ---- -title: Reimbursements -description: Reimbursements ---- -## Resource Coming Soon! +# Overview + +If you want to know more about how and when you’ll be reimbursed through Expensify, we’ve answered your questions below. + +# How to Get Reimbursed + +To get paid back after submitting a report for reimbursement, you’ll want to be sure to connect your bank account. You can do that under **Settings** > **Account** > **Payments** > **Add a Deposit Account**. Once your employer has approved your report, the reimbursement will be paid into the account you added. + +# Deep Dive + +## Reimbursement Timing + +### US Bank Accounts + +If your company uses Expensify's ACH reimbursement we'll first check to see if the report is eligible for Rapid Reimbursement (next business day). For a report to be eligible for Rapid Reimbursement it must fall under two limits: + + - $100 per deposit bank account per day or less for the individuals being reimbursed or businesses receiving payments for bills. + - Less than $10,000 being disbursed in a 24-hour time period from the verified bank account being used to pay the reimbursement. + +If the request passes both checks, then you can expect to see funds deposited into your bank account on the next business day. + +If either limit has been reached, then you can expect to see funds deposited within your bank account within the typical ACH timeframe of 3-5 business days. + +### International Bank Accounts + +If receiving reimbursement to an international deposit account via Global Reimbursement, you should expect to see funds deposited in your bank account within 4 business days. + +## Bank Processing Timeframes + +Banks only process transactions and ACH activity on weekdays that are not bank holidays. These are considered business days. Additionally, the business day on which a transaction will be processed depends upon whether or not a request is created before or after the cutoff time, which is typically 3 pm PST. +For example, if your reimbursement is initiated at 4 pm on Wednesday, this is past the bank's cutoff time, and it will not begin processing until the next business day. +If that same reimbursement starts processing on Thursday, and it's estimated to take 3-5 business days, this will cover a weekend, and both days are not considered business days. So, assuming there are no bank holidays added into this mix, here is how that reimbursement timeline would play out: + +**Wednesday**: Reimbursement initiated after 3 pm PST; will be processed the next business day by your company’s bank. +**Thursday**: Your company's bank will begin processing the withdrawal request +**Friday**: Business day 1 +**Saturday**: Weekend +**Sunday**: Weekend +**Monday**: Business day 2 +**Tuesday**: Business day 3 +**Wednesday**: Business day 4 +**Thursday**: Business day 5 diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles.md index 3ee1c8656b4b..a65dc378a793 100644 --- a/docs/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles.md +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles.md @@ -1,5 +1,63 @@ --- -title: Coming Soon -description: Coming Soon +title: User Roles +description: Each member has a role that defines what they can see and do in the workspace. --- -## Resource Coming Soon! + +# Overview + +This guide is for those who are part of a **Group Workspace**. + +Each member has a role that defines what they can see and do in the workspace. Most members will have the role of "Employee." + +# How to Manage User Roles + +To find and edit the roles of group workspace members, go to **Settings > Workspaces > Group > [Your Specific Workspace Name] > Members > Workspace Members** + +Here you'll see the list of members in your group workspace. To change their roles, click **Settings** next to the member’s name and choose the role that the member needs. + +Next, let’s go over the various user roles that are available on a group workspace. + +## The Employee Role + +- **What can they do:** Employees can only see their own expense reports or reports that have been submitted to or shared with them. They can't change settings or invite new users. +- **Who is it for:** Regular employees who only need to manage their own expenses, or managers who are reviewing expense reports for a few users but don’t need global visibility. +- **Approvers:** Members who approve expenses can either be Employees, Admins, or Workspace Auditors, depending on how much control they need. +- **Billable:** Employees are billable actors if they take actions on a report on your Group Workspace (including **SmartScanning** a receipt). + +## Workspace Admin Role + +- **What can they do:** Admins have full control. They can change settings, invite members, and view all reports. They can also process reimbursements if they have access to the company’s account. +- **Billing Owners:** Billing owners are Admins by default. **Workspace Admins** are assigned by the owner or another admin. +- **Billable:** Yes, if they perform actions like changing settings or inviting users. Just viewing reports is not billable. + +## Workspace Auditor Role + +- **What can they do:** Workspace Auditors can see all reports, make comments, and export them. They can also mark reports as reimbursed if they're the final approver. +- **Who is it for:** Accountants, bookkeepers, and internal or external audit agents who need to view but not edit workspace settings. +- **Billable:** Yes, if they perform any actions like commenting or exporting a report. Viewing alone doesn't incur a charge. + +## Technical Contact + +- **What can they do:** In case of connection issues, alerts go to the billing owner by default. You can set a technical contact if you want alerts to go to an IT administrator instead. +- **How to set one:** Go to **Settings > Workspaces > Group > [Workspace Name] > Connections > Technical Contact**. +- **Billable:** The technical contact doesn’t need to be a group workspace member and so is not counted towards your billable activity. + +Note: running expense analytics from **Insights** follows the same rules. All the reports and data graphs you generate will be created based on the expense data you have access to. + +# Deep Dive + +## Expense Data Visibility + +The amount of expense data you can see depends on your role within any group workspaces you're part of: + +- **Employees:** Whether you're on a free or paid plan, if you're not approving expenses, you'll only see your own expenses. +- **Approvers:** If you approve expenses for your team and also submit your own, you can view both individual and team-wide expenses and analytics. +- **Admins:** Users with an admin role can see analytics and data for every expense report made by anyone on the workspace. + +If you need to see more data, here are some options: + +- **Become an Admin:** Check within your organization if you can be upgraded to an admin role in your group workspaces. +- **Become a Copilot:** Ask to be added as a **Copilot** to an existing admin account, which will allow you some additional viewing privileges. +- **Become an Approver:** You could also be added as an **Approver** in an existing workflow to view more data. + + diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index f41740a8bcb2..73e22053eda1 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.3.74.2 + 1.3.74.3 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 95714ea2cc9f..5e7f02699579 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.3.74.2 + 1.3.74.3 diff --git a/package-lock.json b/package-lock.json index 64ee3cf6308f..8c63ba6ce9b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.74-2", + "version": "1.3.74-3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.74-2", + "version": "1.3.74-3", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -90,7 +90,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.87", + "react-native-onyx": "1.0.89", "react-native-pager-view": "^6.2.0", "react-native-pdf": "^6.7.1", "react-native-performance": "^5.1.0", @@ -41204,9 +41204,9 @@ } }, "node_modules/react-native-onyx": { - "version": "1.0.87", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.87.tgz", - "integrity": "sha512-6mIhobSwpClDDGnJm9XEdjnpEdWfFesJ18J8Ifsb4tL6AVi+uxos5bnlZcOoMbtlUk3UozrgSyTjMfFrkD/aZA==", + "version": "1.0.89", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.89.tgz", + "integrity": "sha512-bSC8YwVbMBJYm6BMtuhuYmZi6zMh13e1t8Kaxp7K5EDLcSoTWsWPkuWX4wBvewlkLfw+HgB1IdgnXpa6+jS+ag==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -77269,9 +77269,9 @@ } }, "react-native-onyx": { - "version": "1.0.87", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.87.tgz", - "integrity": "sha512-6mIhobSwpClDDGnJm9XEdjnpEdWfFesJ18J8Ifsb4tL6AVi+uxos5bnlZcOoMbtlUk3UozrgSyTjMfFrkD/aZA==", + "version": "1.0.89", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.89.tgz", + "integrity": "sha512-bSC8YwVbMBJYm6BMtuhuYmZi6zMh13e1t8Kaxp7K5EDLcSoTWsWPkuWX4wBvewlkLfw+HgB1IdgnXpa6+jS+ag==", "requires": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", diff --git a/package.json b/package.json index cd93f718679e..d013caa1c402 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.74-2", + "version": "1.3.74-3", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -133,7 +133,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.87", + "react-native-onyx": "1.0.89", "react-native-pager-view": "^6.2.0", "react-native-pdf": "^6.7.1", "react-native-performance": "^5.1.0", diff --git a/src/CONST.ts b/src/CONST.ts index 8aee1b9f1af4..dbe47c6ed1a7 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -440,6 +440,12 @@ const CONST = { INTERNAL_DEV_EXPENSIFY_URL: 'https://www.expensify.com.dev', STAGING_EXPENSIFY_URL: 'https://staging.expensify.com', EXPENSIFY_URL: 'https://www.expensify.com', + BANK_ACCOUNT_PERSONAL_DOCUMENTATION_INFO_URL: + 'https://community.expensify.com/discussion/6983/faq-why-do-i-need-to-provide-personal-documentation-when-setting-up-updating-my-bank-account', + PERSONAL_DATA_PROTECTION_INFO_URL: 'https://community.expensify.com/discussion/5677/deep-dive-security-how-expensify-protects-your-information', + ONFIDO_FACIAL_SCAN_POLICY_URL: 'https://onfido.com/facial-scan-policy-and-release/', + ONFIDO_PRIVACY_POLICY_URL: 'https://onfido.com/privacy/', + ONFIDO_TERMS_OF_SERVICE_URL: 'https://onfido.com/terms-of-service/', // Use Environment.getEnvironmentURL to get the complete URL with port number DEV_NEW_EXPENSIFY_URL: 'http://localhost:', @@ -1226,6 +1232,7 @@ const CONST = { EMOJI_NAME: /:[\w+-]+:/g, EMOJI_SUGGESTIONS: /:[a-zA-Z0-9_+-]{1,40}$/, AFTER_FIRST_LINE_BREAK: /\n.*/g, + LINE_BREAK: /\n/g, CODE_2FA: /^\d{6}$/, ATTACHMENT_ID: /chat-attachments\/(\d+)/, HAS_COLON_ONLY_AT_THE_BEGINNING: /^:[^:]+$/, @@ -1363,6 +1370,7 @@ const CONST = { MERCHANT: 'merchant', CATEGORY: 'category', RECEIPT: 'receipt', + DISTANCE: 'distance', TAG: 'tag', }, FOOTER: { diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 6649a33fe15e..d2b3031220f1 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -242,6 +242,7 @@ const ONYXKEYS = { POLICY_RECENTLY_USED_TAGS: 'policyRecentlyUsedTags_', WORKSPACE_INVITE_MEMBERS_DRAFT: 'workspaceInviteMembersDraft_', REPORT: 'report_', + REPORT_METADATA: 'reportMetadata_', REPORT_ACTIONS: 'reportActions_', REPORT_ACTIONS_DRAFTS: 'reportActionsDrafts_', REPORT_ACTIONS_REACTIONS: 'reportActionsReactions_', @@ -380,6 +381,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: Record; [ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report; + [ONYXKEYS.COLLECTION.REPORT_METADATA]: OnyxTypes.ReportMetadata; [ONYXKEYS.COLLECTION.REPORT_ACTIONS]: OnyxTypes.ReportAction; [ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS]: string; [ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS]: OnyxTypes.ReportActionReactions; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 78d5f4d54888..00f3a4012664 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -5,19 +5,18 @@ import CONST from './CONST'; * This is a file containing constants for all of the routes we want to be able to go to */ -// prettier-ignore export default { HOME: '', /** This is a utility route used to go to the user's concierge chat, or the sign-in page if the user's not authenticated */ CONCIERGE: 'concierge', FLAG_COMMENT: { route: 'flag/:reportID/:reportActionID', - getRoute: (reportID: string, reportActionID: string) => `flag/${reportID}/${reportActionID}` + getRoute: (reportID: string, reportActionID: string) => `flag/${reportID}/${reportActionID}`, }, SEARCH: 'search', DETAILS: { route: 'details', - getRoute: (login: string) => `details?login=${encodeURIComponent(login)}` + getRoute: (login: string) => `details?login=${encodeURIComponent(login)}`, }, PROFILE: { route: 'a/:accountID', @@ -31,7 +30,7 @@ export default { VALIDATE_LOGIN: 'v/:accountID/:validateCode', GET_ASSISTANCE: { route: 'get-assistance/:taskID', - getRoute: (taskID: string) => `get-assistance/${taskID}` + getRoute: (taskID: string) => `get-assistance/${taskID}`, }, UNLINK_LOGIN: 'u/:accountID/:validateCode', APPLE_SIGN_IN: 'sign-in-with-apple', @@ -102,11 +101,11 @@ export default { REPORT: 'r', REPORT_WITH_ID: { route: 'r/:reportID?/:reportActionID?', - getRoute: (reportID: string) => `r/${reportID}` + getRoute: (reportID: string) => `r/${reportID}`, }, EDIT_REQUEST: { route: 'r/:threadReportID/edit/:field', - getRoute: (threadReportID: string, field: ValueOf) => `r/${threadReportID}/edit/${field}` + getRoute: (threadReportID: string, field: ValueOf) => `r/${threadReportID}/edit/${field}`, }, EDIT_CURRENCY_REQUEST: { route: 'r/:threadReportID/edit/currency', @@ -114,89 +113,89 @@ export default { }, REPORT_WITH_ID_DETAILS_SHARE_CODE: { route: 'r/:reportID/details/shareCode', - getRoute: (reportID: string) => `r/${reportID}/details/shareCode` + getRoute: (reportID: string) => `r/${reportID}/details/shareCode`, }, REPORT_ATTACHMENTS: { route: 'r/:reportID/attachment', - getRoute: (reportID: string, source: string) => `r/${reportID}/attachment?source=${encodeURI(source)}` + getRoute: (reportID: string, source: string) => `r/${reportID}/attachment?source=${encodeURI(source)}`, }, REPORT_PARTICIPANTS: { route: 'r/:reportID/participants', - getRoute: (reportID: string) => `r/${reportID}/participants` + getRoute: (reportID: string) => `r/${reportID}/participants`, }, REPORT_WITH_ID_DETAILS: { route: 'r/:reportID/details', - getRoute: (reportID: string) => `r/${reportID}/details` + getRoute: (reportID: string) => `r/${reportID}/details`, }, REPORT_SETTINGS: { route: 'r/:reportID/settings', - getRoute: (reportID: string) => `r/${reportID}/settings` + getRoute: (reportID: string) => `r/${reportID}/settings`, }, REPORT_SETTINGS_ROOM_NAME: { route: 'r/:reportID/settings/room-name', - getRoute: (reportID: string) => `r/${reportID}/settings/room-name` + getRoute: (reportID: string) => `r/${reportID}/settings/room-name`, }, REPORT_SETTINGS_NOTIFICATION_PREFERENCES: { route: 'r/:reportID/settings/notification-preferences', - getRoute: (reportID: string) => `r/${reportID}/settings/notification-preferences` + getRoute: (reportID: string) => `r/${reportID}/settings/notification-preferences`, }, REPORT_SETTINGS_WRITE_CAPABILITY: { route: 'r/:reportID/settings/who-can-post', - getRoute: (reportID: string) => `r/${reportID}/settings/who-can-post` + getRoute: (reportID: string) => `r/${reportID}/settings/who-can-post`, }, REPORT_WELCOME_MESSAGE: { route: 'r/:reportID/welcomeMessage', - getRoute: (reportID: string) => `r/${reportID}/welcomeMessage` + getRoute: (reportID: string) => `r/${reportID}/welcomeMessage`, }, SPLIT_BILL_DETAILS: { route: 'r/:reportID/split/:reportActionID', - getRoute: (reportID: string, reportActionID: string) => `r/${reportID}/split/${reportActionID}` + getRoute: (reportID: string, reportActionID: string) => `r/${reportID}/split/${reportActionID}`, }, TASK_TITLE: { route: 'r/:reportID/title', - getRoute: (reportID: string) => `r/${reportID}/title` + getRoute: (reportID: string) => `r/${reportID}/title`, }, TASK_DESCRIPTION: { route: 'r/:reportID/description', - getRoute: (reportID: string) => `r/${reportID}/description` + getRoute: (reportID: string) => `r/${reportID}/description`, }, TASK_ASSIGNEE: { route: 'r/:reportID/assignee', - getRoute: (reportID: string) => `r/${reportID}/assignee` + getRoute: (reportID: string) => `r/${reportID}/assignee`, }, PRIVATE_NOTES_VIEW: { route: 'r/:reportID/notes/:accountID', - getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/notes/${accountID}` + getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/notes/${accountID}`, }, PRIVATE_NOTES_LIST: { route: 'r/:reportID/notes', - getRoute: (reportID: string) => `r/${reportID}/notes` + getRoute: (reportID: string) => `r/${reportID}/notes`, }, PRIVATE_NOTES_EDIT: { route: 'r/:reportID/notes/:accountID/edit', - getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/notes/${accountID}/edit` + getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/notes/${accountID}/edit`, }, // To see the available iouType, please refer to CONST.IOU.MONEY_REQUEST_TYPE MONEY_REQUEST: { route: ':iouType/new/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}` + getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}`, }, MONEY_REQUEST_AMOUNT: { route: ':iouType/new/amount/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/amount/${reportID}` + getRoute: (iouType: string, reportID = '') => `${iouType}/new/amount/${reportID}`, }, MONEY_REQUEST_PARTICIPANTS: { route: ':iouType/new/participants/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/participants/${reportID}` + getRoute: (iouType: string, reportID = '') => `${iouType}/new/participants/${reportID}`, }, MONEY_REQUEST_CONFIRMATION: { route: ':iouType/new/confirmation/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/confirmation/${reportID}` + getRoute: (iouType: string, reportID = '') => `${iouType}/new/confirmation/${reportID}`, }, MONEY_REQUEST_DATE: { route: ':iouType/new/date/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/date/${reportID}` + getRoute: (iouType: string, reportID = '') => `${iouType}/new/date/${reportID}`, }, MONEY_REQUEST_CURRENCY: { route: ':iouType/new/currency/:reportID?', @@ -204,35 +203,39 @@ export default { }, MONEY_REQUEST_DESCRIPTION: { route: ':iouType/new/description/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/description/${reportID}` + getRoute: (iouType: string, reportID = '') => `${iouType}/new/description/${reportID}`, }, MONEY_REQUEST_CATEGORY: { route: ':iouType/new/category/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/category/${reportID}` + getRoute: (iouType: string, reportID = '') => `${iouType}/new/category/${reportID}`, }, MONEY_REQUEST_TAG: { route: ':iouType/new/tag/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/tag/${reportID}` + getRoute: (iouType: string, reportID = '') => `${iouType}/new/tag/${reportID}`, }, MONEY_REQUEST_MERCHANT: { route: ':iouType/new/merchant/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/merchant/${reportID}` + getRoute: (iouType: string, reportID = '') => `${iouType}/new/merchant/${reportID}`, }, MONEY_REQUEST_WAYPOINT: { route: ':iouType/new/waypoint/:waypointIndex', - getRoute: (iouType: string, waypointIndex: number) => `${iouType}/new/waypoint/${waypointIndex}` + getRoute: (iouType: string, waypointIndex: number) => `${iouType}/new/waypoint/${waypointIndex}`, }, MONEY_REQUEST_RECEIPT: { route: ':iouType/new/receipt/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/receipt/${reportID}` + getRoute: (iouType: string, reportID = '') => `${iouType}/new/receipt/${reportID}`, }, - MONEY_REQUEST_ADDRESS: { + MONEY_REQUEST_DISTANCE: { route: ':iouType/new/address/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/address/${reportID}` + getRoute: (iouType: string, reportID = '') => `${iouType}/new/address/${reportID}`, + }, + MONEY_REQUEST_EDIT_WAYPOINT: { + route: 'r/:threadReportID/edit/distance/:transactionID/waypoint/:waypointIndex', + getRoute: (threadReportID: number, transactionID: string, waypointIndex: number) => `r/${threadReportID}/edit/distance/${transactionID}/waypoint/${waypointIndex}`, }, MONEY_REQUEST_DISTANCE_TAB: { route: ':iouType/new/:reportID?/distance', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}/distance` + getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}/distance`, }, MONEY_REQUEST_MANUAL_TAB: ':iouType/new/:reportID?/manual', MONEY_REQUEST_SCAN_TAB: ':iouType/new/:reportID?/scan', @@ -259,47 +262,47 @@ export default { WORKSPACE_NEW_ROOM: 'workspace/new-room', WORKSPACE_INITIAL: { route: 'workspace/:policyID', - getRoute: (policyID: string) => `workspace/${policyID}` + getRoute: (policyID: string) => `workspace/${policyID}`, }, WORKSPACE_INVITE: { route: 'workspace/:policyID/invite', - getRoute: (policyID: string) => `workspace/${policyID}/invite` + getRoute: (policyID: string) => `workspace/${policyID}/invite`, }, WORKSPACE_INVITE_MESSAGE: { route: 'workspace/:policyID/invite-message', - getRoute: (policyID: string) => `workspace/${policyID}/invite-message` + getRoute: (policyID: string) => `workspace/${policyID}/invite-message`, }, WORKSPACE_SETTINGS: { route: 'workspace/:policyID/settings', - getRoute: (policyID: string) => `workspace/${policyID}/settings` + getRoute: (policyID: string) => `workspace/${policyID}/settings`, }, WORKSPACE_CARD: { route: 'workspace/:policyID/card', - getRoute: (policyID: string) => `workspace/${policyID}/card` + getRoute: (policyID: string) => `workspace/${policyID}/card`, }, WORKSPACE_REIMBURSE: { route: 'workspace/:policyID/reimburse', - getRoute: (policyID: string) => `workspace/${policyID}/reimburse` + getRoute: (policyID: string) => `workspace/${policyID}/reimburse`, }, WORKSPACE_RATE_AND_UNIT: { route: 'workspace/:policyID/rateandunit', - getRoute: (policyID: string) => `workspace/${policyID}/rateandunit` + getRoute: (policyID: string) => `workspace/${policyID}/rateandunit`, }, WORKSPACE_BILLS: { route: 'workspace/:policyID/bills', - getRoute: (policyID: string) => `workspace/${policyID}/bills` + getRoute: (policyID: string) => `workspace/${policyID}/bills`, }, WORKSPACE_INVOICES: { route: 'workspace/:policyID/invoices', - getRoute: (policyID: string) => `workspace/${policyID}/invoices` + getRoute: (policyID: string) => `workspace/${policyID}/invoices`, }, WORKSPACE_TRAVEL: { route: 'workspace/:policyID/travel', - getRoute: (policyID: string) => `workspace/${policyID}/travel` + getRoute: (policyID: string) => `workspace/${policyID}/travel`, }, WORKSPACE_MEMBERS: { route: 'workspace/:policyID/members', - getRoute: (policyID: string) => `workspace/${policyID}/members` + getRoute: (policyID: string) => `workspace/${policyID}/members`, }, // These are some on-off routes that will be removed once they're no longer needed (see GH issues for details) diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index 5e9b73f2eb3a..30e5adfc62b0 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -5,39 +5,31 @@ import lodashGet from 'lodash/get'; import lodashIsNil from 'lodash/isNil'; import PropTypes from 'prop-types'; import _ from 'underscore'; - import CONST from '../CONST'; import ROUTES from '../ROUTES'; import ONYXKEYS from '../ONYXKEYS'; - import styles from '../styles/styles'; import variables from '../styles/variables'; -import theme from '../styles/themes/default'; - -import transactionPropTypes from './transactionPropTypes'; - +import LinearGradient from './LinearGradient'; +import * as MapboxToken from '../libs/actions/MapboxToken'; import useNetwork from '../hooks/useNetwork'; -import usePrevious from '../hooks/usePrevious'; import useLocalize from '../hooks/useLocalize'; - -import * as ErrorUtils from '../libs/ErrorUtils'; import Navigation from '../libs/Navigation/Navigation'; -import * as MapboxToken from '../libs/actions/MapboxToken'; +import reportPropTypes from '../pages/reportPropTypes'; +import DotIndicatorMessage from './DotIndicatorMessage'; +import * as ErrorUtils from '../libs/ErrorUtils'; +import usePrevious from '../hooks/usePrevious'; +import theme from '../styles/themes/default'; import * as Transaction from '../libs/actions/Transaction'; import * as TransactionUtils from '../libs/TransactionUtils'; import * as IOUUtils from '../libs/IOUUtils'; - import Button from './Button'; import DistanceMapView from './DistanceMapView'; -import LinearGradient from './LinearGradient'; import * as Expensicons from './Icon/Expensicons'; import PendingMapView from './MapView/PendingMapView'; -import DotIndicatorMessage from './DotIndicatorMessage'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; -import {iouPropTypes} from '../pages/iou/propTypes'; -import reportPropTypes from '../pages/reportPropTypes'; -import * as IOU from '../libs/actions/IOU'; import * as StyleUtils from '../styles/StyleUtils'; +import transactionPropTypes from './transactionPropTypes'; import ScreenWrapper from './ScreenWrapper'; import FullPageNotFoundView from './BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from './HeaderWithBackButton'; @@ -46,18 +38,12 @@ const MAX_WAYPOINTS = 25; const MAX_WAYPOINTS_TO_DISPLAY = 4; const propTypes = { - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - iou: iouPropTypes, - - /** Type of money request (i.e. IOU) */ - iouType: PropTypes.oneOf(_.values(CONST.IOU.MONEY_REQUEST_TYPE)), + /** The transactionID of this request */ + transactionID: PropTypes.string, /** The report to which the distance request is associated */ report: reportPropTypes, - /** The optimistic transaction for this request */ - transaction: transactionPropTypes, - /** Data about Mapbox token for calling Mapbox API */ mapboxAccessToken: PropTypes.shape({ /** Temporary token for Mapbox API */ @@ -67,6 +53,15 @@ const propTypes = { expiration: PropTypes.string, }), + /** Are we editing an existing distance request, or creating a new one? */ + isEditingRequest: PropTypes.bool, + + /** Called on submit of this page */ + onSubmit: PropTypes.func.isRequired, + + /* Onyx Props */ + transaction: transactionPropTypes, + /** React Navigation route */ route: PropTypes.shape({ /** Params from the route */ @@ -81,16 +76,16 @@ const propTypes = { }; const defaultProps = { - iou: {}, - iouType: '', + transactionID: '', report: {}, - transaction: {}, + isEditingRequest: false, mapboxAccessToken: { token: '', }, + transaction: {}, }; -function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken, route}) { +function DistanceRequest({transactionID, report, transaction, mapboxAccessToken, route, isEditingRequest, onSubmit}) { const [shouldShowGradient, setShouldShowGradient] = useState(false); const [scrollContainerHeight, setScrollContainerHeight] = useState(0); const [scrollContentHeight, setScrollContentHeight] = useState(0); @@ -99,6 +94,7 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken, const isEditing = lodashGet(route, 'path', '').includes('address'); const reportID = lodashGet(report, 'reportID', ''); + const iouType = lodashGet(route, 'params.iouType', ''); const waypoints = useMemo(() => lodashGet(transaction, 'comment.waypoints', {}), [transaction]); const previousWaypoints = usePrevious(waypoints); const numberOfWaypoints = _.size(waypoints); @@ -107,6 +103,7 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken, const lastWaypointIndex = numberOfWaypoints - 1; const isLoadingRoute = lodashGet(transaction, 'comment.isLoading', false); + const isLoading = lodashGet(transaction, 'isLoading', false); const hasRouteError = !!lodashGet(transaction, 'errorFields.route'); const hasRoute = TransactionUtils.hasRoute(transaction); const validatedWaypoints = TransactionUtils.getValidWaypoints(waypoints); @@ -159,12 +156,12 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken, }, []); useEffect(() => { - if (!iou.transactionID || !_.isEmpty(waypoints)) { + if (!transactionID || !_.isEmpty(waypoints)) { return; } // Create the initial start and stop waypoints - Transaction.createInitialWaypoints(iou.transactionID); - }, [iou.transactionID, waypoints]); + Transaction.createInitialWaypoints(transactionID); + }, [transactionID, waypoints]); const updateGradientVisibility = (event = {}) => { // If a waypoint extends past the bottom of the visible area show the gradient, else hide it. @@ -176,8 +173,8 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken, return; } - Transaction.getRoute(iou.transactionID, validatedWaypoints); - }, [shouldFetchRoute, iou.transactionID, validatedWaypoints, isOffline]); + Transaction.getRoute(transactionID, validatedWaypoints); + }, [shouldFetchRoute, transactionID, validatedWaypoints, isOffline]); useEffect(() => { if (numberOfWaypoints <= numberOfPreviousWaypoints) { @@ -192,13 +189,12 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken, Navigation.goBack(isEditing ? ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID) : ROUTES.HOME); }; - const navigateToNextPage = () => { - if (isEditing) { - Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID)); - return; - } - - IOU.navigateToNextPage(iou, iouType, reportID, report); + /** + * Takes the user to the page for editing a specific waypoint + * @param {Number} index of the waypoint to edit + */ + const navigateToWaypointEditPage = (index) => { + Navigation.navigate(isEditingRequest ? ROUTES.MONEY_REQUEST_EDIT_WAYPOINT.getRoute(report.reportID, transactionID, index) : ROUTES.MONEY_REQUEST_WAYPOINT.getRoute('request', index)); }; const content = ( @@ -237,7 +233,7 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken, secondaryIcon={waypointIcon} secondaryIconFill={theme.icon} shouldShowRightIcon - onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_WAYPOINT.getRoute('request', index))} + onPress={() => navigateToWaypointEditPage(index)} key={key} /> ); @@ -261,10 +257,7 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken,