diff --git a/.eslintignore b/.eslintignore index aa8b769dfede..3d966d096add 100644 --- a/.eslintignore +++ b/.eslintignore @@ -10,3 +10,4 @@ docs/vendor/** docs/assets/** web/gtm.js **/.expo/** +src/libs/SearchParser/searchParser.js diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml index ffce73644263..0d5879217ea0 100644 --- a/.github/workflows/e2ePerformanceTests.yml +++ b/.github/workflows/e2ePerformanceTests.yml @@ -257,12 +257,12 @@ jobs: - name: Check if test failed, if so post the results and add the DeployBlocker label id: checkIfRegressionDetected run: | - if grep -q '🔴' ./output.md; then + if grep -q '🔴' "./Host_Machine_Files/\$WORKING_DIRECTORY/output.md"; then # Create an output to the GH action that the test failed: echo "performanceRegressionDetected=true" >> "$GITHUB_OUTPUT" gh pr edit ${{ inputs.PR_NUMBER }} --add-label DeployBlockerCash - gh pr comment ${{ inputs.PR_NUMBER }} -F ./output.md + gh pr comment ${{ inputs.PR_NUMBER }} -F "./Host_Machine_Files/\$WORKING_DIRECTORY/output.md" gh pr comment ${{ inputs.PR_NUMBER }} -b "@Expensify/mobile-deployers 📣 Please look into this performance regression as it's a deploy blocker." else echo "performanceRegressionDetected=false" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/sendReassurePerfData.yml b/.github/workflows/sendReassurePerfData.yml index 53b3d3374a9e..30a30918f4f6 100644 --- a/.github/workflows/sendReassurePerfData.yml +++ b/.github/workflows/sendReassurePerfData.yml @@ -25,7 +25,7 @@ jobs: shell: bash run: | set -e - npx reassure --baseline + NODE_OPTIONS=--experimental-vm-modules npx reassure --baseline - name: Get merged pull request id: getMergedPullRequest diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 476b01f87b07..3bfc0ed28d1a 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -34,7 +34,7 @@ jobs: # - git diff is used to see the files that were added on this branch # - gh pr view is used to list files touched by this PR. Git diff may give false positives if the branch isn't up-to-date with main # - wc counts the words in the result of the intersection - count_new_js=$(comm -1 -2 <(git diff --name-only --diff-filter=A origin/main HEAD -- 'src/*.js' '__mocks__/*.js' '.storybook/*.js' 'assets/*.js' 'config/*.js' 'desktop/*.js' 'jest/*.js' 'scripts/*.js' 'tests/*.js' 'workflow_tests/*.js' '.github/libs/*.js' '.github/scripts/*.js') <(gh pr view ${{ github.event.pull_request.number }} --json files | jq -r '.files | map(.path) | .[]') | wc -l) + count_new_js=$(comm -1 -2 <(git diff --name-only --diff-filter=A origin/main HEAD -- 'src/*.js' '__mocks__/*.js' '.storybook/*.js' 'assets/*.js' 'config/*.js' 'desktop/*.js' 'jest/*.js' 'scripts/*.js' 'tests/*.js' 'workflow_tests/*.js' '.github/libs/*.js' '.github/scripts/*.js' ':!src/libs/SearchParser/*.js') <(gh pr view ${{ github.event.pull_request.number }} --json files | jq -r '.files | map(.path) | .[]') | wc -l) if [ "$count_new_js" -gt "0" ]; then echo "ERROR: Found new JavaScript files in the project; use TypeScript instead." exit 1 diff --git a/.prettierignore b/.prettierignore index 09de20ba30b0..a9f7e1464529 100644 --- a/.prettierignore +++ b/.prettierignore @@ -19,3 +19,6 @@ package-lock.json src/libs/E2E/reactNativeLaunchingTest.ts # Temporary while we keep react-compiler in our repo lib/** + +# Automatically generated files +src/libs/SearchParser/searchParser.js diff --git a/android/app/build.gradle b/android/app/build.gradle index d4cc7471a72b..f722d8426b7e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009000600 - versionName "9.0.6-0" + versionCode 1009000602 + versionName "9.0.6-2" // 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/simple-illustrations/simple-illustration__receipt-location-marker.svg b/assets/images/simple-illustrations/simple-illustration__receipt-location-marker.svg new file mode 100644 index 000000000000..01669d07c0f0 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__receipt-location-marker.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__tire.svg b/assets/images/simple-illustrations/simple-illustration__tire.svg new file mode 100644 index 000000000000..9107c88eb3e2 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__tire.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/Pay-Bills.md b/docs/articles/expensify-classic/bank-accounts-and-payments/Pay-Bills.md deleted file mode 100644 index 81dcf3488462..000000000000 --- a/docs/articles/expensify-classic/bank-accounts-and-payments/Pay-Bills.md +++ /dev/null @@ -1,113 +0,0 @@ ---- -title: Pay Bills -description: How to receive and pay company bills in Expensify ---- - - -# Overview -Simplify your back office by receiving bills from vendors and suppliers in Expensify. Anyone with or without an Expensify account can send you a bill, and Expensify will file it as a Bill and help you issue the payment. - -# How to Receive Vendor or Supplier Bills in Expensify - -There are three ways to get a vendor or supplier bill into Expensify: - -**Option 1:** Have vendors send bills to Expensify directly: Ask your vendors to email all bills to your Expensify billing intake email. - -**Option 2:** Forward bills to Expensify: If your bills are emailed to you, you can forward those bills to your Expensify billing intake email yourself. - -**Option 3:** Manually upload bills to Expensify: If you receive physical bills, you can manually create a Bill in Expensify on the web from the Reports page: -1. Click **New Report** and choose **Bill** -2. Add the expense details and vendor's email address to the pop-up window -3. Upload a pdf/image of the bill -4. Click **Submit** - -# How to Pay Bills - -There are multiple ways to pay Bills in Expensify. Let’s go over each method below: - -## ACH bank-to-bank transfer - -To use this payment method, you must have a business bank account connected to your Expensify account. - -To pay with an ACH bank-to-bank transfer: - -1. Sign in to your Expensify account on the web at www.expensify.com. -2. Go to the Inbox or Reports page and locate the Bill that needs to be paid. -3. Click the **Pay** button to be redirected to the Bill. -4. Choose the ACH option from the drop-down list. -5. Follow the prompts to connect your business bank account to Expensify. - -**Fees:** None - -## Pay using a credit or debit card - -This option is available to all US and International customers receiving an bill from a US vendor with a US business bank account. - -To pay with a credit or debit card: -1. Sign-in to your Expensify account on the web app at www.expensify.com. -2, Click on the Bill you’d like to pay to see the details. -3, Click the **Pay** button. -4. You’ll be prompted to enter your credit card or debit card details. - -**Fees:** Includes 2.9% credit card payment fee - -## Venmo - -If both you and the vendor have Venmo setup in their Expensify account, you can opt to pay the bill through Venmo. - -**Fees:** Venmo charges a 3% sender’s fee - -## Pay Outside of Expensify - -If you are not able to pay using one of the above methods, then you can mark the Bill as paid manually in Expensify to update its status and indicate that you have made payment outside Expensify. - -To mark a Bill as paid outside of Expensify: - -1. Sign-in to your Expensify account on the web app at www.expensify.com. -2. Click on the Bill you’d like to pay to see the details. -3. Click on the **Reimburse** button. -4. Choose **I’ll do it manually** - -**Fees:** None - -# Deep Dive: How company bills and vendor invoices are processed in Expensify - -Here is how a vendor or supplier bill goes from received to paid in Expensify: - -1. When a vendor or supplier bill is received in Expensify via, the document is SmartScanned automatically and a Bill is created. The Bill is owned by the primary domain contact, who will see the Bill on the Reports page on their default group policy. -2. When the Bill is ready for processing, it is submitted and follows the primary domain contact’s approval workflow. Each time the Bill is approved, it is visible in the next approver's Inbox. -3. The final approver pays the Bill from their Expensify account on the web via one of the methods. -4. The Bill is coded with the relevant imported GL codes from a connected accounting software. After it has finished going through the approval workflow the Bill can be exported back to the accounting package connected to the policy. - - -{% include faq-begin.md %} - -## What is my company's billing intake email? -Your billing intake email is [yourdomain.com]@expensify.cash. Example, if your domain is `company.io` your billing email is `company.io@expensify.cash`. - -## When a vendor or supplier bill is sent to Expensify, who receives it? - -Bills are received by the Primary Contact for the domain. This is the email address listed at **Settings > Domains > Domain Admins**. - -## Who can view a Bill in Expensify? - -Only the primary contact of the domain can view a Bill. - -## Who can pay a Bill? - -Only the primary domain contact (owner of the bill) will be able to pay the Mill. - -## How can you share access to Bills? - -To give others the ability to view a Bill, the primary contact can manually “share” the Bill under the Details section of the report via the Sharing Options button. -To give someone else the ability to pay Bills, the primary domain contact will need to grant those individuals Copilot access to the primary domain contact's account. - -## Is Bill Pay supported internationally? - -Payments are currently only supported for users paying in United States Dollars (USD). - -## What’s the difference between a Bill and an Invoice in Expensify? - -A Bill is a payable which represents an amount owed to a payee (usually a vendor or supplier), and is usually created from a vendor invoice. An Invoice is a receivable, and indicates an amount owed to you by someone else. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/Reimbursements.md b/docs/articles/expensify-classic/bank-accounts-and-payments/Reimbursements.md deleted file mode 100644 index a31c0a582fd7..000000000000 --- a/docs/articles/expensify-classic/bank-accounts-and-payments/Reimbursements.md +++ /dev/null @@ -1,42 +0,0 @@ -# Overview - -If you want to know more about how and when you’ll be reimbursed through Expensify, we’ve answered your questions below. - -# How to Get Reimbursed - -To get paid back after submitting a report for reimbursement, you’ll want to be sure to connect your bank account. You can do that under **Settings** > **Account** > **Payments** > **Add a Deposit Account**. Once your employer has approved your report, the reimbursement will be paid into the account you added. - -# Deep Dive - -## Reimbursement Timing - -### US Bank Accounts - -If your company uses Expensify's ACH reimbursement we'll first check to see if the report is eligible for Rapid Reimbursement (next business day). For a report to be eligible for Rapid Reimbursement it must fall under two limits: - - - $100 per deposit bank account per day or less for the individuals being reimbursed or businesses receiving payments for bills. - - Less than $10,000 being disbursed in a 24-hour time period from the verified bank account being used to pay the reimbursement. - -If the request passes both checks, then you can expect to see funds deposited into your bank account on the next business day. - -If either limit has been reached, then you can expect to see funds deposited within your bank account within the typical ACH timeframe of 3-5 business days. - -### International Bank Accounts - -If receiving reimbursement to an international deposit account via Global Reimbursement, you should expect to see funds deposited in your bank account within 4 business days. - -## Bank Processing Timeframes - -Banks only process transactions and ACH activity on weekdays that are not bank holidays. These are considered business days. Additionally, the business day on which a transaction will be processed depends upon whether or not a request is created before or after the cutoff time, which is typically 3 pm PST. -For example, if your reimbursement is initiated at 4 pm on Wednesday, this is past the bank's cutoff time, and it will not begin processing until the next business day. -If that same reimbursement starts processing on Thursday, and it's estimated to take 3-5 business days, this will cover a weekend, and both days are not considered business days. So, assuming there are no bank holidays added into this mix, here is how that reimbursement timeline would play out: - -**Wednesday**: Reimbursement initiated after 3 pm PST; will be processed the next business day by your company’s bank. -**Thursday**: Your company's bank will begin processing the withdrawal request -**Friday**: Business day 1 -**Saturday**: Weekend -**Sunday**: Weekend -**Monday**: Business day 2 -**Tuesday**: Business day 3 -**Wednesday**: Business day 4 -**Thursday**: Business day 5 diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/Reimbursing-Reports.md b/docs/articles/expensify-classic/bank-accounts-and-payments/Reimbursing-Reports.md deleted file mode 100644 index 69b39bae2874..000000000000 --- a/docs/articles/expensify-classic/bank-accounts-and-payments/Reimbursing-Reports.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: Reimbursing Reports -description: How to reimburse employee expense reports ---- -# Overview - -One essential aspect of the Expensify workflow is the ability to reimburse reports. This process allows for the reimbursement of expenses that have been submitted for review to the person who made the request. Detailed explanations of the various methods for reimbursing reports within Expensify are provided below. - -# How to reimburse reports - -Reports can be reimbursed directly within Expensify by clicking the **Reimburse** button at the top of the report to reveal the available reimbursement options. - -## Direct Deposit - -To reimburse directly in Expensify, the following needs to be already configured: -- The employee that's receiving reimbursement needs to add a deposit bank account to their Expensify account (under **Settings > Account > Payments > Add a Deposit-only Bank Account**) -- The reimburser needs to add a business bank account to Expensify (under **Settings > Account > Payments > Add a Verified Business Bank Account**) -- The reimburser needs to ensure Expensify is whitelisted to withdraw funds from the bank account - -If all of those settings are in place, to reimburse a report, you will click **Reimburse** on the report and then select **Via Direct Deposit (ACH)**. - -![Reimbursing Reports Dropdown]({{site.url}}/assets/images/Reimbursing Reports Dropdown.png){:width="100%"} - -## Indirect or Manual Reimbursement - -If you don't have the option to utilize direct reimbursement, you can choose to mark a report as reimbursed by clicking the **Reimburse** button at the top of the report and then selecting **I’ll do it manually – just mark as reimbursed**. - -This will effectively mark the report as reimbursed within Expensify, but you'll handle the payment elsewhere, outside of the platform. - -# Best Practices -- Plan ahead! Consider sharing a business bank account with multiple workspace admins so they can reimburse employee reports if you're unavailable. We recommend having at least two workspace admins with reimbursement permissions. - -- Understand there is a verification process when sharing a business bank account. The new reimburser will need access to the business bank account’s transaction history (or access to someone who has access to it) to verify the set of test transactions sent from Expensify. - -- Get into the routine of having every new employee connect a deposit-only bank account to their Expensify account. This will ensure reimbursements happen in a timely manner. - -- Employees can see the expected date of their reimbursement at the top of and in the comments section of their report. - -# How to cancel a reimbursement - -Reimbursed a report by mistake? No worries! Any workspace admin with access to the same Verified Bank Account can cancel the reimbursement from within the report until it is withdrawn from the payment account. - -**Steps to Cancel an ACH Reimbursement:** -1. On your web account, navigate to the Reports page -2. Open the report -3. Click **Cancel Reimbursement** -4. After the prompt, "Are you sure you want to cancel the reimbursement?" click **Cancel Reimbursement**. - -It's important to note that there is a small window of time (roughly less than 24 hours) when a reimbursement can be canceled. If you don't see the **Cancel Reimbursement** button on a report, this means your bank has already begun withdrawing the funds from the reimbursement account and the withdrawal cannot be canceled. - -In that case, you’ll want to contact your bank directly to see if they can cancel the reimbursement on their end - or manage the return of funds directly with your employee, outside of Expensify. - -If you cancel a reimbursement after the withdrawal has started, it will be automatically returned to your Verified Bank Account within 3-5 business days. - -# Deep Dive - -## Rapid Reimbursement -If your company uses Expensify's ACH reimbursement, we'll first check to see if the report is eligible for Rapid Reimbursement (next business day). For a report to be eligible for Rapid Reimbursement, it must fall under two limits: -- $100 per deposit only bank account per day for the individuals being reimbursed or businesses receiving payments for bills -- $10,000 per verified bank account for the company paying bills and reimbursing - -If neither limit is met, you can expect to see funds deposited into your bank account on the next business day. - -If either limit has been reached, you can expect funds deposited within your bank account within the typical ACH time frame of four to five business days. - -Rapid Reimbursement is not available for non-US-based reimbursement. If you are receiving a reimbursement to a non-US-based deposit account, you should expect to see the funds deposited in your bank account within four business days. - -{% include faq-begin.md %} - -## Who can reimburse reports? -Only a workspace admin who has added a verified business bank account to their Expensify account can reimburse employees. - -## Why can’t I trigger direct ACH reimbursements in bulk? - -Instead of a bulk reimbursement option, you can set up automatic reimbursement. With this configured, reports below a certain threshold (defined by you) will be automatically reimbursed via ACH as soon as they're "final approved." - -To set your manual reimbursement threshold, head to **Settings > Workspace > Group > _[Workspace Name]_ > Reimbursement > Manual Reimbursement**. - -![Manual Reimbursement]({{site.url}}/assets/images/Reimbursing Manual.png){:width="100%"} - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Cancel-an-ACH-Reimbursement.md b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Cancel-an-ACH-Reimbursement.md new file mode 100644 index 000000000000..ab75067b1a7f --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Cancel-an-ACH-Reimbursement.md @@ -0,0 +1,17 @@ +--- +title: Cancel an ACH reimbursement +description: Cancel an ACH payment after it has been sent +--- +
+ +If a report was reimbursed with an ACH payment by mistake or otherwise needs to be canceled, a Workspace Admin with access to the verified bank account can cancel the reimbursement up until it is withdrawn from the payment account. + +To cancel an ACH reimbursement, + +1. Click the **Reports** tab. +2. Open the report. +3. Click **Cancel Reimbursement**. + - If you don’t see the Cancel Reimbursement button, this means your bank has already begun transferring the funds and it cannot be canceled. In this case, you’ll need to contact your bank for cancellation. +4. Click **Cancel Reimbursement** to confirm cancellation. + +
diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Receive-Payments.md b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Receive-Payments.md new file mode 100644 index 000000000000..00fb236e1763 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Receive-Payments.md @@ -0,0 +1,36 @@ +--- +title: Receive payments +description: Receive reimbursements from an employer +--- +
+ +To get paid after submitting a report for reimbursement, you must first connect a personal U.S. bank account or a personal Australian bank account. Then once your employer approves your report or invoice, the reimbursement will be paid directly to your bank account. +Funds for U.S. and global payments are generally deposited within a maximum of four to five business days: 2 days for the funds to be debited from the business bank account, and 2-3 business days for the ACH or wire to deposit into the employee account. + +However, banks only process ACH transactions before the daily cutoff (generally 3 p.m. PST) and only on business weekdays that are not bank holidays. This may affect when the payment is disbursed. If the payment qualifies for Rapid Reimbursement, you may receive the payment sooner. + +{% include info.html %} +Companies also have the option to submit payments outside of Expensify via check, cash, or a third-party payment processor. Check with your Workspace Admin to know how you will be reimbursed. +{% include end-info.html %} + +# Rapid Reimbursement (U.S. only) + +With Expensify’s ACH reimbursement, payments may be eligible for reimbursement by the next business day with Rapid Reimbursement if they meet the following qualifications: +- **Deposit-only accounts**: Payment must not exceed $100 +- **Verified business bank accounts**: The account does not disburse more than $10,000 within a 24-hour time period. + +If the payment amount exceeds the limit, funds will be deposited within the typical ACH time frame of four to five business days. + +{% include faq-begin.md %} + +**Is there a way I can track my payment?** + +For U.S. ACH payments and global reimbursements, the expected date of reimbursement is provided at the top of the report and in the comments section of the report. Funds will be deposited within the typical ACH time frame of four to five business days unless the payment is eligible for Rapid Reimbursement. + +**For global payments, what currency is the payment provided in?** + +Global payments are reimbursed in the recipient's currency. + +{% include faq-end.md %} + +
diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Australian-Reports.md b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Australian-Reports.md new file mode 100644 index 000000000000..90a89ff3c75e --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Australian-Reports.md @@ -0,0 +1,63 @@ +--- +title: Reimburse Australian reports +description: Send payment for Australian expense reports +--- +
+ +Workspace Admins can reimburse AUD expense reports by downloading an .aba file containing the accounts needing payment and uploading the file to the bank. This can be done for a single report or for a batch of payments at once. + +{% include info.html %} +Your financial institution may require .aba files to include a self-balancing transaction. If you are unsure, check with your bank. Otherwise, the .aba file may not work with your bank’s internet banking platform. +{% include end-info.html %} + +# Reimburse a single report + +1. Open the report, invoice, or bill from the email or Concierge notification, or from the **Reports** tab. +2. Click the **Reimburse** dropdown and select **Via ABA File**. +3. Click **Generate ABA and Mark as Reimbursed**. +4. Click **Download**. +5. Upload the .aba file to your bank. For additional guidance, use any of the following bank guides: + - [ANZ Bank](https://www.anz.com.au/support/internet-banking/pay-transfer-business/payroll/import-file/) + - [CommBank](https://www.commbank.com.au/business/pds/003-279-importing-a-de-file.pdf) + - [Westpac](https://www.westpac.com.au/business-banking/online-banking/support-faqs/import-files/) + - [NAB](https://www.nab.com.au/business/online-banking/nab-connect/help) + - [Bendigo Bank](https://www.bendigobank.com.au/globalassets/documents/business/bulk-payments-user-guide.pdf) + - [Bank of Queensland](https://www.boq.com.au/help-and-support/online-banking/ob-faqs-and-support/faq-pfuf) + +# Send batch payments + +Once employees submit their expense reports, a Workspace Admin exports the reports (which contains the employees’ bank account information) and uploads the .aba file to the bank. + +## Step 1: Verify currency & reimbursement settings + +1. Hover over **Settings**, then click **Workspaces**. +2. Select the desired workspace. +3. Click the **Reports** tab on the left. +4. Click the Report Currency dropdown and select **AUD A$**. +5. Click the **Reimbursement** tab on the left. +6. Verify that **Indirect** is selected as the Reimbursement type or select it if not. + +## Step 2: Download and upload the ABA file + +1. Click the **Reports** tab. +2. Use the checkbox on the left to select all the reports needing payment. +3. Click **Bulk Actions** and select **Reimburse via ABA**. +5. Click **Generate ABA and Mark as Reimbursed**. +6. Click **Download Report**. +7. Upload the .aba file to your bank. For additional guidance, use any of the following bank guides: + - [ANZ Bank](https://www.anz.com.au/support/internet-banking/pay-transfer-business/payroll/import-file/) + - [CommBank](https://www.commbank.com.au/business/pds/003-279-importing-a-de-file.pdf) + - [Westpac](https://www.westpac.com.au/business-banking/online-banking/support-faqs/import-files/) + - [NAB](https://www.nab.com.au/business/online-banking/nab-connect/help) + - [Bendigo Bank](https://www.bendigobank.com.au/globalassets/documents/business/bulk-payments-user-guide.pdf) + - [Bank of Queensland](https://www.boq.com.au/help-and-support/online-banking/ob-faqs-and-support/faq-pfuf) + +{% include faq-begin.md %} + +**Can I use direct deposit for an AUD bank account?** + +No, AUD bank accounts do not rely on direct deposit or ACH. + +{% include faq-end.md %} + +
diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Reports-Invoices-and-Bills.md b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Reports-Invoices-and-Bills.md new file mode 100644 index 000000000000..b2cfbf833e13 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Reports-Invoices-and-Bills.md @@ -0,0 +1,108 @@ +--- +title: Reimburse reports, invoices, and bills +description: Use direct deposit or indirect reimbursement to pay reports, invoices, and bills +--- +
+ +Once a report, invoice, or bill has been submitted and approved for reimbursement, you can reimburse the expenses using direct deposit or an indirect reimbursement option outside of Expensify (like cash, a check, or a third-party payment processor). + +# Pay with direct deposit + +{% include info.html %} +Before a report can be reimbursed with direct deposit, the employee or vendor receiving the reimbursement must connect their personal U.S. bank account, and the reimburser must connect a verified business bank account. + +Direct deposit is available for U.S. and global reimbursements. It is not available for Australian bank accounts. For Australian accounts, review the process for reimbursing Australian expenses. +{% include end-info.html %} + +1. Open the report, invoice, or bill from the email or Concierge notification, or from the **Reports** tab. +2. Click the **Reimburse** (for reports) or **Pay** (for bills and invoices) dropdown and select **Via Direct Deposit (ACH)**. +3. Confirm that the correct VBA is selected or use the dropdown menu to select a different one. +4. Click **Accept Terms & Pay**. + +The reimbursement is now queued in the daily batch. + +# Pay with indirect reimbursement + +When payments are submitted through Expensify, the report is automatically labeled as Reimbursed after it has been paid. However, if you are reimbursing reports via paper check, payroll, or any other method that takes place outside of Expensify, you’ll want to manually mark the bill as paid in Expensify to track the payment history. + +To label a report as Reimbursed after sending a payment outside of Expensify, + +1. Pay the report, invoice, or bill outside of Expensify. +2. Open the report, invoice, or bill from the email or Concierge notification, or from the **Reports** tab. +3. Click **Reimburse**. +4. Select **I’ll do it manually - just mark it as reimbursed**. This changes the report status to Reimbursed. + +Once the recipient has received the payment, the submitter can return to the report and click **Confirm** at the top of the report. This will change the report status to Reimbursed: CONFIRMED. + +{% include faq-begin.md %} + +**Is there a maximum total report total?** + +Expensify cannot process a reimbursement for any single report over $20,000. If you have a report with expenses exceeding $20,000 we recommend splitting the expenses into multiple reports. + +**Why is my account locked?** + +When you reimburse a report, you authorize Expensify to withdraw the funds from your account and send them to the person requesting reimbursement. If your bank rejects Expensify’s withdrawal request, your verified bank account is locked until the issue is resolved. + +Withdrawal requests can be rejected if the bank account has not been enabled for direct debit or due to insufficient funds. If you need to enable direct debits from your verified bank account, your bank will require the following details: +- The ACH CompanyIDs: 1270239450 and 4270239450 +- The ACH Originator Name: Expensify + +Once resolved, you can request to unlock the bank account by completing the following steps: + +1. Hover over **Settings**, then click **Account**. +2. Click the **Payments** tab. +3. Click **Bank Accounts**. +4. Next to the bank account, click **Fix**. + +Our support team will review and process the request within 4-5 business days. + +**How are bills and invoices processed in Expensify?** + +Here is the process a vendor or supplier bill goes through from receipt to payment: + +1. A vendor or supplier bill is received in Expensify. +2. Automatically, the document is SmartScanned and a bill is created for the primary domain contact. The bill will appear under the Reports tab on their default group policy. +3. When the bill is ready for processing, it is submitted and follows the primary domain contact’s approval workflow until the bill has been fully approved. +4. The final approver pays the bill from their Expensify account using one of the methods outlined in the article above. +5. If the workspace is connected to an accounting integration, the bill is automatically coded with the relevant imported GL codes and can be exported back to the accounting software. + +**When a vendor or supplier bill is sent to Expensify, who receives it?** + +Bills are sent to the primary contact for the domain. They’ll see a notification from Concierge on their Home page, and they’ll also receive an email. + +**How can I share access to bills?** + +By default, only the primary contact for the domain can view and pay the bill. However, you can allow someone else to view or pay bills. + +- **To allow someone to view a bill**: The primary contact can manually share the bill with others to allow them to view it. + 1. Click the **Reports** tab. + 2. Click the report. + 3. Click **Details** in the top right. + 4. Click the **Add Person** icon. + 5. Enter the email address or phone number of the person you will share the report with. + 6. Enter a message, if desired. + 7. Click **Share Report**. + +- **To allow someone to pay bills**: The primary domain contact can allow others to pay bills on their behalf by [assigning those individuals as Copilots](https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Assign-or-remove-a-Copilot). + +**Is Bill Pay supported internationally?** + +Payments are currently only supported for users paying in United States Dollars (USD). + +**What’s the difference between a bill and an invoice?** + +- A **bill** is a payable that represents an amount owed to a payee (usually a vendor or supplier), and it is usually created from a vendor invoice. +- An **invoice** is a receivable that indicates an amount owed to you by someone else. + +**Who can reimburse reports?** + +Only a Workspace Admin who has added a verified business bank account to their Expensify account can reimburse employee reports. + +**Why can’t I trigger direct ACH reimbursements in bulk?** + +Expensify does not offer bulk reimbursement, but you can set up automatic reimbursement to automatically reimburse approved reports via ACH that do not exceed the threshold that you define. + +{% include faq-end.md %} + +
diff --git a/docs/articles/expensify-classic/expensify-card/Unlimited-Virtual-Cards.md b/docs/articles/expensify-classic/expensify-card/Unlimited-Virtual-Cards.md index 239da6518be7..fdbc178737e1 100644 --- a/docs/articles/expensify-classic/expensify-card/Unlimited-Virtual-Cards.md +++ b/docs/articles/expensify-classic/expensify-card/Unlimited-Virtual-Cards.md @@ -66,6 +66,8 @@ There are two different limit types that are best suited for their intended purp - _Fixed limit_ spend cards are ideal for one-time expenses or providing employees access to a card for a designated purchase. - _Monthly_ limit spend cards are perfect for managing recurring expenses such as subscriptions and memberships. +A virtual card with either of these limit types doesn't share its limit with any other cards, including the cardholder's smart limit cards. + **Where can employees see their virtual cards?** Employees can see their assigned virtual cards by navigating to **Settings** > **Account** > [**Credit Cards Import**](https://www.expensify.com/settings?param=%7B%22section%22:%22creditcards%22%7D) in their account. diff --git a/docs/redirects.csv b/docs/redirects.csv index 67ca238c1aed..1a60d52c1749 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -203,6 +203,9 @@ https://help.expensify.com/articles/new-expensify/chat/Expensify-Chat-For-Admins https://help.expensify.com/articles/new-expensify/bank-accounts-and-payments/Connect-a-Bank-Account.html,https://help.expensify.com/articles/new-expensify/expenses/Connect-a-Business-Bank-Account https://help.expensify.com/articles/expensify-classic/travel/Coming-Soon,https://help.expensify.com/expensify-classic/hubs/travel/ https://help.expensify.com/articles/new-expensify/expenses/Manually-submit-reports-for-approval,https://help.expensify.com/new-expensify/hubs/expenses/ +https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Reimbursements.html,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Receive-Payments +https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Pay-Bills.html,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Reports-Invoices-and-Bills +https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Reimbursing-Reports.html,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Reports-Invoices-and-Bills https://help.expensify.com/articles/expensify-classic/expensify-card/Auto-Reconciliation,https://help.expensify.com/articles/expensify-classic/expensify-card/Expensify-Card-Reconciliation.md https://help.expensify.com/articles/new-expensify/expenses/Approve-and-pay-expenses,https://help.expensify.com/articles/new-expensify/expenses-&-payments/Approve-and-pay-expenses https://help.expensify.com/articles/new-expensify/expenses/Connect-a-Business-Bank-Account,https://help.expensify.com/articles/new-expensify/expenses-&-payments/Connect-a-Business-Bank-Account diff --git a/ios/NewApp_AdHoc.mobileprovision.gpg b/ios/NewApp_AdHoc.mobileprovision.gpg index 32ed6ba30059..c08a5aae1b73 100644 Binary files a/ios/NewApp_AdHoc.mobileprovision.gpg and b/ios/NewApp_AdHoc.mobileprovision.gpg differ diff --git a/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg b/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg index 5712b0d86b19..61b6eeb84537 100644 Binary files a/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg and b/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg differ diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 2826f7e51db9..3473c2bfcec8 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.6.0 + 9.0.6.2 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index cf3e420de4e7..f413f0d1ae99 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 9.0.6.0 + 9.0.6.2 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 58cdb65c40e9..212ae9c1aa43 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.0.6 CFBundleVersion - 9.0.6.0 + 9.0.6.2 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 29ab90c4b7db..50dfc65d07b2 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1871,7 +1871,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.91): + - RNLiveMarkdown (0.1.103): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1889,9 +1889,9 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/common (= 0.1.91) + - RNLiveMarkdown/common (= 0.1.103) - Yoga - - RNLiveMarkdown/common (0.1.91): + - RNLiveMarkdown/common (0.1.103): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1935,7 +1935,7 @@ PODS: - ReactCommon/turbomodule/core - Turf - Yoga - - RNPermissions (3.9.3): + - RNPermissions (3.10.1): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2614,10 +2614,10 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 74b7b3d06d667ba0bbf41da7718f2607ae0dfe8f RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 - RNLiveMarkdown: 24fbb7370eefee2f325fb64cfe904b111ffcd81b + RNLiveMarkdown: f12157fc91b72e19705c9cc8c98034c4c1669d5a RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: df8fe93dbd251f25022f4023d31bc04160d4d65c - RNPermissions: 0b61d30d21acbeafe25baaa47d9bae40a0c65216 + RNPermissions: d2392b754e67bc14491f5b12588bef2864e783f3 RNReactNativeHapticFeedback: 616c35bdec7d20d4c524a7949ca9829c09e35f37 RNReanimated: 323436b1a5364dca3b5f8b1a13458455e0de9efe RNScreens: abd354e98519ed267600b7ee64fdcb8e060b1218 diff --git a/package-lock.json b/package-lock.json index 64f5f7a6a94d..6a155219ba5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "new.expensify", - "version": "9.0.6-0", + "version": "9.0.6-2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.6-0", + "version": "9.0.6-2", "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": "0.1.91", + "@expensify/react-native-live-markdown": "0.1.103", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -55,7 +55,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "2.0.26", + "expensify-common": "2.0.35", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", @@ -106,7 +106,7 @@ "react-native-pager-view": "6.2.3", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", - "react-native-permissions": "^3.9.3", + "react-native-permissions": "^3.10.0", "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#da50d2c5c54e268499047f9cc98b8df4196c1ddf", "react-native-plaid-link-sdk": "11.5.0", "react-native-qrcode-svg": "^6.2.0", @@ -233,6 +233,7 @@ "memfs": "^4.6.0", "onchange": "^7.1.0", "patch-package": "^8.0.0", + "peggy": "^4.0.3", "portfinder": "^1.0.28", "prettier": "^2.8.8", "pusher-js-mock": "^0.3.3", @@ -3784,9 +3785,9 @@ } }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.91", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.91.tgz", - "integrity": "sha512-6uQTgwhpvLqQKdtNqSgh45sRuQRXzv/WwyhdvQNge6EYtulyGFqT82GIP+LIGW8Xnl73nzFZTuMKwWxFFR/Cow==", + "version": "0.1.103", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.103.tgz", + "integrity": "sha512-w9jQoxBE9LghfL8UdYbG+8A+CApmER/XMH8N7/bINn7w57+FnnBa5ckPWx6/UYX7OYsmYxSaHJLQkJEXYlDRZg==", "workspaces": [ "parser", "example", @@ -7879,6 +7880,33 @@ "react-native": ">=0.70.0 <1.0.x" } }, + "node_modules/@peggyjs/from-mem": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@peggyjs/from-mem/-/from-mem-1.3.0.tgz", + "integrity": "sha512-kzGoIRJjkg3KuGI4bopz9UvF3KguzfxalHRDEIdqEZUe45xezsQ6cx30e0RKuxPUexojQRBfu89Okn7f4/QXsw==", + "dev": true, + "dependencies": { + "semver": "7.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@peggyjs/from-mem/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@perf-profiler/android": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/@perf-profiler/android/-/android-0.12.1.tgz", @@ -25974,9 +26002,9 @@ } }, "node_modules/expensify-common": { - "version": "2.0.26", - "resolved": "https://registry.npmjs.org/expensify-common/-/expensify-common-2.0.26.tgz", - "integrity": "sha512-3GORs2xfx78SoKLDh4lXpk4Bx61sAVNnlo23VB803zs7qZz8/Oq3neKedtEJuRAmUps0C1Y5y9xZE8nrPO31nQ==", + "version": "2.0.35", + "resolved": "https://registry.npmjs.org/expensify-common/-/expensify-common-2.0.35.tgz", + "integrity": "sha512-5Q4DK5pJIyPd3FmHrargm9fxiOMBdDLMb8rhwa2jN35DL9NiYX9mOTWc327PnjaFO07AeR6wNj8+BVgqOxqMGg==", "dependencies": { "awesome-phonenumber": "^5.4.0", "classnames": "2.5.0", @@ -35858,6 +35886,32 @@ "through2": "^2.0.3" } }, + "node_modules/peggy": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/peggy/-/peggy-4.0.3.tgz", + "integrity": "sha512-v7/Pt6kGYsfXsCrfb52q7/yg5jaAwiVaUMAPLPvy4DJJU6Wwr72t6nDIqIDkGfzd1B4zeVuTnQT0RGeOhe/uSA==", + "dev": true, + "dependencies": { + "@peggyjs/from-mem": "1.3.0", + "commander": "^12.1.0", + "source-map-generator": "0.8.0" + }, + "bin": { + "peggy": "bin/peggy.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/peggy/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/pend": { "version": "1.2.0", "dev": true, @@ -37435,8 +37489,9 @@ } }, "node_modules/react-native-permissions": { - "version": "3.9.3", - "license": "MIT", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/react-native-permissions/-/react-native-permissions-3.10.1.tgz", + "integrity": "sha512-Gc5BxxpjZn4QNUDiVeHOO0vXh3AH7ToolmwTJozqC6DsxV7NAf3ttap+8BSmzDR8WxuAM3Cror+YNiBhHJx7/w==", "peerDependencies": { "react": ">=16.13.1", "react-native": ">=0.63.3", @@ -40226,6 +40281,15 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-generator": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/source-map-generator/-/source-map-generator-0.8.0.tgz", + "integrity": "sha512-psgxdGMwl5MZM9S3FWee4EgsEaIjahYV5AzGnwUvPhWeITz/j6rKpysQHlQ4USdxvINlb8lKfWGIXwfkrgtqkA==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/source-map-js": { "version": "1.0.2", "license": "BSD-3-Clause", diff --git a/package.json b/package.json index 9f1c3ffca7a7..cadd22fd7332 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.6-0", + "version": "9.0.6-2", "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.", @@ -61,13 +61,14 @@ "workflow-test:generate": "ts-node workflow_tests/utils/preGenerateTest.ts", "setup-https": "mkcert -install && mkcert -cert-file config/webpack/certificate.pem -key-file config/webpack/key.pem dev.new.expensify.com localhost 127.0.0.1", "e2e-test-runner-build": "ncc build tests/e2e/testRunner.ts -o tests/e2e/dist/", - "react-compiler-healthcheck": "react-compiler-healthcheck --verbose" + "react-compiler-healthcheck": "react-compiler-healthcheck --verbose", + "generate-search-parser": "peggy --format es -o src/libs/SearchParser/searchParser.js src/libs/SearchParser/searchParser.peggy " }, "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": "0.1.91", + "@expensify/react-native-live-markdown": "0.1.103", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -109,7 +110,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "2.0.26", + "expensify-common": "2.0.35", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", @@ -160,7 +161,7 @@ "react-native-pager-view": "6.2.3", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", - "react-native-permissions": "^3.9.3", + "react-native-permissions": "^3.10.0", "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#da50d2c5c54e268499047f9cc98b8df4196c1ddf", "react-native-plaid-link-sdk": "11.5.0", "react-native-qrcode-svg": "^6.2.0", @@ -287,6 +288,7 @@ "memfs": "^4.6.0", "onchange": "^7.1.0", "patch-package": "^8.0.0", + "peggy": "^4.0.3", "portfinder": "^1.0.28", "prettier": "^2.8.8", "pusher-js-mock": "^0.3.3", diff --git a/patches/@expensify+react-native-live-markdown+0.1.91.patch b/patches/@expensify+react-native-live-markdown+0.1.91.patch deleted file mode 100644 index c77e46accae3..000000000000 --- a/patches/@expensify+react-native-live-markdown+0.1.91.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/@expensify/react-native-live-markdown/src/web/cursorUtils.ts b/node_modules/@expensify/react-native-live-markdown/src/web/cursorUtils.ts -index 1cda659..ba5c3c3 100644 ---- a/node_modules/@expensify/react-native-live-markdown/src/web/cursorUtils.ts -+++ b/node_modules/@expensify/react-native-live-markdown/src/web/cursorUtils.ts -@@ -66,7 +66,7 @@ function setCursorPosition(target: HTMLElement, start: number, end: number | nul - // 3. Caret at the end of whole input, when pressing enter - // 4. All other placements - if (prevChar === '\n' && prevTextLength !== undefined && prevTextLength < textCharacters.length) { -- if (nextChar !== '\n') { -+ if (nextChar && nextChar !== '\n' && i !== n - 1) { - range.setStart(textNodes[i + 1] as Node, 0); - } else if (i !== textNodes.length - 1) { - range.setStart(textNodes[i] as Node, 1); diff --git a/patches/@react-native-community+netinfo+11.2.1+002+turbomodule.patch b/patches/@react-native-community+netinfo+11.2.1+002+turbomodule.patch index f8e171008e14..bf6decac0450 100644 --- a/patches/@react-native-community+netinfo+11.2.1+002+turbomodule.patch +++ b/patches/@react-native-community+netinfo+11.2.1+002+turbomodule.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/@react-native-community/netinfo/android/build.gradle b/node_modules/@react-native-community/netinfo/android/build.gradle -index 0d617ed..e93d64a 100644 +index 0d617ed..97439e6 100644 --- a/node_modules/@react-native-community/netinfo/android/build.gradle +++ b/node_modules/@react-native-community/netinfo/android/build.gradle @@ -3,9 +3,10 @@ buildscript { @@ -105,7 +105,6 @@ index 0d617ed..e93d64a 100644 + implementation 'com.facebook.react:react-native:+' + } } -\ No newline at end of file diff --git a/node_modules/@react-native-community/netinfo/android/src/main/java/com/reactnativecommunity/netinfo/NetInfoModuleImpl.java b/node_modules/@react-native-community/netinfo/android/src/main/java/com/reactnativecommunity/netinfo/NetInfoModuleImpl.java index 2c3280b..296bbfd 100644 --- a/node_modules/@react-native-community/netinfo/android/src/main/java/com/reactnativecommunity/netinfo/NetInfoModuleImpl.java @@ -1609,10 +1608,10 @@ index 095dd3b..596ace1 100644 +{"version":3,"names":["NetInfoStateType","exports","NetInfoCellularGeneration"],"sources":["types.ts"],"sourcesContent":["/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * @format\n */\n\nexport enum NetInfoStateType {\n unknown = 'unknown',\n none = 'none',\n cellular = 'cellular',\n wifi = 'wifi',\n bluetooth = 'bluetooth',\n ethernet = 'ethernet',\n wimax = 'wimax',\n vpn = 'vpn',\n other = 'other',\n}\n\nexport type NetInfoMethodType = 'HEAD' | 'GET';\n\nexport enum NetInfoCellularGeneration {\n '2g' = '2g',\n '3g' = '3g',\n '4g' = '4g',\n '5g' = '5g',\n}\n\nexport interface NetInfoConnectedDetails {\n isConnectionExpensive: boolean;\n}\n\ninterface NetInfoConnectedState<\n T extends NetInfoStateType,\n D extends Record = Record,\n> {\n type: T;\n isConnected: true;\n isInternetReachable: boolean | null;\n details: D & NetInfoConnectedDetails;\n isWifiEnabled?: boolean;\n}\n\ninterface NetInfoDisconnectedState {\n type: T;\n isConnected: false;\n isInternetReachable: false;\n details: null;\n isWifiEnabled?: boolean;\n}\n\nexport interface NetInfoUnknownState {\n type: NetInfoStateType.unknown;\n isConnected: boolean | null;\n isInternetReachable: null;\n details: null;\n isWifiEnabled?: boolean;\n}\n\nexport type NetInfoNoConnectionState =\n NetInfoDisconnectedState;\nexport type NetInfoDisconnectedStates =\n | NetInfoUnknownState\n | NetInfoNoConnectionState;\n\nexport type NetInfoCellularState = NetInfoConnectedState<\n NetInfoStateType.cellular,\n {\n cellularGeneration: NetInfoCellularGeneration | null;\n carrier: string | null;\n }\n>;\nexport type NetInfoWifiState = NetInfoConnectedState<\n NetInfoStateType.wifi,\n {\n ssid: string | null;\n bssid: string | null;\n strength: number | null;\n ipAddress: string | null;\n subnet: string | null;\n frequency: number | null;\n linkSpeed: number | null;\n rxLinkSpeed: number | null;\n txLinkSpeed: number | null;\n }\n>;\nexport type NetInfoBluetoothState =\n NetInfoConnectedState;\nexport type NetInfoEthernetState = NetInfoConnectedState<\n NetInfoStateType.ethernet,\n {\n ipAddress: string | null;\n subnet: string | null;\n }\n>;\nexport type NetInfoWimaxState = NetInfoConnectedState;\nexport type NetInfoVpnState = NetInfoConnectedState;\nexport type NetInfoOtherState = NetInfoConnectedState;\nexport type NetInfoConnectedStates =\n | NetInfoCellularState\n | NetInfoWifiState\n | NetInfoBluetoothState\n | NetInfoEthernetState\n | NetInfoWimaxState\n | NetInfoVpnState\n | NetInfoOtherState;\n\nexport type NetInfoState = NetInfoDisconnectedStates | NetInfoConnectedStates;\n\nexport type NetInfoChangeHandler = (state: NetInfoState) => void;\nexport type NetInfoSubscription = () => void;\n\nexport interface NetInfoConfiguration {\n reachabilityUrl: string;\n reachabilityMethod?: NetInfoMethodType;\n reachabilityHeaders?: Record;\n reachabilityTest: (response: Response) => Promise;\n reachabilityLongTimeout: number;\n reachabilityShortTimeout: number;\n reachabilityRequestTimeout: number;\n reachabilityShouldRun: () => boolean;\n shouldFetchWiFiSSID: boolean;\n useNativeReachability: boolean;\n}\n"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA,IASYA,gBAAgB,GAAAC,OAAA,CAAAD,gBAAA,0BAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAA,OAAhBA,gBAAgB;AAAA;AAAA,IAchBE,yBAAyB,GAAAD,OAAA,CAAAC,yBAAA,0BAAzBA,yBAAyB;EAAzBA,yBAAyB;EAAzBA,yBAAyB;EAAzBA,yBAAyB;EAAzBA,yBAAyB;EAAA,OAAzBA,yBAAyB;AAAA"} \ No newline at end of file diff --git a/node_modules/@react-native-community/netinfo/lib/module/index.js b/node_modules/@react-native-community/netinfo/lib/module/index.js -index 147c72e..02aa0db 100644 +index 147c72e..5de4e7c 100644 --- a/node_modules/@react-native-community/netinfo/lib/module/index.js +++ b/node_modules/@react-native-community/netinfo/lib/module/index.js -@@ -6,20 +6,23 @@ +@@ -6,20 +6,26 @@ * * @format */ @@ -1635,11 +1634,14 @@ index 147c72e..02aa0db 100644 const createState = () => { return new State(_configuration); }; ++ ++// Track ongoing requests ++let isRequestInProgress = false; + /** * Configures the library with the given configuration. Note that calling this will stop all * previously added listeners from being called again. It is best to call this right when your -@@ -27,23 +30,20 @@ const createState = () => { +@@ -27,23 +33,20 @@ const createState = () => { * * @param configuration The new configuration to set. */ @@ -1666,7 +1668,7 @@ index 147c72e..02aa0db 100644 /** * Returns a `Promise` that resolves to a `NetInfoState` object. * This function operates on the global singleton instance configured using `configure()` -@@ -52,27 +52,25 @@ export function configure(configuration) { +@@ -52,27 +55,33 @@ export function configure(configuration) { * * @returns A Promise which contains the current connection state. */ @@ -1689,14 +1691,22 @@ index 147c72e..02aa0db 100644 if (!_state) { _state = createState(); } -- - return _state._fetchCurrentState(); + +- return _state._fetchCurrentState(); ++ if (isRequestInProgress) { ++ return _state.latest(); // Return the latest state if a request is already in progress ++ } ++ ++ isRequestInProgress = true; ++ return _state._fetchCurrentState().finally(() => { ++ isRequestInProgress = false; ++ }); } + /** * Subscribe to the global singleton's connection information. The callback is called with a parameter of type * [`NetInfoState`](README.md#netinfostate) whenever the connection state changes. Your listener -@@ -84,18 +82,16 @@ export function refresh() { +@@ -84,18 +93,16 @@ export function refresh() { * * @returns A function which can be called to unsubscribe. */ @@ -1716,7 +1726,7 @@ index 147c72e..02aa0db 100644 /** * A React Hook into this library's singleton which updates when the connection state changes. * -@@ -103,12 +99,10 @@ export function addEventListener(listener) { +@@ -103,12 +110,10 @@ export function addEventListener(listener) { * * @returns The connection state. */ @@ -1729,7 +1739,7 @@ index 147c72e..02aa0db 100644 const [netInfo, setNetInfo] = useState({ type: Types.NetInfoStateType.unknown, isConnected: null, -@@ -120,6 +114,7 @@ export function useNetInfo(configuration) { +@@ -120,6 +125,7 @@ export function useNetInfo(configuration) { }, []); return netInfo; } @@ -1737,7 +1747,7 @@ index 147c72e..02aa0db 100644 /** * A React Hook which manages an isolated instance of the network info manager. * This is not a hook into a singleton shared state. NetInfo.configure, NetInfo.addEventListener, -@@ -129,7 +124,6 @@ export function useNetInfo(configuration) { +@@ -129,7 +135,6 @@ export function useNetInfo(configuration) { * * @returns the netInfo state and a refresh function */ @@ -1745,7 +1755,7 @@ index 147c72e..02aa0db 100644 export function useNetInfoInstance(isPaused = false, configuration) { const [networkInfoManager, setNetworkInfoManager] = useState(); const [netInfo, setNetInfo] = useState({ -@@ -142,8 +136,8 @@ export function useNetInfoInstance(isPaused = false, configuration) { +@@ -142,8 +147,8 @@ export function useNetInfoInstance(isPaused = false, configuration) { if (isPaused) { return; } @@ -2609,32 +2619,6 @@ index 6982220..b515270 100644 + readonly eventEmitter: NativeEventEmitter; }; export default _default; -diff --git a/node_modules/@react-native-community/netinfo/package.json b/node_modules/@react-native-community/netinfo/package.json -index 3c80db2..61e6564 100644 ---- a/node_modules/@react-native-community/netinfo/package.json -+++ b/node_modules/@react-native-community/netinfo/package.json -@@ -48,6 +48,7 @@ - "network info" - ], - "peerDependencies": { -+ "react": "*", - "react-native": ">=0.59" - }, - "dependencies": {}, -@@ -121,5 +122,13 @@ - "yarn eslint --fix", - "git add" - ] -+ }, -+ "codegenConfig": { -+ "name": "RNCNetInfoSpec", -+ "type": "modules", -+ "jsSrcsDir": "src/internal", -+ "android": { -+ "javaPackageName": "com.reactnativecommunity.netinfo" -+ } - } - } diff --git a/node_modules/@react-native-community/netinfo/react-native-netinfo.podspec b/node_modules/@react-native-community/netinfo/react-native-netinfo.podspec index e34e728..9090eb1 100644 --- a/node_modules/@react-native-community/netinfo/react-native-netinfo.podspec @@ -2971,95 +2955,95 @@ index 878f7ba..0000000 --- a/node_modules/@react-native-community/netinfo/windows/.npmignore +++ /dev/null @@ -1,92 +0,0 @@ --*AppPackages* --*BundleArtifacts* -- --#OS junk files --[Tt]humbs.db --*.DS_Store -- --#Visual Studio files --*.[Oo]bj --*.user --*.aps --*.pch --*.vspscc --*.vssscc --*_i.c --*_p.c --*.ncb --*.suo --*.tlb --*.tlh --*.bak --*.[Cc]ache --*.ilk --*.log --*.lib --*.sbr --*.sdf --*.opensdf --*.opendb --*.unsuccessfulbuild --ipch/ --[Oo]bj/ --[Bb]in --[Dd]ebug*/ --[Rr]elease*/ --Ankh.NoLoad -- --# Visual C++ cache files --ipch/ --*.aps --*.ncb --*.opendb --*.opensdf --*.sdf --*.cachefile --*.VC.db --*.VC.VC.opendb -- --#MonoDevelop --*.pidb --*.userprefs -- --#Tooling --_ReSharper*/ --*.resharper --[Tt]est[Rr]esult* --*.sass-cache -- --#Project files --[Bb]uild/ -- --#Subversion files --.svn -- --# Office Temp Files --~$* -- --# vim Temp Files --*~ -- --#NuGet --packages/ --*.nupkg -- --#ncrunch --*ncrunch* --*crunch*.local.xml -- --# visual studio database projects --*.dbmdl -- --#Test files --*.testsettings -- --#Other files --*.DotSettings --.vs/ --*project.lock.json -- --#Files generated by the VS build --**/Generated Files/** -- +-*AppPackages* +-*BundleArtifacts* +- +-#OS junk files +-[Tt]humbs.db +-*.DS_Store +- +-#Visual Studio files +-*.[Oo]bj +-*.user +-*.aps +-*.pch +-*.vspscc +-*.vssscc +-*_i.c +-*_p.c +-*.ncb +-*.suo +-*.tlb +-*.tlh +-*.bak +-*.[Cc]ache +-*.ilk +-*.log +-*.lib +-*.sbr +-*.sdf +-*.opensdf +-*.opendb +-*.unsuccessfulbuild +-ipch/ +-[Oo]bj/ +-[Bb]in +-[Dd]ebug*/ +-[Rr]elease*/ +-Ankh.NoLoad +- +-# Visual C++ cache files +-ipch/ +-*.aps +-*.ncb +-*.opendb +-*.opensdf +-*.sdf +-*.cachefile +-*.VC.db +-*.VC.VC.opendb +- +-#MonoDevelop +-*.pidb +-*.userprefs +- +-#Tooling +-_ReSharper*/ +-*.resharper +-[Tt]est[Rr]esult* +-*.sass-cache +- +-#Project files +-[Bb]uild/ +- +-#Subversion files +-.svn +- +-# Office Temp Files +-~$* +- +-# vim Temp Files +-*~ +- +-#NuGet +-packages/ +-*.nupkg +- +-#ncrunch +-*ncrunch* +-*crunch*.local.xml +- +-# visual studio database projects +-*.dbmdl +- +-#Test files +-*.testsettings +- +-#Other files +-*.DotSettings +-.vs/ +-*project.lock.json +- +-#Files generated by the VS build +-**/Generated Files/** +- diff --git a/src/CONST.ts b/src/CONST.ts index 50df9118a74e..bcf900bc4578 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -668,6 +668,7 @@ const CONST = { LIMIT: 50, // OldDot Actions render getMessage from Web-Expensify/lib/Report/Action PHP files via getMessageOfOldDotReportAction in ReportActionsUtils.ts TYPE: { + ACTIONABLE_ADD_PAYMENT_CARD: 'ACTIONABLEADDPAYMENTCARD', ACTIONABLE_JOIN_REQUEST: 'ACTIONABLEJOINREQUEST', ACTIONABLE_MENTION_WHISPER: 'ACTIONABLEMENTIONWHISPER', ACTIONABLE_REPORT_MENTION_WHISPER: 'ACTIONABLEREPORTMENTIONWHISPER', @@ -1245,7 +1246,7 @@ const CONST = { MAX_AMOUNT_OF_SUGGESTIONS: 20, MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER: 5, HERE_TEXT: '@here', - SUGGESTION_BOX_MAX_SAFE_DISTANCE: 38, + SUGGESTION_BOX_MAX_SAFE_DISTANCE: 10, BIG_SCREEN_SUGGESTION_WIDTH: 300, }, COMPOSER_MAX_HEIGHT: 125, @@ -2145,6 +2146,7 @@ const CONST = { ACCESS_VARIANTS: { PAID: 'paid', ADMIN: 'admin', + CONTROL: 'control', }, DEFAULT_MAX_EXPENSE_AGE: 90, DEFAULT_MAX_EXPENSE_AMOUNT: 200000, @@ -5243,6 +5245,7 @@ const CONST = { }, }, + MAX_LENGTH_256: 256, WORKSPACE_CARDS_LIST_LABEL_TYPE: { CURRENT_BALANCE: 'currentBalance', REMAINING_LIMIT: 'remainingLimit', @@ -5273,10 +5276,6 @@ const CONST = { DATE: 'date', LIST: 'dropdown', }, - - NAVIGATION_ACTIONS: { - RESET: 'RESET', - }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 8abb7738289c..b06b05dac7e1 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -320,9 +320,6 @@ const ONYXKEYS = { /** Onboarding Purpose selected by the user during Onboarding flow */ ONBOARDING_PURPOSE_SELECTED: 'onboardingPurposeSelected', - /** Onboarding error message to be displayed to the user */ - ONBOARDING_ERROR_MESSAGE: 'onboardingErrorMessage', - /** Onboarding policyID selected by the user during Onboarding flow */ ONBOARDING_POLICY_ID: 'onboardingPolicyID', @@ -801,7 +798,6 @@ type OnyxValuesMapping = { [ONYXKEYS.MAX_CANVAS_HEIGHT]: number; [ONYXKEYS.MAX_CANVAS_WIDTH]: number; [ONYXKEYS.ONBOARDING_PURPOSE_SELECTED]: string; - [ONYXKEYS.ONBOARDING_ERROR_MESSAGE]: string; [ONYXKEYS.ONBOARDING_POLICY_ID]: string; [ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID]: string; [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: boolean; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 2189522e45ea..56006d599d6f 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -704,6 +704,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/categories/:categoryName/edit', getRoute: (policyID: string, categoryName: string) => `settings/workspaces/${policyID}/categories/${encodeURIComponent(categoryName)}/edit` as const, }, + WORKSPACE_CATEGORY_GL_CODE: { + route: 'settings/workspaces/:policyID/categories/:categoryName/gl-code', + getRoute: (policyID: string, categoryName: string) => `settings/workspaces/${policyID}/categories/${encodeURIComponent(categoryName)}/gl-code` as const, + }, WORKSPACE_MORE_FEATURES: { route: 'settings/workspaces/:policyID/more-features', getRoute: (policyID: string) => `settings/workspaces/${policyID}/more-features` as const, @@ -736,6 +740,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/tag-list/:orderWeight', getRoute: (policyID: string, orderWeight: number) => `settings/workspaces/${policyID}/tag-list/${orderWeight}` as const, }, + WORKSPACE_TAG_GL_CODE: { + route: 'settings/workspaces/:policyID/tag/:orderWeight/:tagName/gl-code', + getRoute: (policyID: string, orderWeight: number, tagName: string) => `settings/workspaces/${policyID}/tag/${orderWeight}/${encodeURIComponent(tagName)}/gl-code` as const, + }, WORKSPACE_TAXES: { route: 'settings/workspaces/:policyID/taxes', getRoute: (policyID: string) => `settings/workspaces/${policyID}/taxes` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 0768ca8bb291..4790ac3c6a32 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -372,6 +372,7 @@ const SCREENS = { TAG_CREATE: 'Tag_Create', TAG_SETTINGS: 'Tag_Settings', TAG_LIST_VIEW: 'Tag_List_View', + TAG_GL_CODE: 'Tag_GL_Code', CURRENCY: 'Workspace_Profile_Currency', ADDRESS: 'Workspace_Profile_Address', WORKFLOWS: 'Workspace_Workflows', @@ -384,6 +385,7 @@ const SCREENS = { NAME: 'Workspace_Profile_Name', CATEGORY_CREATE: 'Category_Create', CATEGORY_EDIT: 'Category_Edit', + CATEGORY_GL_CODE: 'Category_GL_Code', CATEGORY_SETTINGS: 'Category_Settings', CATEGORIES_SETTINGS: 'Categories_Settings', MORE_FEATURES: 'Workspace_More_Features', diff --git a/src/components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/getBottomSuggestionPadding/index.ts b/src/components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/getBottomSuggestionPadding/index.ts index 3ad9bbe7b152..acdc643b6b70 100644 --- a/src/components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/getBottomSuggestionPadding/index.ts +++ b/src/components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/getBottomSuggestionPadding/index.ts @@ -1,5 +1,5 @@ function getBottomSuggestionPadding(): number { - return 0; + return 6; } export default getBottomSuggestionPadding; diff --git a/src/components/AutoCompleteSuggestions/index.tsx b/src/components/AutoCompleteSuggestions/index.tsx index 8634d6dd0ca0..1aa486eccd4d 100644 --- a/src/components/AutoCompleteSuggestions/index.tsx +++ b/src/components/AutoCompleteSuggestions/index.tsx @@ -22,8 +22,16 @@ const measureHeightOfSuggestionRows = (numRows: number, canBeBig: boolean): numb } return numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; }; -function isSuggestionRenderedAbove(isEnoughSpaceAboveForBig: boolean, isEnoughSpaceAboveForSmall: boolean): boolean { - return isEnoughSpaceAboveForBig || isEnoughSpaceAboveForSmall; +function isSuggestionMenuRenderedAbove(isEnoughSpaceAboveForBigMenu: boolean, isEnoughSpaceAboveForSmallMenu: boolean): boolean { + return isEnoughSpaceAboveForBigMenu || isEnoughSpaceAboveForSmallMenu; +} + +type IsEnoughSpaceToRenderMenuAboveCursor = Pick & { + contentHeight: number; + topInset: number; +}; +function isEnoughSpaceToRenderMenuAboveCursor({y, cursorCoordinates, scrollValue, contentHeight, topInset}: IsEnoughSpaceToRenderMenuAboveCursor): boolean { + return y + (cursorCoordinates.y - scrollValue) > contentHeight + topInset + CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_BOX_MAX_SAFE_DISTANCE; } /** @@ -35,7 +43,7 @@ function isSuggestionRenderedAbove(isEnoughSpaceAboveForBig: boolean, isEnoughSp function AutoCompleteSuggestions({measureParentContainerAndReportCursor = () => {}, ...props}: AutoCompleteSuggestionsProps) { const containerRef = React.useRef(null); const isInitialRender = React.useRef(true); - const isSuggestionAboveRef = React.useRef(false); + const isSuggestionMenuAboveRef = React.useRef(false); const leftValue = React.useRef(0); const prevLeftValue = React.useRef(0); const {windowHeight, windowWidth, isSmallScreenWidth} = useWindowDimensions(); @@ -44,11 +52,12 @@ function AutoCompleteSuggestions({measureParentContainerAndReportCu width: 0, left: 0, bottom: 0, + cursorCoordinates: {x: 0, y: 0}, }); const StyleUtils = useStyleUtils(); const insets = useSafeAreaInsets(); const {keyboardHeight} = useKeyboardState(); - const {paddingBottom: bottomInset} = StyleUtils.getSafeAreaPadding(insets ?? undefined); + const {paddingBottom: bottomInset, paddingTop: topInset} = StyleUtils.getSafeAreaPadding(insets ?? undefined); useEffect(() => { const container = containerRef.current; @@ -73,51 +82,51 @@ function AutoCompleteSuggestions({measureParentContainerAndReportCu measureParentContainerAndReportCursor(({x, y, width, scrollValue, cursorCoordinates}: MeasureParentContainerAndCursor) => { const xCoordinatesOfCursor = x + cursorCoordinates.x; - const leftValueForBigScreen = + const bigScreenLeftOffset = xCoordinatesOfCursor + CONST.AUTO_COMPLETE_SUGGESTER.BIG_SCREEN_SUGGESTION_WIDTH > windowWidth ? windowWidth - CONST.AUTO_COMPLETE_SUGGESTER.BIG_SCREEN_SUGGESTION_WIDTH : xCoordinatesOfCursor; - - let bottomValue = windowHeight - y - cursorCoordinates.y + scrollValue - (keyboardHeight || bottomInset); - const widthValue = isSmallScreenWidth ? width : CONST.AUTO_COMPLETE_SUGGESTER.BIG_SCREEN_SUGGESTION_WIDTH; - const contentMaxHeight = measureHeightOfSuggestionRows(suggestionsLength, true); const contentMinHeight = measureHeightOfSuggestionRows(suggestionsLength, false); - const isEnoughSpaceAboveForBig = windowHeight - bottomValue - contentMaxHeight > CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_BOX_MAX_SAFE_DISTANCE; - const isEnoughSpaceAboveForSmall = windowHeight - bottomValue - contentMinHeight > CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_BOX_MAX_SAFE_DISTANCE; + let bottomValue = windowHeight - (cursorCoordinates.y - scrollValue + y) - keyboardHeight; + const widthValue = isSmallScreenWidth ? width : CONST.AUTO_COMPLETE_SUGGESTER.BIG_SCREEN_SUGGESTION_WIDTH; + + const isEnoughSpaceToRenderMenuAboveForBig = isEnoughSpaceToRenderMenuAboveCursor({y, cursorCoordinates, scrollValue, contentHeight: contentMaxHeight, topInset}); + const isEnoughSpaceToRenderMenuAboveForSmall = isEnoughSpaceToRenderMenuAboveCursor({y, cursorCoordinates, scrollValue, contentHeight: contentMinHeight, topInset}); - const newLeftValue = isSmallScreenWidth ? x : leftValueForBigScreen; + const newLeftOffset = isSmallScreenWidth ? x : bigScreenLeftOffset; // If the suggested word is longer than 150 (approximately half the width of the suggestion popup), then adjust a new position of popup - const isAdjustmentNeeded = Math.abs(prevLeftValue.current - leftValueForBigScreen) > 150; + const isAdjustmentNeeded = Math.abs(prevLeftValue.current - bigScreenLeftOffset) > 150; if (isInitialRender.current || isAdjustmentNeeded) { - isSuggestionAboveRef.current = isSuggestionRenderedAbove(isEnoughSpaceAboveForBig, isEnoughSpaceAboveForSmall); - leftValue.current = newLeftValue; + isSuggestionMenuAboveRef.current = isSuggestionMenuRenderedAbove(isEnoughSpaceToRenderMenuAboveForBig, isEnoughSpaceToRenderMenuAboveForSmall); + leftValue.current = newLeftOffset; isInitialRender.current = false; - prevLeftValue.current = newLeftValue; + prevLeftValue.current = newLeftOffset; } let measuredHeight = 0; - if (isSuggestionAboveRef.current && isEnoughSpaceAboveForBig) { + if (isSuggestionMenuAboveRef.current && isEnoughSpaceToRenderMenuAboveForBig) { // calculation for big suggestion box above the cursor measuredHeight = measureHeightOfSuggestionRows(suggestionsLength, true); - } else if (isSuggestionAboveRef.current && isEnoughSpaceAboveForSmall) { + } else if (isSuggestionMenuAboveRef.current && isEnoughSpaceToRenderMenuAboveForSmall) { // calculation for small suggestion box above the cursor measuredHeight = measureHeightOfSuggestionRows(suggestionsLength, false); } else { // calculation for big suggestion box below the cursor measuredHeight = measureHeightOfSuggestionRows(suggestionsLength, true); - bottomValue = windowHeight - y - cursorCoordinates.y + scrollValue - measuredHeight - CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; + bottomValue = windowHeight - y - cursorCoordinates.y + scrollValue - measuredHeight - CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT - keyboardHeight; } setSuggestionHeight(measuredHeight); setContainerState({ left: leftValue.current, bottom: bottomValue, width: widthValue, + cursorCoordinates, }); }); - }, [measureParentContainerAndReportCursor, windowHeight, windowWidth, keyboardHeight, isSmallScreenWidth, suggestionsLength, bottomInset]); + }, [measureParentContainerAndReportCursor, windowHeight, windowWidth, keyboardHeight, isSmallScreenWidth, suggestionsLength, bottomInset, topInset]); - if (containerState.width === 0 && containerState.left === 0 && containerState.bottom === 0) { + if ((containerState.width === 0 && containerState.left === 0 && containerState.bottom === 0) || (containerState.cursorCoordinates.x === 0 && containerState.cursorCoordinates.y === 0)) { return null; } return ( diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx index e641a0c2218a..4cbf85cb0014 100644 --- a/src/components/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs.tsx @@ -36,10 +36,11 @@ function Breadcrumbs({breadcrumbs, style}: BreadcrumbsProps) { const theme = useTheme(); const styles = useThemeStyles(); const [primaryBreadcrumb, secondaryBreadcrumb] = breadcrumbs; + const isRootBreadcrumb = primaryBreadcrumb.type === CONST.BREADCRUMB_TYPE.ROOT; const fontScale = PixelRatio.getFontScale() > CONST.LOGO_MAX_SCALE ? CONST.LOGO_MAX_SCALE : PixelRatio.getFontScale(); return ( - {primaryBreadcrumb.type === CONST.BREADCRUMB_TYPE.ROOT ? ( + {isRootBreadcrumb ? (
/ {secondaryBreadcrumb.text} diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index f8acec2b07dd..126c81961cee 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -121,6 +121,9 @@ type ButtonProps = Partial & { /** Whether the button should use split style or not */ isSplitButton?: boolean; + + /** Whether button's content should be centered */ + isContentCentered?: boolean; }; type KeyboardShortcutComponentProps = Pick; @@ -206,6 +209,7 @@ function Button( accessibilityLabel = '', isSplitButton = false, link = false, + isContentCentered = false, ...rest }: ButtonProps, ref: ForwardedRef, @@ -248,7 +252,7 @@ function Button( // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if (icon || shouldShowRightIcon) { return ( - + {icon && ( diff --git a/src/components/ConfirmContent.tsx b/src/components/ConfirmContent.tsx index 26331f92401c..36f24c2a3477 100644 --- a/src/components/ConfirmContent.tsx +++ b/src/components/ConfirmContent.tsx @@ -14,8 +14,11 @@ import type IconAsset from '@src/types/utils/IconAsset'; import Button from './Button'; import Header from './Header'; import Icon from './Icon'; +import {Close} from './Icon/Expensicons'; import ImageSVG from './ImageSVG'; +import {PressableWithoutFeedback} from './Pressable'; import Text from './Text'; +import Tooltip from './Tooltip'; type ConfirmContentProps = { /** Title of the modal */ @@ -51,15 +54,36 @@ type ConfirmContentProps = { /** Icon to display above the title */ iconSource?: IconAsset; + /** Fill color for the Icon */ + iconFill?: string | false; + + /** Icon width */ + iconWidth?: number; + + /** Icon height */ + iconHeight?: number; + + /** Should the icon be centered? */ + shouldCenterIcon?: boolean; + /** Whether to center the icon / text content */ shouldCenterContent?: boolean; + /** Whether to show the dismiss icon */ + shouldShowDismissIcon?: boolean; + /** Whether to stack the buttons */ shouldStackButtons?: boolean; + /** Whether to reverse the order of the stacked buttons */ + shouldReverseStackedButtons?: boolean; + /** Styles for title */ titleStyles?: StyleProp; + /** Styles for title container */ + titleContainerStyles?: StyleProp; + /** Styles for prompt */ promptStyles?: StyleProp; @@ -85,13 +109,20 @@ function ConfirmContent({ shouldDisableConfirmButtonWhenOffline = false, shouldShowCancelButton = false, iconSource, + iconFill, shouldCenterContent = false, shouldStackButtons = true, titleStyles, promptStyles, contentStyles, iconAdditionalStyles, + iconWidth = variables.appModalAppIconSize, + iconHeight = variables.appModalAppIconSize, + shouldCenterIcon = false, + shouldShowDismissIcon = false, image, + titleContainerStyles, + shouldReverseStackedButtons = false, }: ConfirmContentProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -116,19 +147,35 @@ function ConfirmContent({ )} + {shouldShowDismissIcon && ( + + + + + + + + )} - {typeof iconSource === 'function' && ( - + {iconSource && ( + )} - +
+ {shouldShowCancelButton && shouldReverseStackedButtons && ( +