diff --git a/.github/actions/javascript/awaitStagingDeploys/index.js b/.github/actions/javascript/awaitStagingDeploys/index.js index 1cb7934cfb91..d84c6df1a0d3 100644 --- a/.github/actions/javascript/awaitStagingDeploys/index.js +++ b/.github/actions/javascript/awaitStagingDeploys/index.js @@ -12738,6 +12738,10 @@ function promiseWhile(condition, action) { else { const actionResult = action?.(); console.info('[promiseWhile] promiseWhile() actionResult', actionResult); + if (!actionResult) { + resolve(); + return; + } Promise.resolve(actionResult).then(loop).catch(reject); } }; diff --git a/.github/libs/promiseWhile.ts b/.github/libs/promiseWhile.ts index 2fc53c34fec3..01c061096d64 100644 --- a/.github/libs/promiseWhile.ts +++ b/.github/libs/promiseWhile.ts @@ -13,6 +13,12 @@ function promiseWhile(condition: () => boolean, action: (() => Promise) | } else { const actionResult = action?.(); console.info('[promiseWhile] promiseWhile() actionResult', actionResult); + + if (!actionResult) { + resolve(); + return; + } + Promise.resolve(actionResult).then(loop).catch(reject); } }; diff --git a/__mocks__/react-native-onyx.ts b/__mocks__/react-native-onyx.ts index 253e3db47a96..9c6a36380c04 100644 --- a/__mocks__/react-native-onyx.ts +++ b/__mocks__/react-native-onyx.ts @@ -5,7 +5,7 @@ /* eslint-disable rulesdir/prefer-onyx-connect-in-libs */ import type {ConnectOptions, OnyxKey} from 'react-native-onyx'; -import Onyx, {withOnyx} from 'react-native-onyx'; +import Onyx, {useOnyx, withOnyx} from 'react-native-onyx'; let connectCallbackDelay = 0; function addDelayToConnectCallback(delay: number) { @@ -40,4 +40,4 @@ const reactNativeOnyxMock: ReactNativeOnyxMock = { }; export default reactNativeOnyxMock; -export {withOnyx}; +export {withOnyx, useOnyx}; diff --git a/android/app/build.gradle b/android/app/build.gradle index 1a37ce00c193..da340184e7c8 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,8 +98,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001046200 - versionName "1.4.62-0" + versionCode 1001046210 + versionName "1.4.62-10" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/assets/images/new-expensify-adhoc.svg b/assets/images/new-expensify-adhoc.svg index f2603555fc38..b3dd92fbbaae 100644 --- a/assets/images/new-expensify-adhoc.svg +++ b/assets/images/new-expensify-adhoc.svg @@ -1 +1,31 @@ - \ No newline at end of file + + + + + + + + + + + + + + + diff --git a/assets/images/new-expensify-dev.svg b/assets/images/new-expensify-dev.svg index 9c11ed02433c..316da6b5aa4d 100644 --- a/assets/images/new-expensify-dev.svg +++ b/assets/images/new-expensify-dev.svg @@ -1 +1,27 @@ - \ No newline at end of file + + + + + + + + + + + + + + + diff --git a/assets/images/new-expensify-stg.svg b/assets/images/new-expensify-stg.svg index f151d7c4c130..1a1994c7a9fd 100644 --- a/assets/images/new-expensify-stg.svg +++ b/assets/images/new-expensify-stg.svg @@ -1 +1,35 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/simple-illustrations/simple-illustration__lightbulb.svg b/assets/images/simple-illustrations/simple-illustration__lightbulb.svg new file mode 100644 index 000000000000..1dc359764147 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__lightbulb.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + diff --git a/config/webpack/webpack.common.ts b/config/webpack/webpack.common.ts index b0e301ef3a6c..7cafafca9973 100644 --- a/config/webpack/webpack.common.ts +++ b/config/webpack/webpack.common.ts @@ -30,7 +30,7 @@ const includeModules = [ ].join('|'); const environmentToLogoSuffixMap: Record = { - production: '', + production: '-dark', staging: '-stg', dev: '-dev', adhoc: '-adhoc', diff --git a/docs/articles/expensify-classic/connect-credit-cards/business-bank-accounts/Business-Bank-Accounts-AUD.md b/docs/articles/expensify-classic/bank-accounts-and-payments/Business-Bank-Accounts-AUD.md similarity index 100% rename from docs/articles/expensify-classic/connect-credit-cards/business-bank-accounts/Business-Bank-Accounts-AUD.md rename to docs/articles/expensify-classic/bank-accounts-and-payments/Business-Bank-Accounts-AUD.md diff --git a/docs/articles/expensify-classic/connect-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-payments/Business-Bank-Accounts-USD.md similarity index 100% rename from docs/articles/expensify-classic/connect-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md rename to docs/articles/expensify-classic/bank-accounts-and-payments/Business-Bank-Accounts-USD.md diff --git a/docs/articles/expensify-classic/connect-credit-cards/deposit-accounts/Deposit-Accounts-AUD.md b/docs/articles/expensify-classic/bank-accounts-and-payments/Deposit-Accounts-AUD.md similarity index 99% rename from docs/articles/expensify-classic/connect-credit-cards/deposit-accounts/Deposit-Accounts-AUD.md rename to docs/articles/expensify-classic/bank-accounts-and-payments/Deposit-Accounts-AUD.md index 6114e98883e0..e274cb3d5b60 100644 --- a/docs/articles/expensify-classic/connect-credit-cards/deposit-accounts/Deposit-Accounts-AUD.md +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/Deposit-Accounts-AUD.md @@ -19,4 +19,3 @@ Bank accounts are easy to delete! Simply click the red **Delete** button in the ![Click the Delete button](https://help.expensify.com/assets/images/delete-australian-bank-account.png){:width="100%"} You can complete this process on a computer or on the mobile app. - diff --git a/docs/articles/expensify-classic/connect-credit-cards/deposit-accounts/Deposit-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-payments/Deposit-Accounts-USD.md similarity index 98% rename from docs/articles/expensify-classic/connect-credit-cards/deposit-accounts/Deposit-Accounts-USD.md rename to docs/articles/expensify-classic/bank-accounts-and-payments/Deposit-Accounts-USD.md index 0bc5cb0ad955..0e195d5e3f1c 100644 --- a/docs/articles/expensify-classic/connect-credit-cards/deposit-accounts/Deposit-Accounts-USD.md +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/Deposit-Accounts-USD.md @@ -33,7 +33,7 @@ You should be all set! You’ll receive reimbursement for your expense reports d **Connect a business deposit-only bank account if you are:** - A US-based vendor who wants to be paid directly for bills sent to customers/clients -- A US-based vendor who want to pay invoices directly via Expensify +- A US-based vendor who wants to pay invoices directly via Expensify **To establish the connection to a business bank account, follow these steps:** diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/Global-Reimbursements.md b/docs/articles/expensify-classic/bank-accounts-and-payments/Global-Reimbursements.md new file mode 100644 index 000000000000..2ff74760b376 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/Global-Reimbursements.md @@ -0,0 +1,106 @@ +--- +title: International Reimbursements +description: International Reimbursements +--- +# Overview + +If your company’s business bank account is in the US, Canada, the UK, Europe, or Australia, you now have the option to send direct reimbursements to nearly any country across the globe! +The process to enable global reimbursements is dependent on the currency of your reimbursement bank account, so be sure to review the corresponding instructions below. + +# How to request international reimbursements + +## The reimbursement account is in USD + +If your reimbursement bank account is in USD, the first step is connecting the bank account to Expensify. +The individual who plans on sending reimbursements internationally should head to **Settings > Account > Payments > Add Verified Bank Account**. From there, you will provide company details, input personal information, and upload a copy of your ID. + +Once the USD bank account is verified (or if you already had a USD business bank account connected), click the support icon in your Expensify account to inform your Setup Specialist, Account Manager, or Concierge that you’d like to enable international reimbursements. From there, Expensify will ask you to confirm the currencies of the reimbursement and employee bank accounts. + +Our team will assess your account, and if you meet the criteria, international reimbursements will be enabled. + +## The reimbursement account is in AUD, CAD, GBP, EUR + +To request international reimbursements, contact Expensify Support to make that request. + +You can do this by clicking on the support icon and informing your Setup Specialist, Account Manager, or Concierge that you’d like to set up global reimbursements on your account. +From there, Expensify will ask you to confirm both the currencies of the reimbursement and employee bank accounts. + +Our team will assess your account, and if you meet the criteria, international reimbursements will be enabled. + +# How to verify the bank account for sending international payments + +Once international payments are enabled on your Expensify account, the next step is verifying the bank account to send the reimbursements. + +## The reimbursement account is in USD + +First, confirm the workspace settings are set up correctly by doing the following: +1. Head to **Settings > Workspaces > Group > _[Workspace Name]_ > Reports** and check that the workspace currency is USD +2. Under **Settings > Workspaces > Group > _[Workspace Name]_ > Reimbursements**, set the reimbursement method to direct +3. Under **Settings > Workspaces > Group > _[Workspace Name]_ > Reimbursements**, set the USD bank account to the default account + +Once that’s all set, head to **Settings > Account > Payments**, and click **Enable Global Reimbursement** on the bank account (this button may not show for up to 60 minutes after the Expensify team confirms international reimbursements are available on your account). + +From there, you’ll fill out a form via DocuSign. Once the form is complete, it is automatically sent to our Compliance Team for review. Our Support Team will contact you with more details if additional information is required. + +## The reimbursement account is in AUD, CAD, GBP, EUR + +First, confirm the workspace currency corresponds with the currency of the reimbursement bank account. You can do this under **Settings > Workspaces > Group > _[Workspace Name]_ > Reports**. It should be AUD, CAD, GBP, or EUR. + +Next, add the bank account to Expensify: +1. Head to **Settings > Workspaces > Group > _[Workspace Name]_ > Reimbursements** and set the reimbursement method to direct (this button may not show for up to 60 minutes after the Expensify team confirms international reimbursements are available on your account) +2. Click **Add Business Bank Account** +3. If the incorrect country shows as the default, click **Switch Country** to select the correct country +4. Enter the bank account details +5. Click **Save & Continue** + +From there, you’ll fill out a form via DocuSign. Once the form is complete, it is automatically sent to our Compliance Team for review. Our Support Team will contact you with more details if additional information is required. + +# How to start reimbursing internationally + +After the bank account is verified for international payments, set the correct bank account as the reimbursement account. + +You can do this under **Settings > Workspaces > Group > _[Workspace Name]_ > Reimbursements** by selecting the reimbursement account as the default account. + +Finally, have your employees add their deposit-only bank accounts. They can do this by logging into their Expensify accounts, heading to **Settings > Account > Payments**, and clicking **Add Deposit-Only Bank Account**. + +# Deep Dive + +## Documents requested + +Our Compliance Team may ask for additional information depending on who initiates the verification or what information you provide on the DocuSign form. + +Examples of additional requested information: +- The reimburser’s proof of address and ID +- Company directors’ proofs of address and IDs +- An authorization letter +- An independently certified documentation such as shareholder agreement from a lawyer, notary, or public accountant if an individual owns more than 25% of the company + +{% include faq-begin.md %} + +## How many people can send reimbursements internationally? + +Once your company is authorized to send global payments, the individual who verified the bank account can share it with additional admins on the workspace. That way, multiple workspace members can send international reimbursements. + +## How long does it take to verify an account for international payments? + +It varies! The verification process can take a few business days to several weeks. It depends on whether or not the information in the DocuSign form is correct if our Compliance Team requires any additional information, and how responsive the employee verifying the company’s details is to our requests. + +## If I already have a USD bank account connected to Expensify, do I need to go through the verification process again to enable international payments? + +If you’ve already connected a US business bank account, you can request to enable global reimbursements by contacting Expensify Support immediately. However, additional steps are required to verify the bank account for international payments. + +## My employee says they don’t have the option to add their non-USD bank account as a deposit account – what should they do? + +Have the employee double-check that their default workspace is set as the workspace that's connected to the bank you're using to send international payments. + +An employee can confirm their default workspace is under **Settings > Workspaces > Group**. The default workspace has a green checkmark next to it. They can change their default workspace by clicking **Default Workspace** on the correct workspace. + +## Who is the “Authorized User” on the International Reimbursement DocuSign form? + +This is the person who will process international reimbursements. The authorized user should be the same person who manages the bank account connection in Expensify. + +## Who should I enter as the “User” on the International Reimbursement form? + +You can leave this form section blank since the “User” is Expensify. + +{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/copilots-and-delegates/Invite-Members.md b/docs/articles/expensify-classic/copilots-and-delegates/Invite-Members.md deleted file mode 100644 index 5a27f58cf2e8..000000000000 --- a/docs/articles/expensify-classic/copilots-and-delegates/Invite-Members.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -title: Invite Members -description: Learn how add your employees to submit expenses in Expensify ---- -# Overview - -To invite your employees to Expensify, simply add them as members to your Workspace. - -# How to Invite members to Expensify - -## Inviting Members Manually - -Navigate to **Settings > Workspace > Group > *Workspace Name* > People** - then click **Invite** and enter the invitee's email address. - -Indicate whether you want them to be an Employee, Admin, or Auditor on the Workspace. - -If you are utilizing the Advanced Approval feature and the invitee is an approver, you can use the "Approves to" field to specify to whom they approve and forward reports for additional approval. - -## Inviting Members to a Workspace in Bulk - -Navigate to **Settings > Workspaces > Group > *Workspace Name* > People** - then click Invite and enter all of the email addresses separated by comma. Indicate whether you want them to be an Employee, Admin, or Auditor on the Workspace. - -If you are utilizing the Advanced Approval feature, you can specify who each member should submit their expense reports to and who an approver should send approved reports to for the next step in the approval process. If someone is the final approver, you can leave this field blank. - -Another convenient method is to employ the spreadsheet bulk upload option for inviting members to a Workspace. This proves particularly helpful when initially configuring your system or when dealing with numerous member updates. Simply click the "Import from Spreadsheet" button and upload a file in formats such as .csv, .txt, .xls, or .xlsx to streamline the process. - -After uploading the spreadsheet, we'll display a window where you can choose which columns to import and what they correspond to. These are the fields: -- Email -- Role -- Custom Field 1 -- Custom Field 2 -- Submits To -- Approves To -- Approval Limit -- Over Limit Forward To - -Click the **Import** button and you're done. We will import the new members with the optional settings and update any already existing ones. - -## Inviting Members with a Shareable Workspace Joining Link - -You have the ability to invite your colleagues to join your Expensify Workspace by sharing a unique Workspace Joining Link. You can use this link as many times as necessary to invite multiple members through various communication methods such as internal emails, chats, text messages, and more. - -To find your unique link, simply go to **Settings > Workspace > Group > *Workspace Name* > People**. - -## Allowing Members to Automatically Join Your Workspace - -You can streamline the process of inviting colleagues to your Workspace by enabling the Pre-approve switch located below your Workspace Joining Link. This allows teammates to automatically become part of your Workspace as soon as they create an Expensify account using their work email address. - -Here's how it works: If a colleague signs up with a work email address that matches the email domain of a company Workspace owner (e.g., if the Workspace owner's email is admin@expensify.com and the colleague signs up with employee@expensify.com), they will be able to join your Workspace seamlessly without requiring a manual invitation. When new members join the Workspace, they will be set up to submit their expense reports to the Workspace owner by default. - -To enable this feature, go to **Settings > Workspace > Group > *Workspace Name* > People**. - - -{% include faq-begin.md %} -## Who can invite members to Expensify -Any Workspace Admin can add members to a Group Workspace using any of the above methods. - -## How can I customize an invite message? -Under **Settings > Workspace > Group > *Workspace Name* > People > Invite** you can enter a custom message you'd like members to receive in their invitation email. - -## How can I invite members via the API? -If you would like to integrate an open API HR software, you can use our [Advanced Employee Updater API](https://integrations.expensify.com/Integration-Server/doc/employeeUpdater/) to invite members to your Workspace. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/copilots-and-delegates/Removing-Members.md b/docs/articles/expensify-classic/copilots-and-delegates/Removing-Members.md deleted file mode 100644 index 65acc3630582..000000000000 --- a/docs/articles/expensify-classic/copilots-and-delegates/Removing-Members.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Remove a Workspace Member -description: How to remove a member from a Workspace in Expensify ---- - -Removing a member from a workspace disables their ability to use the workspace. Please note that it does not delete their account or deactivate the Expensify Card. - -## How to Remove a Workspace Member -1. Important: Make sure the employee has submitted all Draft reports and the reports have been approved, reimbursed, etc. -2. Go to Settings > Workspaces > Group > [Workspace Name] > Members > Workspace Members -3. Select the member you'd like to remove and click the **Remove** button at the top of the Members table. -4. If this member was an approver, make sure that reports are not routing to them in the workflow. - -![image of members table in a workspace]({{site.url}}/assets/images/ExpensifyHelp_RemovingMembers.png){:width="100%"} - -{% include faq-begin.md %} - -## Will reports from this member on this workspace still be available? -Yes, as long as the reports have been submitted. You can navigate to the Reports page and enter the member's email in the search field to find them. However, Draft reports will be removed from the workspace, so these will no longer be visible to the Workspace Admin. - -## Can members still access their reports on a workspace after they have been removed? -Yes. Any report that has been approved will now show the workspace as “(not shared)” in their account. If it is a Draft Report they will still be able to edit it and add it to a new workspace. If the report is Approved or Reimbursed they will not be able to edit it further. - -## Who can remove members from a workspace? -Only Workspace Admins. It is not possible for a member to add or remove themselves from a workspace. It is not possible for a Domain Admin who is not also a Workspace Admin to remove a member from a workspace. - -## How do I remove a member from a workspace if I am seeing an error message? -If a member is a **preferred exporter, billing owner, report approver** or has **processing reports**, to remove them the workspace you will first need to: - -* **Preferred Exporter** - go to Settings > Workspaces > Group > [Workspace Name] > Connections > Configure and select a different Workspace Admin in the dropdown for **Preferred Exporter**. -* **Billing Owner** - take over billing on the Settings > Workspaces > Group > [Workspace Name] > Overview page. -* **Processing reports** - approve or reject the member’s reports on your Reports page. -* **Approval Workflow** - remove them as a workflow approver on your Settings > Workspaces > Group > [Workspace Name] > Members > Approval Mode > page by changing the "**Submit reports to**" field. - -## How do I remove a user completely from a company account? -If you have a Control Workspace and have Domain Control enabled, you will need to remove them from the domain to delete members' accounts entirely and deactivate the Expensify Card. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/reports/Add-comments-and-attachments-to-a-report.md b/docs/articles/expensify-classic/reports/Add-comments-and-attachments-to-a-report.md new file mode 100644 index 000000000000..b647a02190bc --- /dev/null +++ b/docs/articles/expensify-classic/reports/Add-comments-and-attachments-to-a-report.md @@ -0,0 +1,32 @@ +--- +title: Add comments & attachments to a report +description: Add clarification for expenses by adding comments and attachments to a report +--- +
+ +You can add comments and attachments to a report to help clarify or provide justification for the expenses. + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab. +2. Click the report. +3. Scroll down to the bottom of the report and add a comment or attachment. + - **To add a comment**: Type a comment into the field and click the Send icon, or press the Enter key on your keyboard. + - **To add an attachment**: Click the paperclip icon, then select a jpeg, jpg, png, gif, csv, or pdf file to attach to the report. Then click **Upload**. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the ☰ menu icon in the top left. +2. Tap **Reports**. +3. Tap the report. +4. At the bottom of the report, add a comment or attachment. + - **To add a comment**: Type a comment into the field and click the Send icon. + - **To add an attachment**: Click the paperclip icon, then select a jpeg, jpg, png, gif, csv, or pdf file to attach to the report. Then click **Confirm**. +{% include end-option.html %} + +{% include end-selector.html %} + +In this section at the bottom of the report, Expensify also logs actions taken on the report. + +
diff --git a/docs/articles/expensify-classic/reports/Add-expenses-to-a-report.md b/docs/articles/expensify-classic/reports/Add-expenses-to-a-report.md new file mode 100644 index 000000000000..e3c6c5fa2a46 --- /dev/null +++ b/docs/articles/expensify-classic/reports/Add-expenses-to-a-report.md @@ -0,0 +1,81 @@ +--- +title: Add expenses to a report +description: Put your expenses on a report to submit them for reimbursement +--- +
+ +Once you’ve created your expenses, they may be automatically added to an expense report if your company has this feature enabled. If not, your next step will be to add your expenses to a report and submit them for payment. + +You can either create a new report or add expenses to an existing report. + +{% include info.html %} +There may be restrictions on your ability to create reports depending on your workspace settings. +{% include end-info.html %} + +# Add expenses to an existing report + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab. +2. Click the report. +3. Click **Add Expenses** at the top of the report. +4. Select the expenses to add to the report. + - If an expense you already added does not appear in the list, use the filter on the left to search by the merchant name or change the date range. *Note: Only expenses that are not already on a report will appear.* +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the ☰ menu icon in the top left. +2. Tap **Reports**. +3. Tap the report. +4. Tap **Add Expense**, then tap an expense to add it to the report. +{% include end-option.html %} + +{% include end-selector.html %} + +# Create a new report + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab. + - If a report has been automatically created for your most recently submitted expense, then you don’t have to do anything else—your report is already created and will also be automatically submitted. + - If a report has not been automatically created, follow the steps below. +2. Click **New Report**, or click the New Report dropdown and select **Expense Report** (*The other report types are explained in the FAQ section below*). +3. Click **Add Expenses**. +4. Click an expense to add it to the report. + - If an expense you already added does not appear in the list, use the filter on the left to search by the merchant name or change the date range. *Note: Only expenses that are not already on a report will appear.* +5. Once all your expenses are added to the report, click the X to close the pop-up. +6. (Optional) Make any desired changes to the report and/or expenses. + - Click the Edit icon next to the report name to change it. If this icon is not visible, the option has been disabled by your workspace. + - Click the X icon next to an expense to remove it from the report. + - Click the Expense Details icon to review or edit the expense details. + - At the bottom of the report, add comments to include more information. + - Click the Attachments icon to add additional attachments. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the ☰ menu icon in the top left. +2. Tap **Reports**. + - If a report has been automatically created for your most recently submitted expense, then you don’t have to do anything else—your report is already created and will also be automatically submitted. + - If a report has not been automatically created, follow the steps below. +3. Tap the + icon and tap **Expense Report** (*The other report types are explained in the FAQ section below*). +4. Tap **Add Expenses**, then tap an expense to add it to the report. Repeat this step until all desired expenses are added. *Note: Only expenses that are not already on a report will appear.* +5. (Optional) Make any desired changes to the report and/or expenses. + - Tap the report name to change it. + - Tap an expense to review or edit the expense details. + - At the bottom of the report, add comments to include more information. + - Click the Attachments icon to add additional attachments. +{% include end-option.html %} + +{% include end-selector.html %} + +# FAQs + +**What’s the difference between expense reports, bills, and invoices?** + +- **Expense Report**: Expense reports are submitted by an employee to their employer. This may include reimbursable expenses like business travel paid for with personal funds, or non-reimbursable expenses like a lunch paid for with a company card. +- **Invoice**: Invoices are reports that a business or contractor will send to another business to charge them for goods or services the business received. For example, a contractor that provides an hourly-rate service (like landscaping) may provide their clients with an invoice to detail the different services and products they provided, how many hours they worked, what their rate per hour is for each service, etc. Invoices are generally expected to be paid within a duration of time (for example, within 30 days of receipt). +- **Bill**: Each invoice will have a matching bill owned by the recipient so they may use it to pay the invoice sender. Bills are for businesses and contractors who provide their client with a bill for goods or services. For example, a restaurant, store, or hair salon provides bills. Bills are generally expected to be paid upon receipt. + +
diff --git a/docs/articles/expensify-classic/reports/Edit-a-report.md b/docs/articles/expensify-classic/reports/Edit-a-report.md new file mode 100644 index 000000000000..b10dd2ce3019 --- /dev/null +++ b/docs/articles/expensify-classic/reports/Edit-a-report.md @@ -0,0 +1,133 @@ +--- +title: Edit a report +description: Make updates to a report +--- +
+ +You can update a report’s details such as the report title, workspace, report type, layout, and the attached expenses. + +{% include info.html %} +Some report details may be restricted from editing depending on your workspace settings. +{% include end-info.html %} + +# Edit report title + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab and select the report. +2. Click the pencil icon next to the name and edit the name as desired. +3. Press Enter on your keyboard to save the changes. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the ☰ menu icon in the top left. +2. Tap the **Reports** tab then tap the report. +3. Tap the report name to edit it. +{% include end-option.html %} + +{% include end-selector.html %} + +# Change the report workspace + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab and select the report. +2. Click **Details** in the top right of the report. +3. Click the Workspace dropdown list and select the correct workspace. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the ☰ menu icon in the top left. +2. Tap the **Reports** tab then tap the report. +3. Tap **Edit** in the top right. +4. Tap the current workspace name to select a new one. +5. Tap **Done**. +{% include end-option.html %} + +{% include end-selector.html %} + +# Change the report type + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab and select the report. +2. Click **Details** in the top right of the report. +3. Click the Type dropdown and select either Expense Report or Invoice. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the ☰ menu icon in the top left. +2. Tap the **Reports** tab then tap the report. +3. Tap **Edit** in the top right. +4. Tap either Expense Report or Invoice. +5. Tap **Done**. +{% include end-option.html %} + +{% include end-selector.html %} + +# Change the report layout + +1. Click the **Reports** tab and select the report. +2. Click **Details** in the top right of the report. +3. Click the view option that you want to change: + - **View**: Choose between a basic or detailed report view. + - **Group By**: Group expenses on the report based on their category or tag. + - **Split By**: Split out the expenses based on their reimbursable or billable status. + +# Edit expenses + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab and select the report. +2. Click **Details** in the top right of the report. +3. Click the pencil icon at the top of the menu. +4. Hover over an expense and edit: + - A specific field by clicking the pencil icon next to it. + - Multiple fields by clicking the pencil icon to the left of the expense. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the ☰ menu icon in the top left. +2. Tap the **Reports** tab then tap the report. +3. Tap an expense to open it. +4. Tap any field on the expense to edit it. +{% include end-option.html %} + +{% include end-selector.html %} + +# Remove expenses + +{% include info.html %} +This process only removes the expense from the report—it does not permanently delete the expense. +{% include end-info.html %} + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab and select the report. +2. Click the X icon to the left of the expense to remove it from the report. +{% include end-option.html %} + +{% include option.html value="mobile" %} + +**Android** + +1. Tap the ☰ menu icon in the top left. +2. Tap the **Reports** tab then tap the report. +3. Hold the expense and tap Delete to remove it from the report. + +**iOS** + +1. Tap the ☰ menu icon in the top left. +2. Tap the **Reports** tab then tap the report. +3. Swipe the expense to the left and tap Delete to remove it from the report. + +{% include end-option.html %} + +{% include end-selector.html %} + +
diff --git a/docs/articles/expensify-classic/reports/Print-or-download-a-report.md b/docs/articles/expensify-classic/reports/Print-or-download-a-report.md new file mode 100644 index 000000000000..b2e55b09e6c5 --- /dev/null +++ b/docs/articles/expensify-classic/reports/Print-or-download-a-report.md @@ -0,0 +1,15 @@ +--- +title: Print or download a report +description: Share, print, or download a report +--- +
+ +1. Click the **Reports** tab. +2. Select the report. +3. Click **Details** in the top right of the report. +4. Use the icons at the top to print, download, or share the report. + - Click the Print icon to print the report. + - Click the Download icon to download a PDF of the report. + - Click the Share icon to share the report via email or SMS. + +
diff --git a/docs/articles/expensify-classic/reports/Report-Audit-Log-and-Comments.md b/docs/articles/expensify-classic/reports/Report-Audit-Log-and-Comments.md deleted file mode 100644 index 04183608e3d1..000000000000 --- a/docs/articles/expensify-classic/reports/Report-Audit-Log-and-Comments.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Report Audit Log and Comments -description: Details on the expense report audit log and how to leave comments on reports ---- - -# Overview - -At the bottom of each expense report, there’s a section that acts as an audit log for the report. This section details report actions, such as submitting, approving, or exporting. The audit log records the user who completed the action as well as the timestamp for the action. - -This section also doubles as the space where submitters, approvers, and admins can converse with each other by leaving comments. Comments trigger notifications to everyone connected to the report and help facilitate communication inside of Expensify. - -# How to use the audit log - -All report actions are recorded in the audit log. Anytime you need to identify who touched a report or track its progress through the approval process, simply scroll down to the bottom of the report and review the log. - -Each recorded action is timestamped - tap or mouse over the timestamp to see the exact date and time the action occurred. - -# How to use report comments - -There’s a freeform field just under the audit log where you can leave a comment on the report. Type in your comment and click or tap the green arrow to send. The comment will be visible to anyone with visibility on the report, and also automatically sent to anyone who has actioned the report. - -# Deep Dive - -## Audit log - -Here’s a list of actions recorded by the audit log: - -- Report creation -- Report submission -- Report approval -- Report reimbursement -- Exports to accounting or to CSV/Excel files -- Report and expense rejections -- Changes made to expenses by approvers/admins -- Changes made to report fields by approvers/admins -- Automated actions taken by Concierge - -Both manual and automated actions are recorded. If a report action is completed by Concierge, that generally indicates an automation feature triggered the action. For example, an entry that shows a report submitted by Concierge indicates that the **Scheduled Submit** feature is enabled. - -Note that timestamps for actions recorded in the log reflect your own timezone. You can either set a static time zone manually, or we can trace your location data to set a time zone automatically for you. - -To set your time zone manually, head to **Settings > Account > Preferences > Time Zone** and check **Automatically Set my Time Zone**, or uncheck the box and manually choose your time zone from the searchable list of locations. - -## Comments - -Anyone with visibility on a report can leave a comment. Comments are interspersed with audit log entries. - -Report comments initially trigger a mobile app notification to report participants. If you don't read the notification within a certain amount of time, you'll receive an email notification with the report comment instead. The email will include a link to the report, allowing you to view and add additional comments directly on the report. You can also reply directly to the email, which will record your response as a comment. - -Comments can be formatted with bold, italics, or strikethrough using basic Markdown formatting. You can also add receipts and supporting documents to a report by clicking the paperclip icon on the right side of the comment field. - -{% include faq-begin.md %} - -## Why don’t some timestamps in Expensify match up with what’s shown in the report audit log? - -While the audit log is localized to your own timezone, some other features in Expensify (like times shown on the reports page) are not. Those use UTC as a baseline, so it’s possible that some times may look mismatched at first glance. In reality, it’s just a timezone discrepancy. - -## Is commenting on a report a billable action? - -Yes. If you comment on a report, you become a billable actor for the current month. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/reports/Report-statuses.md b/docs/articles/expensify-classic/reports/Report-statuses.md new file mode 100644 index 000000000000..7fbdefc5a999 --- /dev/null +++ b/docs/articles/expensify-classic/reports/Report-statuses.md @@ -0,0 +1,13 @@ +--- +title: Report statuses +description: What your report status means +--- +Each report is given a status based on where it is in the approval process: + +- **Open**: The report is “In Progress” and has not yet been submitted. If an open report is also labeled as Rejected, that means that the report was submitted but then rejected by an Approver because it requires adjustments. Open the report to review the comments for clarification on the rejection and next steps to take. +- **Processing**: The report has been submitted and is pending approval. +- **Approved**: The report has been approved but has not been reimbursed. For non-reimbursable reports, this is the final status. +- **Reimbursed**: The report has been successfully reimbursed. If a reimbursed report is also labeled as + - **Withdrawing**, an ACH process is initiated. + - **Confirmed**, the ACH process is in progress or complete. +- **Closed**: The report is closed. diff --git a/docs/articles/expensify-classic/reports/Submit-or-retract-a-report.md b/docs/articles/expensify-classic/reports/Submit-or-retract-a-report.md new file mode 100644 index 000000000000..857217189e50 --- /dev/null +++ b/docs/articles/expensify-classic/reports/Submit-or-retract-a-report.md @@ -0,0 +1,65 @@ +--- +title: Submit or retract a report +description: Submit a report for reimbursement or retract a submitted report to make corrections +--- +
+ +Once your report is ready to send, you can submit your expenses for approval. Depending on your workspace settings, your reports may be automatically submitted for you, or you may have to manually submit them. + +{% include info.html %} +Depending on your workspace settings, your reports may be automatically submitted or approved. In this case, you will not need to manually submit your reports. +{% include end-info.html %} + +# Manually submit a report + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab. +2. Click a report to open it. +3. Review the report for completion and click **Submit**. +4. Verify or enter the details for who will receive a notification email about your report and what they will receive: + - **To**: Enter the name(s) who will be approving your report (if they are not already listed). + - **CC**: Enter the email address of anyone else who should be notified that your expense report has been submitted. Add a comma between each email address if adding more than one. + - **Memo**: Enter any relevant notes. + - **Attach PDF**: Select this checkbox to attach a copy of your report to the email. +5. Click **Send**. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the ☰ menu icon in the top left. +2. Tap the **Reports** tab. +3. Tap a report to open it. +4. Review the report for completion and tap Submit Report. +5. Verify the details for who will receive a notification email about your report and what they will receive: + - **To**: Enter the name(s) who will be approving your report (if they are not already listed). + - **CC**: Enter the email address of anyone else who should be notified that your expense report has been submitted. Add a comma between each email address if adding more than one. + - **Memo**: Enter any relevant notes. + - **Attach PDF**: Select this checkbox to attach a copy of your report to the email. +6. Tap **Submit**. +{% include end-option.html %} + +{% include end-selector.html %} + +# Retract a report + +You can retract a submitted report to edit the reported expenses and re-submit the report. + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab. +2. Click a report to open it. +3. Click **Undo Submit** on the top left of the report. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the ☰ menu icon in the top left. +2. Tap the **Reports** tab. +3. Tap a report to open it. +4. Tap **Retract** at the top of the report. +{% include end-option.html %} + +{% include end-selector.html %} + +
diff --git a/docs/articles/expensify-classic/reports/The-Reports-Page.md b/docs/articles/expensify-classic/reports/The-Reports-Page.md deleted file mode 100644 index 9c55cd9b4b8d..000000000000 --- a/docs/articles/expensify-classic/reports/The-Reports-Page.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: The Reports Page -description: Details about the Reports Page filters and CSV export options ---- - -## How to use the Reports Page -The Reports page is your central hub for a high-level view of a Reports' status. You can see the Reports page on a web browser when you sign into your Expensify account. -Here, you can quickly see which reports need submission (referred to as **Open**), which are currently awaiting approval (referred to as **Processing**), and which reports have successfully been **Approved** or **Reimbursed**. -To streamline your experience, we've incorporated user-friendly filters on the Reports page. These filters allow you to refine your report search by specific criteria, such as dates, submitters, or their association with a workspace. - -## Report filters -- **Reset Filters/Show Filters:** You can reset or display your filters at the top of the Reports page. -- **From & To:** Use these fields to refine your search to a specific date range. -- **Report ID, Name, or Email:** Narrow your search by entering a Report ID, Report Name, or the submitter's email. -- **Report Types:** If you're specifically looking for Bills or Invoices, you can select this option. -- **Submitters:** Choose between "All Submitters" or enter a specific employee's email to view their reports. -- **Policies:** Select "All Policies" or specify a particular policy associated with the reports you're interested in. - -## Report status -- **Open icon:** These reports are still "In Progress" and must be submitted by the creator. If they contain company card expenses, a domain admin can submit them. If labeled as “Rejected," an Approver has rejected it, typically requiring some adjustments. Click into the report and review the History for any comments from your Approver. -- **Processing icon:** These reports have been submitted for Approval but have not received the final approval. -- **Approved icon:** Reports in this category have been Approved but have yet to be Reimbursed. For non-reimbursable reports, this is the final status. -- **Reimbursed icon:** These reports have been successfully Reimbursed. If you see "Withdrawing," it means the ACH (Automated Clearing House) process is initiated. "Confirmed" indicates the ACH process is in progress or complete. No additional status means your Admin is handling reimbursement outside of Expensify. -- **Closed icon:** This status represents an officially closed report. - - -## How to Export a report to a CSV -To export a report to a CSV file, follow these steps on the Reports page: - -1. Click the checkbox on the far left of the report row you want to export. -2. Navigate to the upper right corner of the page and click the "Export to" button. -3. From the drop-down options that appear, select your preferred export format. - -{% include faq-begin.md %} -## What does it mean if the integration icon for a report is grayed out? -If the integration icon for a report appears grayed out, the report has yet to be fully exported. -To address this, consider these options: -- Go to **Settings > Policies > Group > Connections** within the workspace associated with the report to check for any errors with the accounting integration (i.e., The connection to NetSuite, QuickBooks Online, Xero, Sage Intacct shows an error). -- Alternatively, click the “Sync Now" button on the Connections page to see if any error prevents the export. - -## How can I see a specific expense on a report? -To locate a specific expense within a report, click on the Report from the Reports page and then click on an expense to view the expense details. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/workspaces/Change-member-workspace-roles.md b/docs/articles/expensify-classic/workspaces/Change-member-workspace-roles.md index 29fbc8b46323..6a6f99fa398f 100644 --- a/docs/articles/expensify-classic/workspaces/Change-member-workspace-roles.md +++ b/docs/articles/expensify-classic/workspaces/Change-member-workspace-roles.md @@ -20,6 +20,7 @@ To change the roles and permissions for members of your workspace, | Approve workspace reports | Only reports submitted to them | Yes | Yes | | Edit workspace settings | No | No | Yes | +{:start="7"} 7. If your workspace uses Advanced Approvals, select an “Approves to.” This determines who the member’s reports must be approved by, if applicable. If “no one” is selected, then any one with the Auditor or Workspace Admin role can approve the member’s reports. 8. Click **Save**. diff --git a/docs/redirects.csv b/docs/redirects.csv index fac32e6e4e57..51c8c7515e10 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -109,3 +109,53 @@ https://help.expensify.com/articles/expensify-classic/reports/Expense-Rules,http https://help.expensify.com/articles/expensify-classic/reports/Currency,https://help.expensify.com/articles/expensify-classic/workspaces/Currency https://help.expensify.com/articles/expensify-classic/reports/The-Expenses-Page,https://help.expensify.com/articles/expensify-classic/expenses/The-Expenses-Page https://help.expensify.com/articles/expensify-classic/reports/Attendee-Tracking,https://help.expensify.com/articles/expensify-classic/expenses/Track-group-expenses +https://help.expensify.com/articles/expensify-classic/account-settings/Close-Account,https://help.expensify.com/articles/expensify-classic/settings/Close-or-reopen-account +https://help.expensify.com/articles/expensify-classic/account-settings/Copilot,https://help.expensify.com/expensify-classic/hubs/copilots-and-delegates/ +https://help.expensify.com/articles/expensify-classic/account-settings/Notification-Troubleshooting,https://help.expensify.com/articles/expensify-classic/settings/Notification-Troubleshooting +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription,https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Overview +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Billing-Overview,https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Overview +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Billing-Owner,https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Overview +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Change-Plan-Or-Subscription,https://help.expensify.com/articles/expensify-classic/expensify-billing/Change-Plan-Or-Subscription +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Consolidated-Domain-Billing,https://help.expensify.com/articles/expensify-classic/expensify-billing/Consolidated-Domain-Billing +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Individual-Subscription,https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Overview +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Pay-Per-Use-Subscription,https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Overview +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Receipt-Breakdown,https://help.expensify.com/articles/expensify-classic/expensify-billing/Receipt-Breakdown +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Tax-Exempt,https://help.expensify.com/articles/expensify-classic/expensify-billing/Tax-Exempt +https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Approving-Reports,https://help.expensify.com/expensify-classic/hubs/reports/ +https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Invite-Members,https://help.expensify.com/articles/expensify-classic/workspaces/Invite-members-and-assign-roles +https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Removing-Members,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Removing-Members +https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Attendee-Tracking,https://help.expensify.com/articles/expensify-classic/expenses/Track-group-expenses +https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Currency,https://help.expensify.com/articles/expensify-classic/workspaces/Currency +https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Expense-Rules,https://help.expensify.com/articles/expensify-classic/expenses/Expense-Rules +https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Expense-Types,https://help.expensify.com/articles/expensify-classic/expenses/Expense-Types +https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Report-Audit-Log-and-Comments,https://help.expensify.com/articles/expensify-classic/reports/Report-Audit-Log-and-Comments +https://help.expensify.com/articles/expensify-classic/expense-and-report-features/The-Expenses-Page,https://help.expensify.com/articles/expensify-classic/expenses/The-Expenses-Page +https://help.expensify.com/articles/expensify-classic/expense-and-report-features/The-Reports-Page,https://help.expensify.com/articles/expensify-classic/reports/The-Reports-Page +https://help.expensify.com/articles/expensify-classic/expenses/Per-Diem-Expenses,https://help.expensify.com/articles/expensify-classic/expenses/Track-per-diem-expenses +https://help.expensify.com/articles/expensify-classic/get-paid-back/Distance-Tracking,https://help.expensify.com/articles/expensify-classic/expenses/Track-mileage-expenses +https://help.expensify.com/articles/expensify-classic/get-paid-back/expenses/Apply-Tax,https://help.expensify.com/articles/expensify-classic/expenses/expenses/Apply-Tax +https://help.expensify.com/articles/expensify-classic/get-paid-back/expenses/Create-Expenses,https://help.expensify.com/articles/expensify-classic/expenses/expenses/Add-an-expense +https://help.expensify.com/articles/expensify-classic/get-paid-back/expenses/Merge-Expenses,https://help.expensify.com/articles/expensify-classic/expenses/expenses/Merge-expenses +https://help.expensify.com/articles/expensify-classic/get-paid-back/expenses/Upload-Receipts,https://help.expensify.com/articles/expensify-classic/expenses/expenses/Add-an-expense +https://help.expensify.com/articles/expensify-classic/get-paid-back/Per-Diem-Expenses,https://help.expensify.com/articles/expensify-classic/expenses/Track-per-diem-expenses +https://help.expensify.com/articles/expensify-classic/get-paid-back/Referral-Program,https://help.expensify.com/articles/new-expensify/expenses/Referral-Program +https://help.expensify.com/articles/expensify-classic/get-paid-back/reports/Create-A-Report,https://help.expensify.com/articles/expensify-classic/expenses/reports/Create-A-Report +https://help.expensify.com/articles/expensify-classic/get-paid-back/reports/Reimbursements,https://help.expensify.com/articles/expensify-classic/expenses/reports/Reimbursements +https://help.expensify.com/articles/expensify-classic/get-paid-back/Trips,https://help.expensify.com/articles/expensify-classic/expenses/Trips +https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Custom-Templates,https://help.expensify.com/articles/expensify-classic/spending-insights/Custom-Templates +https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Default-Export-Templates,https://help.expensify.com/articles/expensify-classic/spending-insights/Default-Export-Templates +https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Fringe-Benefits,https://help.expensify.com/articles/expensify-classic/spending-insights/Fringe-Benefits +https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Insights,https://help.expensify.com/articles/expensify-classic/spending-insights/Insights +https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Other-Export-Options,https://help.expensify.com/articles/expensify-classic/spending-insights/Other-Export-Options +https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Approval-Workflows,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Approval-Workflows +https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Approving-Reports +https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Invite-Members,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Invite-Members +https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Removing-Members,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Removing-Members +https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles,https://help.expensify.com/expensify-classic/hubs/copilots-and-delegates/ +https://help.expensify.com/articles/expensify-classic/reports/Currency,https://help.expensify.com/articles/expensify-classic/workspaces/Currency +https://help.expensify.com/articles/expensify-classic/send-payments/Reimbursing-Reports,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Reimbursing-Reports +https://help.expensify.com/articles/expensify-classic/workspace-and-domain-settings/SAML-SSO,https://help.expensify.com/articles/expensify-classic/settings/Enable-two-factor-authentication +https://help.expensify.com/articles/expensify-classic/workspaces/Budgets,https://help.expensify.com/articles/expensify-classic/workspaces/Set-budgets +https://help.expensify.com/articles/expensify-classic/workspaces/Categories,https://help.expensify.com/articles/expensify-classic/workspaces/Create-categories +https://help.expensify.com/articles/expensify-classic/workspaces/Tags,https://help.expensify.com/articles/expensify-classic/workspaces/Create-tags +https://help.expensify.com/expensify-classic/hubs/manage-employees-and-report-approvals,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Approval-Workflows diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 9d09b4dc04f2..7f50db5da85a 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -626,9 +626,7 @@ "${PODS_ROOT}/Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipAutomationResources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipCoreResources.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipExtendedActionsResources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipMessageCenterResources.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipPreferenceCenterResources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher_Core_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GoogleSignIn/GoogleSignIn.bundle", @@ -641,9 +639,7 @@ outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipAutomationResources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipCoreResources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipExtendedActionsResources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipMessageCenterResources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipPreferenceCenterResources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GTMSessionFetcher_Core_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle", @@ -793,9 +789,7 @@ "${PODS_ROOT}/Target Support Files/Pods-NewExpensify/Pods-NewExpensify-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipAutomationResources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipCoreResources.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipExtendedActionsResources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipMessageCenterResources.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipPreferenceCenterResources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher_Core_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GoogleSignIn/GoogleSignIn.bundle", @@ -808,9 +802,7 @@ outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipAutomationResources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipCoreResources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipExtendedActionsResources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipMessageCenterResources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipPreferenceCenterResources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GTMSessionFetcher_Core_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle", diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 23ad07539da1..4dea8203b477 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.62.0 + 1.4.62.10 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 8e8588c64720..0d1e81ade440 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.62.0 + 1.4.62.10 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 8a1bd000fd9a..a2dfb017df48 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.62 CFBundleVersion - 1.4.62.0 + 1.4.62.10 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2f53f00918bf..1ebfc6bb1b62 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,25 +1,24 @@ PODS: - - Airship (16.12.1): - - Airship/Automation (= 16.12.1) - - Airship/Basement (= 16.12.1) - - Airship/Core (= 16.12.1) - - Airship/ExtendedActions (= 16.12.1) - - Airship/MessageCenter (= 16.12.1) - - Airship/Automation (16.12.1): + - Airship (17.7.3): + - Airship/Automation (= 17.7.3) + - Airship/Basement (= 17.7.3) + - Airship/Core (= 17.7.3) + - Airship/FeatureFlags (= 17.7.3) + - Airship/MessageCenter (= 17.7.3) + - Airship/PreferenceCenter (= 17.7.3) + - Airship/Automation (17.7.3): - Airship/Core - - Airship/Basement (16.12.1) - - Airship/Core (16.12.1): + - Airship/Basement (17.7.3) + - Airship/Core (17.7.3): - Airship/Basement - - Airship/ExtendedActions (16.12.1): + - Airship/FeatureFlags (17.7.3): - Airship/Core - - Airship/MessageCenter (16.12.1): + - Airship/MessageCenter (17.7.3): - Airship/Core - - Airship/PreferenceCenter (16.12.1): + - Airship/PreferenceCenter (17.7.3): - Airship/Core - - AirshipFrameworkProxy (2.1.1): - - Airship (= 16.12.1) - - Airship/MessageCenter (= 16.12.1) - - Airship/PreferenceCenter (= 16.12.1) + - AirshipFrameworkProxy (5.1.1): + - Airship (= 17.7.3) - AirshipServiceExtension (17.8.0) - AppAuth (1.6.2): - AppAuth/Core (= 1.6.2) @@ -1162,8 +1161,8 @@ PODS: - React-Mapbuffer (0.73.4): - glog - React-debug - - react-native-airship (15.3.1): - - AirshipFrameworkProxy (= 2.1.1) + - react-native-airship (17.2.1): + - AirshipFrameworkProxy (= 5.1.1) - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1237,7 +1236,7 @@ PODS: - React-Codegen - React-Core - ReactCommon/turbomodule/core - - react-native-geolocation (3.0.6): + - react-native-geolocation (3.2.1): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1836,7 +1835,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.38): + - RNLiveMarkdown (0.1.47): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1854,9 +1853,9 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/common (= 0.1.38) + - RNLiveMarkdown/common (= 0.1.47) - Yoga - - RNLiveMarkdown/common (0.1.38): + - RNLiveMarkdown/common (0.1.47): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1945,7 +1944,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - RNScreens (3.29.0): + - RNScreens (3.30.1): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1963,9 +1962,9 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNScreens/common (= 3.29.0) + - RNScreens/common (= 3.30.1) - Yoga - - RNScreens/common (3.29.0): + - RNScreens/common (3.30.1): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2438,8 +2437,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - Airship: 2f4510b497a8200780752a5e0304a9072bfffb6d - AirshipFrameworkProxy: ea1b6c665c798637b93c465b5e505be3011f1d9d + Airship: 5a6d3f8a982398940b0d48423bb9b8736717c123 + AirshipFrameworkProxy: 7255f4ed9836dc2920f2f1ea5657ced4cee8a35c AirshipServiceExtension: 0a5fb14c3fd1879355ab05a81d10f64512a4f79c AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570 boost: d3f49c53809116a5d38da093a8aa78bf551aed09 @@ -2508,12 +2507,12 @@ SPEC CHECKSUMS: React-jsitracing: e8a2dafb9878dbcad02b6b2b88e66267fb427b74 React-logger: 0a57b68dd2aec7ff738195f081f0520724b35dab React-Mapbuffer: 63913773ed7f96b814a2521e13e6d010282096ad - react-native-airship: 2ed75ff2278f11ff1c1ab08ed68f5bf02727b971 + react-native-airship: 6ab7a7974d53f92b0c46548fc198f797fdbf371f react-native-blob-util: a3ee23cfdde79c769c138d505670055de233b07a react-native-cameraroll: 95ce0d1a7d2d1fe55bf627ab806b64de6c3e69e9 react-native-config: 5ce986133b07fc258828b20b9506de0e683efc1c react-native-document-picker: 8532b8af7c2c930f9e202aac484ac785b0f4f809 - react-native-geolocation: dcc37809bc117ffdb5946fecc127d62319ccd4a9 + react-native-geolocation: c1c21a8cda4abae6724a322458f64ac6889b8c2b react-native-image-picker: f8a13ff106bcc7eb00c71ce11fdc36aac2a44440 react-native-key-command: 74d18ad516037536c2f671ef0914bcce7739b2f5 react-native-launch-arguments: 5f41e0abf88a15e3c5309b8875d6fd5ac43df49d @@ -2565,13 +2564,13 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 1190c218cdaaf029ee1437076a3fbbc3297d89fb RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 - RNLiveMarkdown: 9d974f060d0bd857f7d96fac0e9a1539363baa5e + RNLiveMarkdown: f172c7199283dc9d21bccf7e21ea10741fd19e1d RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: 3e273e0e867a079ec33df9ee33bb0482434b897d RNPermissions: 8990fc2c10da3640938e6db1647cb6416095b729 RNReactNativeHapticFeedback: 616c35bdec7d20d4c524a7949ca9829c09e35f37 RNReanimated: 605409e0d0ced6f2e194ae585fedc2f8a1935bf2 - RNScreens: f7b8bb892b4957f6f91e5dfd9a191e7f13ce8baa + RNScreens: 65a936f4e227b91e4a8e2a7d4c4607355bfefda0 RNShare: 2a4cdfc0626ad56b0ef583d424f2038f772afe58 RNSound: 6c156f925295bdc83e8e422e7d8b38d33bc71852 RNSVG: db32cfcad0a221fd175e0882eff7bcba7690380a @@ -2582,7 +2581,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 3033e0dd5272d46e97bcb406adea4ae0e6907abf - Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 + Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 PODFILE CHECKSUM: a25a81f2b50270f0c0bd0aff2e2ebe4d0b4ec06d diff --git a/package-lock.json b/package-lock.json index 5315c22d9161..203e062de680 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "new.expensify", - "version": "1.4.62-0", + "version": "1.4.62-10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.62-0", + "version": "1.4.62-10", "hasInstallScript": true, "license": "MIT", "dependencies": { "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "github:Expensify/react-native-live-markdown#f762be6fa832419dbbecb8a0cf64bf7dce18545b", + "@expensify/react-native-live-markdown": "0.1.47", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -28,7 +28,7 @@ "@react-native-async-storage/async-storage": "1.21.0", "@react-native-camera-roll/camera-roll": "7.4.0", "@react-native-clipboard/clipboard": "^1.13.2", - "@react-native-community/geolocation": "^3.0.6", + "@react-native-community/geolocation": "3.2.1", "@react-native-community/netinfo": "11.2.1", "@react-native-firebase/analytics": "^12.3.0", "@react-native-firebase/app": "^12.3.0", @@ -47,7 +47,7 @@ "@storybook/cli": "^8.0.6", "@storybook/react": "^8.0.6", "@storybook/theming": "^8.0.6", - "@ua/react-native-airship": "^15.3.1", + "@ua/react-native-airship": "17.2.1", "@vue/preload-webpack-plugin": "^2.0.0", "awesome-phonenumber": "^5.4.0", "babel-polyfill": "^6.26.0", @@ -101,7 +101,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.23", + "react-native-onyx": "2.0.27", "react-native-pager-view": "6.2.3", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -114,7 +114,7 @@ "react-native-release-profiler": "^0.1.6", "react-native-render-html": "6.3.1", "react-native-safe-area-context": "4.8.2", - "react-native-screens": "3.29.0", + "react-native-screens": "3.30.1", "react-native-share": "^10.0.2", "react-native-sound": "^0.11.2", "react-native-svg": "14.1.0", @@ -3570,13 +3570,9 @@ } }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.38", - "resolved": "git+ssh://git@github.com/Expensify/react-native-live-markdown.git#f762be6fa832419dbbecb8a0cf64bf7dce18545b", - "integrity": "sha512-m8+t3y1AtpvFAt3GAwRCiGwcOhUagOTCvwJ87kMGO5q/SKB2GCBHYMQ0QZaHw2QvAzRE6v2kCdqItX5DY+4MPQ==", - "license": "MIT", - "workspaces": [ - "example" - ], + "version": "0.1.47", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.47.tgz", + "integrity": "sha512-zUfwgg6qq47MnGuynamDpdHSlBYwVKFV4Zc/2wlVzFcBndQOjOyFu04Ns8YDB4Gl80LyGvfAuBT/sU+kvmMU6g==", "engines": { "node": ">= 18.0.0" }, @@ -8794,8 +8790,12 @@ "license": "MIT" }, "node_modules/@react-native-community/geolocation": { - "version": "3.0.6", - "license": "MIT", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@react-native-community/geolocation/-/geolocation-3.2.1.tgz", + "integrity": "sha512-/+HNzuRl4UCMma7KK+KYL8k2nxAGuW+DGxqmqfpiqKBlCkCUbuFHaZZdqVD6jpsn9r/ghe583ECLmd9SV9I4Bw==", + "engines": { + "node": ">=18.0.0" + }, "peerDependencies": { "react": "*", "react-native": "*" @@ -13340,8 +13340,9 @@ } }, "node_modules/@ua/react-native-airship": { - "version": "15.3.1", - "license": "Apache-2.0", + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/@ua/react-native-airship/-/react-native-airship-17.2.1.tgz", + "integrity": "sha512-+C5fuPU4MMEpN7I5NbrR8F8awPyaHC732ONxMAZhrjVbfNVuZlpCwptz1xmiRkfiH/nzxhF5uvf+CiOKVYamPQ==", "engines": { "node": ">= 16.0.0" }, @@ -31340,9 +31341,9 @@ } }, "node_modules/react-native-onyx": { - "version": "2.0.23", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.23.tgz", - "integrity": "sha512-A0SuipCwAswl8+hBlr9tNPTBdxixKBJkcdP8YpgUC072/4Kcfvv0pfpfQOHoeCR/bP2wvDagpdN3VtebRfoVMg==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.27.tgz", + "integrity": "sha512-mNtXmJ2r7UwEym2J7Tu09M42QoxIhwEdiGYDw9v26wp/kQCJChKTP0yUrp8QdPKkcwywRFPVlNxt3Rx8Mp0hFg==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -31562,8 +31563,9 @@ } }, "node_modules/react-native-screens": { - "version": "3.29.0", - "license": "MIT", + "version": "3.30.1", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.30.1.tgz", + "integrity": "sha512-/muEvjocCtFb+j5J3YmLvB25+f4rIU8hnnxgGTkXcAf2omPBY8uhPjJaaFUlvj64VEoEzJcRpugbXWsjfPPIFg==", "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" diff --git a/package.json b/package.json index cca384eeb386..43a3ed8cae6a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.62-0", + "version": "1.4.62-10", "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.", @@ -64,7 +64,7 @@ "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "github:Expensify/react-native-live-markdown#f762be6fa832419dbbecb8a0cf64bf7dce18545b", + "@expensify/react-native-live-markdown": "0.1.47", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -79,7 +79,7 @@ "@react-native-async-storage/async-storage": "1.21.0", "@react-native-camera-roll/camera-roll": "7.4.0", "@react-native-clipboard/clipboard": "^1.13.2", - "@react-native-community/geolocation": "^3.0.6", + "@react-native-community/geolocation": "3.2.1", "@react-native-community/netinfo": "11.2.1", "@react-native-firebase/analytics": "^12.3.0", "@react-native-firebase/app": "^12.3.0", @@ -98,7 +98,7 @@ "@storybook/cli": "^8.0.6", "@storybook/react": "^8.0.6", "@storybook/theming": "^8.0.6", - "@ua/react-native-airship": "^15.3.1", + "@ua/react-native-airship": "17.2.1", "@vue/preload-webpack-plugin": "^2.0.0", "awesome-phonenumber": "^5.4.0", "babel-polyfill": "^6.26.0", @@ -152,7 +152,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.23", + "react-native-onyx": "2.0.27", "react-native-pager-view": "6.2.3", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -165,7 +165,7 @@ "react-native-release-profiler": "^0.1.6", "react-native-render-html": "6.3.1", "react-native-safe-area-context": "4.8.2", - "react-native-screens": "3.29.0", + "react-native-screens": "3.30.1", "react-native-share": "^10.0.2", "react-native-sound": "^0.11.2", "react-native-svg": "14.1.0", diff --git a/patches/@react-native-community+geolocation+3.1.0.patch b/patches/@react-native-community+geolocation+3.1.0.patch deleted file mode 100644 index 5afa0b8e0897..000000000000 --- a/patches/@react-native-community+geolocation+3.1.0.patch +++ /dev/null @@ -1,38 +0,0 @@ -diff --git a/node_modules/@react-native-community/geolocation/react-native-geolocation.podspec b/node_modules/@react-native-community/geolocation/react-native-geolocation.podspec -index a319e73..c1ea11c 100644 ---- a/node_modules/@react-native-community/geolocation/react-native-geolocation.podspec -+++ b/node_modules/@react-native-community/geolocation/react-native-geolocation.podspec -@@ -1,8 +1,6 @@ - require 'json' - - package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) --folly_version = '2021.07.22.00' --folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' - - Pod::Spec.new do |s| - s.name = "react-native-geolocation" -@@ -17,20 +15,11 @@ Pod::Spec.new do |s| - s.source = { :git => "https://github.com/react-native-community/react-native-geolocation.git", :tag => "v#{s.version}" } - s.source_files = "ios/**/*.{h,m,mm}" - -- s.dependency 'React-Core' - s.frameworks = 'CoreLocation' - -- if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then -- s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" -- s.pod_target_xcconfig = { -- "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", -- "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" -- } -- -- s.dependency "React-Codegen" -- s.dependency "RCT-Folly", folly_version -- s.dependency "RCTRequired" -- s.dependency "RCTTypeSafety" -- s.dependency "ReactCommon/turbomodule/core" -+ if defined?(install_modules_dependencies()) != nil -+ install_modules_dependencies(s) -+ else -+ s.dependency "React-Core" - end - end diff --git a/patches/@ua+react-native-airship+15.3.1.patch b/patches/@ua+react-native-airship+15.3.1.patch deleted file mode 100644 index 1ab11c1f7444..000000000000 --- a/patches/@ua+react-native-airship+15.3.1.patch +++ /dev/null @@ -1,24 +0,0 @@ -diff --git a/node_modules/@ua/react-native-airship/react-native-airship.podspec b/node_modules/@ua/react-native-airship/react-native-airship.podspec -index 5e0ce2d..4456f61 100644 ---- a/node_modules/@ua/react-native-airship/react-native-airship.podspec -+++ b/node_modules/@ua/react-native-airship/react-native-airship.podspec -@@ -20,18 +20,7 @@ Pod::Spec.new do |s| - - # Don't install the dependencies when we run `pod install` in the old architecture. - if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then -- s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" -- s.pod_target_xcconfig = { -- "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", -- "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", -- "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" -- } -- s.dependency "React-Codegen" -- s.dependency "React-RCTFabric" -- s.dependency "RCT-Folly" -- s.dependency "RCTRequired" -- s.dependency "RCTTypeSafety" -- s.dependency "ReactCommon/turbomodule/core" -+ install_modules_dependencies(s) - end - - diff --git a/patches/react-native-quick-sqlite+8.0.0-beta.2.patch b/patches/react-native-quick-sqlite+8.0.0-beta.2.patch new file mode 100644 index 000000000000..b5810c903873 --- /dev/null +++ b/patches/react-native-quick-sqlite+8.0.0-beta.2.patch @@ -0,0 +1,12 @@ +diff --git a/node_modules/react-native-quick-sqlite/android/build.gradle b/node_modules/react-native-quick-sqlite/android/build.gradle +index 323d34e..c2d0c44 100644 +--- a/node_modules/react-native-quick-sqlite/android/build.gradle ++++ b/node_modules/react-native-quick-sqlite/android/build.gradle +@@ -90,7 +90,6 @@ android { + externalNativeBuild { + cmake { + cppFlags "-O2", "-fexceptions", "-frtti", "-std=c++1y", "-DONANDROID" +- abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' + arguments '-DANDROID_STL=c++_shared', + "-DREACT_NATIVE_DIR=${REACT_NATIVE_DIR}", + "-DSQLITE_FLAGS='${SQLITE_FLAGS ? SQLITE_FLAGS : ''}'" diff --git a/patches/react-native-screens+3.29.0+001+initial.patch b/patches/react-native-screens+3.29.0+001+initial.patch deleted file mode 100644 index dbe65b2abf3f..000000000000 --- a/patches/react-native-screens+3.29.0+001+initial.patch +++ /dev/null @@ -1,49 +0,0 @@ -diff --git a/node_modules/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt b/node_modules/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt -index d9e0e58..2d946c4 100644 ---- a/node_modules/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt -+++ b/node_modules/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt -@@ -3,7 +3,6 @@ package com.swmansion.rnscreens - import android.view.ViewGroup - import androidx.annotation.UiThread - import com.facebook.react.bridge.ReactContext --import com.facebook.react.bridge.ReadableMap - import com.facebook.react.bridge.WritableMap - import com.facebook.react.bridge.WritableNativeMap - import com.facebook.react.uimanager.FabricViewStateManager -@@ -13,6 +12,9 @@ import kotlin.math.abs - abstract class FabricEnabledViewGroup constructor(context: ReactContext?) : ViewGroup(context), FabricViewStateManager.HasFabricViewStateManager { - private val mFabricViewStateManager: FabricViewStateManager = FabricViewStateManager() - -+ private var lastSetWidth = 0f -+ private var lastSetHeight = 0f -+ - override fun getFabricViewStateManager(): FabricViewStateManager { - return mFabricViewStateManager - } -@@ -28,17 +30,16 @@ abstract class FabricEnabledViewGroup constructor(context: ReactContext?) : View - - // Check incoming state values. If they're already the correct value, return early to prevent - // infinite UpdateState/SetState loop. -- val currentState: ReadableMap? = mFabricViewStateManager.getStateData() -- if (currentState != null) { -- val delta = 0.9f -- val stateFrameHeight: Float = if (currentState.hasKey("frameHeight")) currentState.getDouble("frameHeight").toFloat() else 0f -- val stateFrameWidth: Float = if (currentState.hasKey("frameWidth")) currentState.getDouble("frameWidth").toFloat() else 0f -- if (abs(stateFrameWidth - realWidth) < delta && -- abs(stateFrameHeight - realHeight) < delta -- ) { -- return -- } -+ val delta = 0.9f -+ if (abs(lastSetWidth - realWidth) < delta && -+ abs(lastSetHeight - realHeight) < delta -+ ) { -+ return - } -+ -+ lastSetWidth = realWidth -+ lastSetHeight = realHeight -+ - mFabricViewStateManager.setState { - val map: WritableMap = WritableNativeMap() - map.putDouble("frameWidth", realWidth.toDouble()) diff --git a/patches/react-native-screens+3.29.0+002+fixLayoutIssues.patch b/patches/react-native-screens+3.29.0+002+fixLayoutIssues.patch deleted file mode 100644 index 9654c9cfcb42..000000000000 --- a/patches/react-native-screens+3.29.0+002+fixLayoutIssues.patch +++ /dev/null @@ -1,214 +0,0 @@ -diff --git a/node_modules/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt b/node_modules/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt -index 2d946c4..ccda8f3 100644 ---- a/node_modules/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt -+++ b/node_modules/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt -@@ -12,38 +12,36 @@ import kotlin.math.abs - abstract class FabricEnabledViewGroup constructor(context: ReactContext?) : ViewGroup(context), FabricViewStateManager.HasFabricViewStateManager { - private val mFabricViewStateManager: FabricViewStateManager = FabricViewStateManager() - -- private var lastSetWidth = 0f -- private var lastSetHeight = 0f -+ private var lastHeaderHeight: Double = 0.0 - - override fun getFabricViewStateManager(): FabricViewStateManager { - return mFabricViewStateManager - } - -- protected fun updateScreenSizeFabric(width: Int, height: Int) { -- updateState(width, height) -+ protected fun updateScreenSizeFabric(width: Int, height: Int, headerHeight: Double) { -+ updateState(width, height, headerHeight) - } - - @UiThread -- fun updateState(width: Int, height: Int) { -+ fun updateState(width: Int, height: Int, headerHeight: Double) { - val realWidth: Float = PixelUtil.toDIPFromPixel(width.toFloat()) - val realHeight: Float = PixelUtil.toDIPFromPixel(height.toFloat()) - - // Check incoming state values. If they're already the correct value, return early to prevent - // infinite UpdateState/SetState loop. -- val delta = 0.9f -- if (abs(lastSetWidth - realWidth) < delta && -- abs(lastSetHeight - realHeight) < delta -- ) { -+ val delta = 0.9 -+ if (abs(lastHeaderHeight - headerHeight) < delta) { - return - } - -- lastSetWidth = realWidth -- lastSetHeight = realHeight -+ lastHeaderHeight = headerHeight - - mFabricViewStateManager.setState { - val map: WritableMap = WritableNativeMap() - map.putDouble("frameWidth", realWidth.toDouble()) - map.putDouble("frameHeight", realHeight.toDouble()) -+ map.putDouble("contentOffsetX", 0.0) -+ map.putDouble("contentOffsetY", headerHeight) - map - } - } -diff --git a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/Screen.kt -index a7d28f9..e0b0d8e 100644 ---- a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/Screen.kt -+++ b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/Screen.kt -@@ -72,9 +72,9 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex - val width = r - l - val height = b - t - -- calculateHeaderHeight() -+ val headerHeight = if (container is ScreenStack) calculateHeaderHeight().first else 0.0 - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { -- updateScreenSizeFabric(width, height) -+ updateScreenSizeFabric(width, height, headerHeight) - } else { - updateScreenSizePaper(width, height) - } -@@ -246,7 +246,7 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex - mNativeBackButtonDismissalEnabled = enableNativeBackButtonDismissal - } - -- private fun calculateHeaderHeight() { -+ private fun calculateHeaderHeight(): Pair { - val actionBarTv = TypedValue() - val resolvedActionBarSize = context.theme.resolveAttribute(android.R.attr.actionBarSize, actionBarTv, true) - -@@ -265,6 +265,8 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex - val totalHeight = actionBarHeight + statusBarHeight - UIManagerHelper.getEventDispatcherForReactTag(context as ReactContext, id) - ?.dispatchEvent(HeaderHeightChangeEvent(id, totalHeight)) -+ -+ return actionBarHeight to statusBarHeight - } - - enum class StackPresentation { -diff --git a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenComponentDescriptor.h b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenComponentDescriptor.h -index 67194d3..c1a1b40 100644 ---- a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenComponentDescriptor.h -+++ b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenComponentDescriptor.h -@@ -13,7 +13,7 @@ class RNSScreenComponentDescriptor final - using ConcreteComponentDescriptor::ConcreteComponentDescriptor; - - void adopt(ShadowNode& shadowNode) const override { -- react_native_assert( -+ react_native_assert( - dynamic_cast(&shadowNode)); - auto& screenShadowNode = - static_cast(shadowNode); -@@ -28,10 +28,7 @@ class RNSScreenComponentDescriptor final - shadowNode.getState()); - auto stateData = state->getData(); - -- if (stateData.frameSize.width != 0 && stateData.frameSize.height != 0) { -- layoutableShadowNode.setSize( -- Size{stateData.frameSize.width, stateData.frameSize.height}); -- } -+ layoutableShadowNode.setPadding({.bottom = stateData.contentOffset.y}); - - ConcreteComponentDescriptor::adopt(shadowNode); - } -diff --git a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.cpp b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.cpp -index ba61ed8..0e3746e 100644 ---- a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.cpp -+++ b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.cpp -@@ -5,5 +5,11 @@ namespace react { - - extern const char RNSScreenComponentName[] = "RNSScreen"; - -+Point RNSScreenShadowNode::getContentOriginOffset() const { -+ auto stateData = getStateData(); -+ auto contentOffset = stateData.contentOffset; -+ return {contentOffset.x, contentOffset.y}; -+} -+ - } // namespace react - } // namespace facebook -diff --git a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.h b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.h -index ef25dd1..bbd7599 100644 ---- a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.h -+++ b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.h -@@ -19,9 +19,11 @@ class JSI_EXPORT RNSScreenShadowNode final : public ConcreteViewShadowNode< - public: - using ConcreteViewShadowNode::ConcreteViewShadowNode; - -+ Point getContentOriginOffset() const override; -+ - static ShadowNodeTraits BaseTraits() { - auto traits = ConcreteViewShadowNode::BaseTraits(); -- traits.set(ShadowNodeTraits::Trait::RootNodeKind); -+ // traits.set(ShadowNodeTraits::Trait::RootNodeKind); - return traits; - } - }; -diff --git a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenState.cpp b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenState.cpp -index 69c77a6..6c3b8ca 100644 ---- a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenState.cpp -+++ b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenState.cpp -@@ -6,8 +6,8 @@ namespace react { - #ifdef ANDROID - folly::dynamic RNSScreenState::getDynamic() const { - return folly::dynamic::object("frameWidth", frameSize.width)( -- "frameHeight", frameSize.height); --} -+ "frameHeight", frameSize.height)("contentOffsetX", contentOffset.x)("contentOffsetY", contentOffset.y); -+ } - #endif - - } // namespace react -diff --git a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenState.h b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenState.h -index ce09807..e26d411 100644 ---- a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenState.h -+++ b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenState.h -@@ -17,7 +17,7 @@ class JSI_EXPORT RNSScreenState final { - using Shared = std::shared_ptr; - - RNSScreenState(){}; -- RNSScreenState(Size frameSize_) : frameSize(frameSize_){}; -+ RNSScreenState(Size frameSize_, Point contentOffset_) : frameSize(frameSize_), contentOffset(contentOffset_){}; - - #ifdef ANDROID - RNSScreenState( -@@ -25,10 +25,14 @@ class JSI_EXPORT RNSScreenState final { - folly::dynamic data) - : frameSize(Size{ - (Float)data["frameWidth"].getDouble(), -- (Float)data["frameHeight"].getDouble()}){}; -+ (Float)data["frameHeight"].getDouble()}), -+ contentOffset(Point{ -+ (Float)data["contentOffsetX"].getDouble(), -+ (Float)data["contentOffsetY"].getDouble()}){}; - #endif - - const Size frameSize{}; -+ Point contentOffset; - - #ifdef ANDROID - folly::dynamic getDynamic() const; -diff --git a/node_modules/react-native-screens/ios/RNSScreen.h b/node_modules/react-native-screens/ios/RNSScreen.h -index f1bd9d8..797fc12 100644 ---- a/node_modules/react-native-screens/ios/RNSScreen.h -+++ b/node_modules/react-native-screens/ios/RNSScreen.h -@@ -42,6 +42,7 @@ namespace react = facebook::react; - #ifdef RCT_NEW_ARCH_ENABLED - - (void)setViewToSnapshot:(UIView *)snapshot; - - (void)resetViewToScreen; -+- (CGFloat)calculateHeaderHeightIsModal:(BOOL)isModal; - #endif - - @end -diff --git a/node_modules/react-native-screens/ios/RNSScreen.mm b/node_modules/react-native-screens/ios/RNSScreen.mm -index 4b24cff..8f480ca 100644 ---- a/node_modules/react-native-screens/ios/RNSScreen.mm -+++ b/node_modules/react-native-screens/ios/RNSScreen.mm -@@ -107,7 +107,8 @@ - (void)updateBounds - { - #ifdef RCT_NEW_ARCH_ENABLED - if (_state != nullptr) { -- auto newState = react::RNSScreenState{RCTSizeFromCGSize(self.bounds.size)}; -+ CGFloat headerHeight = [_controller calculateHeaderHeightIsModal:self.isPresentedAsNativeModal]; -+ auto newState = react::RNSScreenState{RCTSizeFromCGSize(self.bounds.size), RCTPointFromCGPoint(CGPointMake(0, headerHeight))}; - _state->updateState(std::move(newState)); - UINavigationController *navctr = _controller.navigationController; - [navctr.view setNeedsLayout]; diff --git a/patches/react-native-screens+3.29.0+003+fixIOSHeaderHeight.patch b/patches/react-native-screens+3.29.0+003+fixIOSHeaderHeight.patch deleted file mode 100644 index ae162204a692..000000000000 --- a/patches/react-native-screens+3.29.0+003+fixIOSHeaderHeight.patch +++ /dev/null @@ -1,35 +0,0 @@ -diff --git a/node_modules/react-native-screens/ios/RNSScreen.mm b/node_modules/react-native-screens/ios/RNSScreen.mm -index 8f480ca..4cc5e7b 100644 ---- a/node_modules/react-native-screens/ios/RNSScreen.mm -+++ b/node_modules/react-native-screens/ios/RNSScreen.mm -@@ -108,7 +108,7 @@ - (void)updateBounds - #ifdef RCT_NEW_ARCH_ENABLED - if (_state != nullptr) { - CGFloat headerHeight = [_controller calculateHeaderHeightIsModal:self.isPresentedAsNativeModal]; -- auto newState = react::RNSScreenState{RCTSizeFromCGSize(self.bounds.size), RCTPointFromCGPoint(CGPointMake(0, headerHeight))}; -+ auto newState = react::RNSScreenState{RCTSizeFromCGSize(self.bounds.size), RCTPointFromCGPoint(CGPointMake(0, 0))}; - _state->updateState(std::move(newState)); - UINavigationController *navctr = _controller.navigationController; - [navctr.view setNeedsLayout]; -@@ -1106,17 +1106,11 @@ - (CGFloat)calculateHeaderHeightIsModal:(BOOL)isModal - { - UINavigationController *navctr = [self getVisibleNavigationControllerIsModal:isModal]; - -- // If navigation controller doesn't exists (or it is hidden) we want to handle two possible cases. -- // If there's no navigation controller for the modal, we simply don't want to return header height, as modal possibly -- // does not have header and we don't want to count status bar. If there's no navigation controller for the view we -- // just want to return status bar height (if it's hidden, it will simply return 0). -+ // If there's no navigation controller for the modal (or the navigation bar is hidden), we simply don't want to -+ // return header height, as modal possibly does not have header when navigation controller is nil, -+ // and we don't want to count status bar if navigation bar is hidden (inset could be negative). - if (navctr == nil || navctr.isNavigationBarHidden) { -- if (isModal) { -- return 0; -- } else { -- CGSize statusBarSize = [self getStatusBarHeightIsModal:isModal]; -- return MIN(statusBarSize.width, statusBarSize.height); -- } -+ return 0; - } - - CGFloat navbarHeight = navctr.navigationBar.frame.size.height; diff --git a/src/CONST.ts b/src/CONST.ts index 39c3d9d3109b..74e722cdba59 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -683,6 +683,7 @@ const CONST = { UNAPPROVED: 'UNAPPROVED', // OldDot Action UNHOLD: 'UNHOLD', UNSHARE: 'UNSHARE', // OldDot Action + UPDATEGROUPCHATMEMBERROLE: 'UPDATEGROUPCHATMEMBERROLE', POLICYCHANGELOG: { ADD_APPROVER_RULE: 'POLICYCHANGELOG_ADD_APPROVER_RULE', ADD_BUDGET: 'POLICYCHANGELOG_ADD_BUDGET', @@ -749,6 +750,7 @@ const CONST = { UPDATE_TAG_NAME: 'POLICYCHANGELOG_UPDATE_TAG_NAME', UPDATE_TIME_ENABLED: 'POLICYCHANGELOG_UPDATE_TIME_ENABLED', UPDATE_TIME_RATE: 'POLICYCHANGELOG_UPDATE_TIME_RATE', + LEAVE_POLICY: 'POLICYCHANGELOG_LEAVE_POLICY', }, ROOMCHANGELOG: { INVITE_TO_ROOM: 'INVITETOROOM', @@ -1862,17 +1864,17 @@ const CONST = { MAX_THREAD_REPLIES_PREVIEW: 99, + // Character Limits FORM_CHARACTER_LIMIT: 50, LEGAL_NAMES_CHARACTER_LIMIT: 150, LOGIN_CHARACTER_LIMIT: 254, CATEGORY_NAME_LIMIT: 256, - TAG_NAME_LIMIT: 256, - + REPORT_NAME_LIMIT: 256, TITLE_CHARACTER_LIMIT: 100, DESCRIPTION_LIMIT: 500, - WORKSPACE_NAME_CHARACTER_LIMIT: 80, + AVATAR_CROP_MODAL: { // The next two constants control what is min and max value of the image crop scale. // Values define in how many times the image can be bigger than its container. @@ -4313,6 +4315,9 @@ const CONST = { SESSION_STORAGE_KEYS: { INITIAL_URL: 'INITIAL_URL', }, + + DOT_SEPARATOR: '•', + DEFAULT_TAX: { defaultExternalID: 'id_TAX_EXEMPT', defaultValue: '0%', diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 42ca7ee7a907..3959f76a626f 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -157,7 +157,7 @@ const ONYXKEYS = { FREQUENTLY_USED_EMOJIS: 'nvp_expensify_frequentlyUsedEmojis', /** The NVP with the last distance rate used per policy */ - NVP_LAST_SELECTED_DISTANCE_RATES: 'lastSelectedDistanceRates', + NVP_LAST_SELECTED_DISTANCE_RATES: 'nvp_expensify_lastSelectedDistanceRates', /** The NVP with the last action taken (for the Quick Action Button) */ NVP_QUICK_ACTION_GLOBAL_CREATE: 'nvp_quickActionGlobalCreate', @@ -290,6 +290,9 @@ const ONYXKEYS = { /** Indicates whether an forced upgrade is required */ UPDATE_REQUIRED: 'updateRequired', + /** Indicates whether an forced reset is required. Used in emergency situations where we must completely erase the Onyx data in the client because it is in a bad state. This will clear Oynx data without signing the user out. */ + RESET_REQUIRED: 'resetRequired', + /** Stores the logs of the app for debugging purposes */ LOGS: 'logs', @@ -336,9 +339,10 @@ const ONYXKEYS = { SECURITY_GROUP: 'securityGroup_', TRANSACTION: 'transactions_', TRANSACTION_VIOLATIONS: 'transactionViolations_', + TRANSACTION_DRAFT: 'transactionsDraft_', // Holds temporary transactions used during the creation and edit flow - TRANSACTION_DRAFT: 'transactionsDraft_', + TRANSACTION_BACKUP: 'transactionsBackup_', SPLIT_TRANSACTION_DRAFT: 'splitTransactionDraft_', PRIVATE_NOTES_DRAFT: 'privateNotesDraft_', NEXT_STEP: 'reportNextStep_', @@ -436,8 +440,8 @@ const ONYXKEYS = { REPORT_FIELD_EDIT_FORM_DRAFT: 'reportFieldEditFormDraft', REIMBURSEMENT_ACCOUNT_FORM: 'reimbursementAccount', REIMBURSEMENT_ACCOUNT_FORM_DRAFT: 'reimbursementAccountDraft', - PERSONAL_BANK_ACCOUNT_FORM: 'personalBankAccountForm', - PERSONAL_BANK_ACCOUNT_FORM_DRAFT: 'personalBankAccountFormDraft', + PERSONAL_BANK_ACCOUNT_FORM: 'personalBankAccount', + PERSONAL_BANK_ACCOUNT_FORM_DRAFT: 'personalBankAccountDraft', EXIT_SURVEY_REASON_FORM: 'exitSurveyReasonForm', EXIT_SURVEY_REASON_FORM_DRAFT: 'exitSurveyReasonFormDraft', EXIT_SURVEY_RESPONSE_FORM: 'exitSurveyResponseForm', @@ -452,6 +456,8 @@ const ONYXKEYS = { WORKSPACE_TAX_NAME_FORM_DRAFT: 'workspaceTaxNameFormDraft', WORKSPACE_TAX_VALUE_FORM: 'workspaceTaxValueForm', WORKSPACE_TAX_VALUE_FORM_DRAFT: 'workspaceTaxValueFormDraft', + NEW_CHAT_NAME_FORM: 'newChatNameForm', + NEW_CHAT_NAME_FORM_DRAFT: 'newChatNameFormDraft', }, } as const; @@ -507,6 +513,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.POLICY_DISTANCE_RATE_EDIT_FORM]: FormTypes.PolicyDistanceRateEditForm; [ONYXKEYS.FORMS.WORKSPACE_TAX_NAME_FORM]: FormTypes.WorkspaceTaxNameForm; [ONYXKEYS.FORMS.WORKSPACE_TAX_VALUE_FORM]: FormTypes.WorkspaceTaxValueForm; + [ONYXKEYS.FORMS.NEW_CHAT_NAME_FORM]: FormTypes.NewChatNameForm; }; type OnyxFormDraftValuesMapping = { @@ -538,6 +545,7 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.SECURITY_GROUP]: OnyxTypes.SecurityGroup; [ONYXKEYS.COLLECTION.TRANSACTION]: OnyxTypes.Transaction; [ONYXKEYS.COLLECTION.TRANSACTION_DRAFT]: OnyxTypes.Transaction; + [ONYXKEYS.COLLECTION.TRANSACTION_BACKUP]: OnyxTypes.Transaction; [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: OnyxTypes.TransactionViolations; [ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT]: OnyxTypes.Transaction; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS]: OnyxTypes.RecentlyUsedTags; @@ -640,6 +648,7 @@ type OnyxValuesMapping = { [ONYXKEYS.LAST_VISITED_PATH]: string | undefined; [ONYXKEYS.RECENTLY_USED_REPORT_FIELDS]: OnyxTypes.RecentlyUsedReportFields; [ONYXKEYS.UPDATE_REQUIRED]: boolean; + [ONYXKEYS.RESET_REQUIRED]: boolean; [ONYXKEYS.PLAID_CURRENT_EVENT]: string; [ONYXKEYS.LOGS]: OnyxTypes.CapturedLogs; [ONYXKEYS.SHOULD_STORE_LOGS]: boolean; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 31c2af8f4e58..df5c510ca954 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1,6 +1,7 @@ -import type {IsEqual, ValueOf} from 'type-fest'; +import type {ValueOf} from 'type-fest'; import type CONST from './CONST'; import type {IOURequestType} from './libs/actions/IOU'; +import type AssertTypesNotEqual from './types/utils/AssertTypesNotEqual'; // This is a file containing constants for all the routes we want to be able to go to @@ -189,6 +190,7 @@ const ROUTES = { NEW: 'new', NEW_CHAT: 'new/chat', NEW_CHAT_CONFIRM: 'new/chat/confirm', + NEW_CHAT_EDIT_NAME: 'new/chat/confirm/name/edit', NEW_ROOM: 'new/room', REPORT: 'r', @@ -225,6 +227,18 @@ const ROUTES = { route: 'r/:reportID/participants', getRoute: (reportID: string) => `r/${reportID}/participants` as const, }, + REPORT_PARTICIPANTS_INVITE: { + route: 'r/:reportID/participants/invite', + getRoute: (reportID: string) => `r/${reportID}/participants/invite` as const, + }, + REPORT_PARTICIPANTS_DETAILS: { + route: 'r/:reportID/participants/:accountID', + getRoute: (reportID: string, accountID: number) => `r/${reportID}/participants/${accountID}` as const, + }, + REPORT_PARTICIPANTS_ROLE_SELECTION: { + route: 'r/:reportID/participants/:accountID/role', + getRoute: (reportID: string, accountID: number) => `r/${reportID}/participants/${accountID}/role` as const, + }, REPORT_WITH_ID_DETAILS: { route: 'r/:reportID/details', getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/details`, backTo), @@ -237,6 +251,10 @@ const ROUTES = { route: 'r/:reportID/settings/room-name', getRoute: (reportID: string) => `r/${reportID}/settings/room-name` as const, }, + REPORT_SETTINGS_GROUP_NAME: { + route: 'r/:reportID/settings/group-name', + getRoute: (reportID: string) => `r/${reportID}/settings/group-name` as const, + }, REPORT_SETTINGS_NOTIFICATION_PREFERENCES: { route: 'r/:reportID/settings/notification-preferences', getRoute: (reportID: string) => `r/${reportID}/settings/notification-preferences` as const, @@ -334,8 +352,8 @@ const ROUTES = { }, MONEY_REQUEST_STEP_CURRENCY: { route: ':action/:iouType/currency/:transactionID/:reportID/:pageIndex?', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, pageIndex = '', backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/currency/${transactionID}/${reportID}/${pageIndex}`, backTo), + getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, pageIndex = '', currency = '', backTo = '') => + getUrlWithBackToParam(`${action}/${iouType}/currency/${transactionID}/${reportID}/${pageIndex}?currency=${currency}`, backTo), }, MONEY_REQUEST_STEP_DATE: { route: ':action/:iouType/date/:transactionID/:reportID', @@ -368,20 +386,20 @@ const ROUTES = { getUrlWithBackToParam(`${action}/${iouType}/scan/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_TAG: { - route: ':action/:iouType/tag/:tagIndex/:transactionID/:reportID/:reportActionID?', + route: ':action/:iouType/tag/:orderWeight/:transactionID/:reportID/:reportActionID?', getRoute: ( action: ValueOf, iouType: ValueOf, - tagIndex: number, + orderWeight: number, transactionID: string, reportID: string, backTo = '', reportActionID?: string, - ) => getUrlWithBackToParam(`${action}/${iouType}/tag/${tagIndex}/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo), + ) => getUrlWithBackToParam(`${action}/${iouType}/tag/${orderWeight}/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo), }, MONEY_REQUEST_STEP_WAYPOINT: { route: ':action/:iouType/waypoint/:transactionID/:reportID/:pageIndex', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, pageIndex = '', backTo = '') => + getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID?: string, pageIndex = '', backTo = '') => getUrlWithBackToParam(`${action}/${iouType}/waypoint/${transactionID}/${reportID}/${pageIndex}`, backTo), }, // This URL is used as a redirect to one of the create tabs below. This is so that we can message users with a link @@ -730,20 +748,18 @@ export default ROUTES; // eslint-disable-next-line @typescript-eslint/no-explicit-any type ExtractRouteName = TRoute extends {getRoute: (...args: any[]) => infer TRouteName} ? TRouteName : TRoute; -type AllRoutes = { +/** + * Represents all routes in the app as a union of literal strings. + */ +type Route = { [K in keyof typeof ROUTES]: ExtractRouteName<(typeof ROUTES)[K]>; }[keyof typeof ROUTES]; -type RouteIsPlainString = IsEqual; +type RoutesValidationError = 'Error: One or more routes defined within `ROUTES` have not correctly used `as const` in their `getRoute` function return value.'; -/** - * Represents all routes in the app as a union of literal strings. - * - * If this type resolves to `never`, it implies that one or more routes defined within `ROUTES` have not correctly used - * `as const` in their `getRoute` function return value. - */ -type Route = RouteIsPlainString extends true ? never : AllRoutes; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +type RouteIsPlainString = AssertTypesNotEqual; type HybridAppRoute = (typeof HYBRID_APP_ROUTES)[keyof typeof HYBRID_APP_ROUTES]; -export type {Route, HybridAppRoute, AllRoutes}; +export type {Route, HybridAppRoute}; diff --git a/src/SCREENS.ts b/src/SCREENS.ts index b3c2012e90d2..96372d5bbabb 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -172,6 +172,7 @@ const SCREENS = { REPORT_SETTINGS: { ROOT: 'Report_Settings_Root', ROOM_NAME: 'Report_Settings_Room_Name', + GROUP_NAME: 'Report_Settings_Group_Name', NOTIFICATION_PREFERENCES: 'Report_Settings_Notification_Preferences', WRITE_CAPABILITY: 'Report_Settings_Write_Capability', VISIBILITY: 'Report_Settings_Visibility', @@ -273,6 +274,7 @@ const SCREENS = { ROOT: 'NewChat_Root', NEW_CHAT: 'chat', NEW_CHAT_CONFIRM: 'NewChat_Confirm', + NEW_CHAT_EDIT_NAME: 'NewChat_Edit_Name', NEW_ROOM: 'room', }, @@ -309,7 +311,12 @@ const SCREENS = { PROFILE_ROOT: 'Profile_Root', PROCESS_MONEY_REQUEST_HOLD_ROOT: 'ProcessMoneyRequestHold_Root', REPORT_DESCRIPTION_ROOT: 'Report_Description_Root', - REPORT_PARTICIPANTS_ROOT: 'ReportParticipants_Root', + REPORT_PARTICIPANTS: { + ROOT: 'ReportParticipants_Root', + INVITE: 'ReportParticipants_Invite', + DETAILS: 'ReportParticipants_Details', + ROLE: 'ReportParticipants_Role', + }, ROOM_MEMBERS_ROOT: 'RoomMembers_Root', ROOM_INVITE_ROOT: 'RoomInvite_Root', SEARCH_ROOT: 'Search_Root', diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index 35638a0b604e..ef4d7e3e4064 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -237,7 +237,9 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s const validateAndCompleteAttachmentSelection = useCallback( (fileData: FileResponse) => { - if (fileData.width === -1 || fileData.height === -1) { + // Check if the file dimensions indicate corruption + // The width/height for corrupt file is -1 on android native and 0 on ios native + if (!fileData.width || !fileData.height || (fileData.width <= 0 && fileData.height <= 0)) { showImageCorruptionAlert(); return Promise.resolve(); } @@ -283,16 +285,18 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s }; /* eslint-enable @typescript-eslint/prefer-nullish-coalescing */ if (fileDataName && Str.isImage(fileDataName)) { - ImageSize.getSize(fileDataUri).then(({width, height}) => { - fileDataObject.width = width; - fileDataObject.height = height; - validateAndCompleteAttachmentSelection(fileDataObject); - }); + ImageSize.getSize(fileDataUri) + .then(({width, height}) => { + fileDataObject.width = width; + fileDataObject.height = height; + validateAndCompleteAttachmentSelection(fileDataObject); + }) + .catch(() => showImageCorruptionAlert()); } else { return validateAndCompleteAttachmentSelection(fileDataObject); } }, - [validateAndCompleteAttachmentSelection], + [validateAndCompleteAttachmentSelection, showImageCorruptionAlert], ); /** diff --git a/src/components/AvatarWithImagePicker.tsx b/src/components/AvatarWithImagePicker.tsx index d803aa97fd0f..5fa17e726c6f 100644 --- a/src/components/AvatarWithImagePicker.tsx +++ b/src/components/AvatarWithImagePicker.tsx @@ -117,6 +117,12 @@ type AvatarWithImagePickerProps = { /** Allows to open an image without Attachment Picker. */ enablePreview?: boolean; + + /** Hard disables the "View photo" option */ + shouldDisableViewPhoto?: boolean; + + /** Optionally override the default "Edit" icon */ + editIcon?: IconAsset; }; function AvatarWithImagePicker({ @@ -144,6 +150,8 @@ function AvatarWithImagePicker({ disabled = false, onViewPhotoPress, enablePreview = false, + shouldDisableViewPhoto = false, + editIcon = Expensicons.Pencil, }: AvatarWithImagePickerProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -332,7 +340,7 @@ function AvatarWithImagePicker({ {!disabled && ( { const menuItems = createMenuItems(openPicker); - // If the current avatar isn't a default avatar, allow the "View Photo" option - if (!isUsingDefaultAvatar) { + // If the current avatar isn't a default avatar and we are not overriding this behavior allow the "View Photo" option + if (!shouldDisableViewPhoto && !isUsingDefaultAvatar) { menuItems.push({ icon: Expensicons.Eye, text: translate('avatarWithImagePicker.viewPhoto'), diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 3b05d22fe6c4..68f1aac41a5b 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -289,6 +289,10 @@ function Button( currentTarget?.blur(); } + if (event?.type === 'keyup') { + return; + } + if (shouldEnableHapticFeedback) { HapticFeedback.press(); } diff --git a/src/components/ChatDetailsQuickActionsBar.tsx b/src/components/ChatDetailsQuickActionsBar.tsx new file mode 100644 index 000000000000..f15fc31aec45 --- /dev/null +++ b/src/components/ChatDetailsQuickActionsBar.tsx @@ -0,0 +1,64 @@ +import React, {useState} from 'react'; +import {View} from 'react-native'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as Report from '@userActions/Report'; +import type {Report as OnyxReportType} from '@src/types/onyx'; +import Button from './Button'; +import ConfirmModal from './ConfirmModal'; +import * as Expensicons from './Icon/Expensicons'; + +type ChatDetailsQuickActionsBarProps = { + report: OnyxReportType; +}; + +function ChatDetailsQuickActionsBar({report}: ChatDetailsQuickActionsBarProps) { + const styles = useThemeStyles(); + const [isLastMemberLeavingGroupModalVisible, setIsLastMemberLeavingGroupModalVisible] = useState(false); + const {translate} = useLocalize(); + const isPinned = !!report.isPinned; + return ( + + + { + setIsLastMemberLeavingGroupModalVisible(false); + Report.leaveGroupChat(report.reportID); + }} + onCancel={() => setIsLastMemberLeavingGroupModalVisible(false)} + prompt={translate('groupChat.lastMemberWarning')} + confirmText={translate('common.leave')} + cancelText={translate('common.cancel')} + /> +