diff --git a/Mobile-Expensify b/Mobile-Expensify index f370c5f31cdf..768d69540612 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit f370c5f31cdfd750b0d42d75a471a9b8d30935ad +Subproject commit 768d695406126652ce222a46c95e643ba2e51e45 diff --git a/android/app/build.gradle b/android/app/build.gradle index 4c917b995331..09bf1473e841 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -110,8 +110,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009007706 - versionName "9.0.77-6" + versionCode 1009007801 + versionName "9.0.78-1" // 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/buildings.svg b/assets/images/buildings.svg new file mode 100644 index 000000000000..42171d499f26 --- /dev/null +++ b/assets/images/buildings.svg @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__building.svg b/assets/images/simple-illustrations/simple-illustration__building.svg new file mode 100644 index 000000000000..94a7320d8471 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__building.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__buildings.svg b/assets/images/simple-illustrations/simple-illustration__buildings.svg new file mode 100644 index 000000000000..cb22c3a29ce4 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__buildings.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md index f94e692f5e56..1398e02a7a03 100644 --- a/docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md +++ b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md @@ -3,97 +3,156 @@ title: Troubleshooting description: How to troubleshoot company card importing in Expensify --- # Overview -Whether you're encountering issues related to company cards, require assistance with company card account access, or have questions about company card import features, you've come to the right place. +This guide helps you troubleshoot common issues with company cards in Expensify, including connection errors, missing transactions, and account setup problems. -## How to add company cards to Expensify -You can add company credit cards under the Domain settings in your Expensify account by navigating to *Settings* > *Domain* > _Domain Name_ > *Company Cards* and clicking *Import Card/Bank* and following the prompts. +## Adding company cards to Expensify +To add company credit cards: -## To Locate Missing Card Transactions in Expensify -1. **Wait for Posting**: Bank transactions may take up to 24 hours to import into Expensify after they have "posted" at your bank. Ensure sufficient time has passed for transactions to appear. -2. **Update Company Cards**: Go to Settings > Domains > Company Cards. Click on the card in question and click "Update" to refresh the card feed. -3. **Reconcile Cards**: Navigate to the Reconciliation section under Settings > Domains > Company Cards. Refer to the detailed guide on how to use the [Reconciliation Dashboard](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Reconciliation#identifying-outstanding-unapproved-expenses-using-the-reconciliation-dashboard). -4. **Review Transactions**: Use the Reconciliation Dashboard to view all transactions within a specific timeframe. Transactions will display on the Expenses page based on their "Posted Date". If needed, uncheck the "use posted date" checkbox near the filters to view transactions based on their "Transaction Date" instead. -5. **Address Gaps**: If there is a significant gap in transactions or if transactions are still missing, contact Expensify's Concierge or your Account Manager. They can initiate a historical data update on your card feed to ensure all transactions are properly imported. +1. Go to **Settings** > **Domain** > _[Domain Name]_ > **Company Cards**. +2. Click **Import Card/Bank** and follow the prompts. -Following these steps should help you identify and resolve any issues with missing card transactions in Expensify. +{% include info.html %} +Only Domain Admins can connect and assign company cards in Expensify. If you're not a Domain Admin and want to connect your own credit card, follow the steps [here](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Personal-Credit-Cards) to connect it as a personal card. +{% include end-info.html %} -## Known issues importing transactions -The first step should always be to "Update" your card, either from Settings > Your Account > Credit Card Import or Settings > Domain > [Domain Name] > Company Cards for centrally managed cards. If a "Fix" or "Fix card" option appears, follow the steps to fix the connection. If this fails to import your missing transactions, there is a known issue whereby some transactions will not import for certain API-based company card connections. So far this has been reported on American Express, Chase and Wells Fargo. This can be temporarily resolved by creating the expenses manually instead: +## Best practices for establishing the initial card connection +To ensure a successful initial card connection in Expensify, follow these best practices: -- [Manually add the expenses](https://help.expensify.com/articles/expensify-classic/expenses/expenses/Add-an-expense) -- [Upload the expenses via CSV](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import) +- **Import in the Correct Location**: For company cards, navigate to **Settings** > **Domains** > _[Domain Name]_ > **Company Cards** > **Import Card** to establish the connection. For personal or individual card accounts, refer to the instructions [here](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Personal-Credit-Cards). +- **Select the Appropriate Bank Connection**: Ensure you’re selecting the appropriate bank connection for your cards. +- **Use Master or Parent Administrative Credentials**: For company cards, always use the master administrative credentials to import the entire set of cards. +- **Disable Two-Factor Authentication (2FA)**: Expensify cannot bypass bank-imposed 2FA requirements. To maintain a stable connection, temporarily disable 2FA on your bank account before attempting to connect. -# Errors connecting company cards +By following these steps, you can avoid common issues and establish a stable card connection with Expensify. + +# Resolving missing card transactions + +Here are some common steps to resolve issues with missing imported expenses: + +1. **Wait for posting.** Bank transactions may take up to 24 hours to import into Expensify after they have posted at your bank. Ensure sufficient time has passed for transactions to appear. +2. **Update company cards.** Go to **Settings** > **Domains** > _[Domain Name]_ > **Company Cards**. Click on the card in question and select **Update** to refresh the card feed. +3. **Reconcile cards.** Navigate to the **Reconciliation** section under **Settings** > **Domains** > _[Domain Name]_ > **Company Cards**. Refer to the detailed guide on how to use the [Reconciliation Dashboard](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Reconciliation#identifying-outstanding-unapproved-expenses-using-the-reconciliation-dashboard). +4. **Review transactions.** Use the [Reconciliation Dashboard](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Reconciliation#identifying-outstanding-unapproved-expenses-using-the-reconciliation-dashboard) to view all transactions within a specific timeframe. Transactions will display on the **Expenses** page based on their posted date. If needed, uncheck the Use Posted Date checkbox near the filters to view transactions based on their Transaction Date instead. +5. **Address gaps.** If there is a significant gap in transactions or if transactions are still missing, contact Concierge or your Account Manager. They can initiate a historical data update on your card feed to ensure all transactions are properly imported. + +# General troubleshooting + +## Common import problems + +If company cards seem to be disconnected or not working as expected, troubleshoot by: +- Clicking **Update Card** under: + - **Settings** > **Account** > **Credit Card Import** for personal cards, or + - **Settings** > **Domains** > _[Domain Name]_ > **Company Cards** for company cards. +- If a **Fix** option appears, click on it and follow the steps to fix the connection. + +## Alternative workarounds +For persistent issues with API-based connections (e.g., American Express, Chase, Wells Fargo), the alternative option is to [manually add expenses](https://help.expensify.com/articles/expensify-classic/expenses/expenses/Add-an-expense), or [upload expenses via CSV](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import). + +## The connection is established but there are no cards to assign +When establishing the connection, you must assign cards during the same session. It isn't possible to create the connection, log out, and assign the cards later, as the connection will not stick and will require you to reattempt the connection. + +# Addressing duplicate expenses + +If a workspace member is experiencing duplicated expenses, this is typically due to: + + - A cardholder having accidentally imported the card as a personal credit card, in addition to being assigned the company card by a Domain Admin. + - To troubleshoot, have the employee navigate to **Settings** > **Account** > **Credit Card Import** and confirm that their card is only listed once. + + - The card was reassigned to the cardholder without the appropriate transaction start date being selected, resulting in a period of overlap. + - To troubleshoot, ensure expenses on the new card assignment have not been submitted. Then unassign the card and reassign it with a more appropriate start date. This action will delete all unsubmitted expenses from the new card feed. + +{% include info.html %} +Deleting a duplicate card will remove all Unapproved and Open expenses linked to that card. However, transactions associated with the remaining assigned card will remain unaffected. Any receipts attached to the deleted transactions will still appear on the Expenses page and can be reattached to the corresponding imported expense on the remaining assigned card. +{% include end-info.html %} + +# Tips for stable bank connections + +## Causes for connection breaks +Banks frequently update their APIs to enhance the security of financial information. However, for security reasons, they may not notify third-party services like Expensify in advance of these changes. Expensify's engineering team works diligently to minimize interruptions by monitoring bank connections and collaborating with banks to address updates promptly. + +## Resolving connection issues +Expensify's API-based banking connections rely on the online banking login credentials to maintain the connection. If your online banking username, password, security questions, login authentication, or card numbers change, the connection may need to be reestablished. Domain Admins can update this information in Expensify and manually reestablish the connection via **Settings** > **Domains** > _[Domain Name]_ > **Company Cards** > **Fix**. The Domain Admin will be prompted to enter the new credentials or updated information, which should reestablish the connection. + +# Common errors and resolutions + +Here are some errors that can occur when working with bank connections, and steps for resolving them: ## Error: Too many attempts -If you've been locked out while trying to import a new card, you'll need to wait a full 24 hours before trying again. This lock happens when incorrect online banking credentials are entered multiple times, and it's there for your security — it can't be removed. To avoid this, make sure your online banking credentials are correct before attempting to import your card again. - -## Error: Invalid credentials/Login failed -Verify your ability to log into your online banking portal by attempting to log into your bank account via the banking website. -Check for any potential temporary outages on your bank's end that may affect third-party connections like Expensify. -For specific card types: -- *Chase Card*: Confirm your password meets their new 8-32 character requirement. -- *Wells Fargo Card*: Ensure your password is under 14 characters. Reset it if necessary before importing your card to Expensify. If your card is already imported, update it and use the "Fix Card" option to reestablish the connection. -- *SVB Card*: Enable Direct Connect from the SVB website and use your online banking username and Direct Connect PIN instead of your password when connecting an SVB card. If connecting via *Settings* > *Domain* > _[Domain Name]_ > *Company Cards*, contact SVB for CDF feed setup. +If you've been locked out while trying to import a new card, you will need to wait a full 24 hours before trying again. This lock happens when incorrect online banking credentials are entered multiple times, and it cannot be bypassed. To avoid this, make sure your online banking credentials are correct before attempting to import your card again. + +## Error: Invalid credentials/login failed +Verify the online banking login details by accessing your bank's website directly. +- Some known bank-specific requirements are: + - **Chase**. Password must meet their 8-32 character requirement. + - **Wells Fargo**. Password must be under 14 characters. + - **SVB**. Enable Direct Connect and use the Direct Connect PIN for login. ## Error: Direct Connect not enabled -Direct Connect will need to be enabled in your account for your bank/credit card provider before you can import your card to Expensify. Please reach out to your bank to confirm if this option is available for your account, as well as get instructions on how to get this setup. +Direct Connect needs to be enabled on the bank account by your bank or credit card provider before it can be connected to Expensify. Please reach out to your bank to confirm if this option is available for your account and get instructions on how to enable it. -## Error: Account Setup -This error message typically indicates that there's something you need to do on your bank account's end. Please visit your online banking portal and check if there are any pending actions required. Once you've addressed those, you can try connecting your card again. -For Amex cardholders with multiple card programs in your Amex US Business account: To import multiple card programs into Expensify, you'll need to contact Amex and request that they separate the multiple card programs into distinct logins. For instance, you'll want to have your _Business Platinum_ cards under *"username1/password1"* and _Business Gold_ cards under *"username2/password2."* This ensures smooth integration with Expensify. +## Error: Account setup +This error message indicates that there is something you need to do on your bank account's end. Please visit your online banking portal and check if there are any pending actions required before attempting to connect your card again. -## Error: Account type not supported -If Expensify doesn't have a direct connection to your bank/credit card provider, we can still support the connection via spreadsheet import, which you can learn more about [here](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import). If the cards you're trying to import are company cards, it’s possible that you might be able to obtain a commercial feed directly from your bank. Please find more information on this [here](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Commercial-Card-Feeds). +# Troubleshooting American Express connections -## Error: Username/Password/Questions out of date -Your company card connection is broken because we're missing some answers to some security questions. Please head to *Settings* > *Domain* > _[Domain Name]_ > *Company Cards* and click _Fix Card_. -This will require you to answer your bank's security questions. You will need to do this for each security question you have with your bank; so if you have 3 security questions, you will need to do this 3 times. +## Account roles and permissions +When connecting American Express cards to Expensify, you must use the Amex login credentials of the Primary/Basic account holder. Using other credentials, such as Supplemental Cardmember or Authorized Account Manager, will fail to load card data or may result in an error. -## Error: Account not found/Card number changed -This error message appears when you have been issued a new card, or if there's been a significant change to the account in some other way (password and/or card number change). -When your online bank/card account password has been changed, you may need to update the details on the Expensify end as well. To do this, navigate to *Settings* > *Domain* > _[Domain Name]_ > *Company Cards* and click _Fix Card_. -If there’s been a recent change to the card number, you’ll have to remove the card with the previous number and re-import the card using the new number. A Domain Admin will have to re-assign the card via *Settings* > *Domain* > _Domain Name_ > *Company Cards*. Before removing the card, please ensure *all Open reports have been submitted*, as removing the card will remove all imported transactions from the account that are associated with that card. +{% include info.html %} +In American Express, the Primary/Basic Account Holder is typically the person who applied for the American Express Business card, owns the account, manages its finances, and controls card issuance and account management. They can see all charges made by other cardmembers on their account. -## Error: General connection error -This error message states that your bank or credit card provider is under maintenance and is unavailable at this time. Try waiting a few hours before trying to import your credit card again. Check out our [status page](https://status.expensify.com/) for updates on bank/credit card connections, or you can also choose to subscribe to updates for your specific account type. +By contrast, a Supplemental Cardmember or Employee Cardmember is typically an employee on American Express accounts with access to their own card and payments. An Authorized Account Manager (AAM) has management privileges allowing them to manage the account and Supplemental Cardmembers' accounts. These roles do not have sufficient permissions in American Express to authorize the connection to Expensify, and therefore only the Primary/Basic Account Holder credentials can be used. +{% include end-info.html %} -## Error: Not seeing cards listed after a successful login -The card will only appear in the drop-down list for assignment once it’s activated and there are transactions that have been incurred and posted on the card. If not, the card won't be available to assign to the card holder until then. +## Importing multiple card programs +If you have multiple American Express card programs, contact Amex and request that they separate the multiple card programs into distinct logins. For example, you can have your _Business Platinum_ cards under *"username1/password1"* and _Business Gold_ cards under *"username2/password2"*. This ensures smooth integration with Expensify. -# Troubleshooting issues assigning company cards +## Connecting multiple company card programs under the same credentials +If you have multiple company card programs using the same credentials, you can import all programs together, which will display them under a single dropdown. Be sure to select all relevant cards each time you add cards from any program. -## Why do bank connections break? -Banks often make changes to safeguard your confidential information, and when they do, we need to update the connection between Expensify and the bank. We have a team of engineers who work closely with banks to monitor this and update our software accordingly when this happens. -The first step is to check if there have been any changes to your bank information. Have you recently changed your banking password without updating it in Expensify? Has your banking username or card number been updated? Did you update your security questions for your bank? -If you've answered "yes" to any of these questions, a Domain Admins need to update this information in Expensify and manually re-establish the connection by heading to *Settings* > *Domains* > _Domain Name_ > *Company Cards* > *Fix*. The Domain Admin will be prompted to enter the new credentials/updated information and this should reestablish the connection. +If you prefer to manage card programs separately, you can import them one at a time, ensuring you select all cards within the specific program during each import. After authorizing the account, you will be guided back to Expensify to assign the cards as needed. -## How do I resolve errors while I’m trying to import my card?* -Make sure you're importing your card in the correct spot in Expensify and selecting the right bank connection. For company cards, use the master administrative credentials to import your set of cards at *Settings* > *Domains* > _Domain Name_ > *Company Cards* > *Import Card*. -Please note there are some things that cannot be bypassed within Expensify, including two-factor authentication being enabled within your bank account. This will prevent the connection from remaining stable and will need to be turned off on the bank side. +*Important Reminder*: Whenever you need to access the connection to assign a new card, you must still choose all card programs. For example, if you have a new employee with a card under your Business Gold Rewards Card program, you will still need to authorize all the cards in that program or all the programs if you have only one dropdown menu. -## Why Can’t I See the Transactions Before a Certain Date? -When importing a card into Expensify, the platform typically retrieves 30-90 days of historical transactions, depending on the card or account type. For commercial feeds, transactions cannot be imported before the bank starts sending data. If needed, banks can send backdated files, and Expensify can run a historical update upon request. +## Adding cards under different programs with different logins +If you have multiple card programs with different credentials, you will need another Domain Admin account to add each card program from their own account. Once all Domain Admins have connected and assigned the cards they are the Primary account holder for, all cards will be listed under one *American Express (New and Upgraded)* list on the Domain Company Card page. -Additionally, Expensify does not import transactions dated before the "start date" you specify when assigning the card. Unless transitioning from an old card to a new one to avoid duplicates, it's advisable to set the start date to "earliest possible" or leave it blank. +## Amex error: Username, password, or security questions out of date +Your company card connection is broken because Expensify is missing answers to your security questions. Go to **Settings** > **Domain** > _[Domain Name]_ > **Company Cards** and click **Fix**. Answer your bank's security questions to restore the connection. Repeat this process for each security question your bank requires. -For historical expenses that cannot be imported automatically, consider using Expensify's [company card](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/CSV-Import) or [personal card](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Personal-Credit-Cards#importing-expenses-via-a-spreadsheet) spreadsheet import method. This allows you to manually input missing transactions into the system. +## Amex error: Account not found or card number changed +This error occurs when you have been issued a new card or if there has been a significant change to the account, such as a password or card number update. -## Why Am I / Why Is My Employee Seeing Duplicates? -If an employee is seeing duplicate expenses, they may have accidentally imported the card as a personal credit card as well as having the Domain Admin assign them a company card. +To update the connection: +1. Go to **Settings** > **Domain** > _[Domain Name]_ > **Company Cards** and click **Fix**. +2. If there has been a card number change, remove the card with the previous number and re-import the card with the new number. +3. Before removing the card, ensure all open reports have been submitted. Removing the card will delete all imported transactions associated with that card. A Domain Admin will need to re-assign the card after re-importing it. -To troubleshoot: -- Have the employee navigate to their Settings > Your Account > Credit Card Import and confirm that their card is only listed once. -- If the card is listed twice, delete the entry without the "padlock" icon. +## Amex error: General connection error +This error indicates that your bank or credit card provider is under maintenance and unavailable. Wait a few hours before trying to import your credit card again. Check Expensify's [status page](https://status.expensify.com/) for updates on bank or credit card connections, or subscribe to updates for your account type. + +## Amex error: Session has expired +If you see an error stating "Your session has expired. Please return to Expensify and try again," this means you are using incorrect Amex credentials. Use the Primary/Basic account holder credentials. If you are unsure which credentials to use, contact American Express for guidance. + +## Amex error: Card isn't eligible +This error occurs when the account is not a business account or the credentials used are not for the Primary account holder. Verify the account type and credentials before attempting to connect again. -**Important:** Deleting a duplicate card will delete all unapproved expenses from that transaction feed. Transactions associated with the remaining card will not be affected. If receipts were attached to those transactions, they will still be on the Expenses page, and the employee can click to SmartScan them again. +# Troubleshooting Chase connections -Duplicate expenses might also occur if you recently unassigned and reassigned a company card with an overlapping start date. If this is the case and expenses on the “new” copy have not been submitted, you can unassign the card again and reassign it with a more appropriate start date. This action will delete all unsubmitted expenses from the new card feed. +## Resetting Chase access to Expensify +If you are experiencing issues with your Chase connection in Expensify, resetting access can often resolve the problem. Follow these steps to troubleshoot: -## What are the most reliable bank connections in Expensify?* -All bank connections listed below are extremely reliable, but we recommend transacting with the Expensify Visa® Commercial Card. It also offers daily and monthly settlement, unapproved expense limits, realtime compliance for secure and efficient spending, and cash back on all US purchases. [Click here to learn more about the Expensify Card](https://use.expensify.com/company-credit-card). +1. Log in to your Chase account portal and visit the [Linked Apps & Websites](https://www.chase.com/digital/data-sharing) page in the Security Center. +2. Locate Expensify in the Linked Apps & Websites list. +3. Select **Stop sharing data** to disconnect Expensify's access to your Chase account. +4. After resetting access, follow the instructions [here](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting#how-to-add-company-cards-to-expensify) to reestablish the connection to Chase. -We've also teamed up with major banks worldwide to ensure a smooth import of credit card transactions into your accounts: +{% include faq-begin.md %} + +## What bank connections does Expensify offer? +Expensify offers highly reliable bank connections, but we recommend using the Expensify Visa® Commercial Card. It provides daily and monthly settlement, unapproved expense limits, real-time compliance for secure and efficient spending, and cash back on all US purchases. [Click here to learn more about the Expensify Card](https://use.expensify.com/company-credit-card). + +Alternatively, Expensify has partnered with major banks worldwide to ensure a smooth import of credit card transactions into your accounts, including: - American Express - Bank of America - Brex @@ -103,33 +162,13 @@ We've also teamed up with major banks worldwide to ensure a smooth import of cre - Stripe - Wells Fargo -Commercial feeds for company cards are the dependable connections in Expensify. If you have a corporate or commercial card account, you might have access to a daily transaction feed where expenses from Visa, Mastercard, and American Express are automatically sent to Expensify. Reach out to your banking relationship manager to check if your card program qualifies for this feature. +## What are the most stable bank connections? +Commercial feeds for company cards are the most dependable connections in Expensify and are considered more stable than API-based connections. If you have a corporate or commercial card account, you might have access to a daily transaction feed where expenses from Visa, Mastercard, and American Express are automatically sent to Expensify. Contact your banking relationship manager to check if your card program qualifies for this feature. -# Troubleshooting American Express Business +## Why can’t I see the transactions before a certain date? +When importing a card into Expensify, the bank typically provides 30-90 days of historical transactions, depending on the card or account type. For commercial feeds, transactions cannot be imported before the bank starts sending data, however banks can send backdated files if historical transactions are needed. -## Amex account roles -American Express provides three different roles for accessing accounts on their website. When connecting Amex cards to Expensify, it's crucial to use the credentials of the Primary/Basic account holder. Here's what each role means: -- *Primary/Basic Account Holder*: The person who applied for the American Express Business card, owns the account, manages its finances, and controls card issuance and account management. They can view all charges by other cardmembers on their account. They can see all charges made by other cardmembers on their account. -- *Supplemental Cardmember (Employee Cardmember)*: Chosen by the Primary Card Member (typically an employee on business accounts), they can access their own card info and make payments but can't see other account details. -- *Authorized Account Manager (AAM)*: Chosen by the Primary Card Member, AAMs can manage the account online or by phone, but they can't link cards to services like Expensify. They have admin rights, including adding cards, making payments, canceling cards, and setting limits. To connect cards to Expensify, use the Primary Card Holder's credentials for full access. - -## The connection is established but there are no cards to assign +Additionally, Expensify does not import transactions dated before the "start date" you specify when assigning the card. Unless transitioning from an old card to a new one to avoid duplicates, it is advisable to set the start date to "earliest possible" or leave it blank. For historical expenses that cannot be imported automatically, consider using Expensify's [company card](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/CSV-Import) or [personal card](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Personal-Credit-Cards#importing-expenses-via-a-spreadsheet) spreadsheet import method to manually input missing transactions into the system. -When establishing the connection, you must assign cards during the same session. It isn't possible to create the connection, log out, and assign the cards later, as the connection will not stick, and require you to reattempt the connection again. +{% include faq-end.md %} -## Amex error: Card isn't eligible -This error comes directly from American Express and is typically related to an account that is not a business account or using credentials that are not the primary account holder credentials. - -## Amex error: Session has expired -If you get an error stating an American Express Business Card “Your session has expired. Please return to Expensify and try again, this always means that you are using the incorrect credentials. Remember, you need to use primary/basic cardholder credentials. If you are not sure which credentials you should use, reach out to American Express for guidance. - -## Connect multiple company card programs under the same credentials -If you have multiple company card programs with the same credentials, you can select ALL programs at once. With this, all programs will be under one dropdown. Make sure to select all cards each time you are adding any cards from any program. -If you would like your card programs listed under separate dropdowns, you can select only that group making sure to select all cards from that group each time you are adding a new card. -Once you have authorized the account, you’ll be guided back to Expensify where you’ll assign all necessary cards across all programs. -This will store all cards under the same American Express Business connection dropdown and allow all cards to be added to Expensify for you to assign to users. -*Important Reminder*: Whenever you need to access the connection to assign a new card, you must still choose "ALL card programs." For instance, if you have a new employee with a card under your Business Gold Rewards Card program, you'll still need to authorize all the cards in that program or all the programs if you have only one dropdown menu! - -## Add cards under different programs with different logins -If you have multiple card programs with different credentials, you will need to have another Domain Admin account add each card program from their own account. -Once all Domain Admins have connected and assigned the cards that they are the Primary account holder for, all cards will be listed under one *American Express (New and Upgraded)* list in the Domain Company Card page. diff --git a/docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md b/docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md index 68bca5228913..ec3d45b3ac08 100644 --- a/docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md +++ b/docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md @@ -39,14 +39,14 @@ The three options for the date your report will export with are: ## Accounting Method This dictates when reimbursable expenses will export, according to your preferred accounting method: -- Accrual: Out of pocket expenses will export immediately when the report is final approved -- Cash: Out of pocket expenses will export when paid via Expensify or marked as Reimbursed +- Accrual: Out-of-pocket expenses will export immediately when the report is final approved +- Cash: Out-of-pocket expenses will export when paid via Expensify or marked as Reimbursed ## Export Settings for Reimbursable Expenses **Expense Reports:** Expensify transactions will export reimbursable expenses as expense reports by default, which will be posted to the payables account designated in NetSuite. -**Vendor Bills:** Expensify transactions export as vendor bills in NetSuite and will be mapped to the subsidiary associated with the corresponding workspace. Each report will be posted as payable to the vendor associated with the employee who submitted the report. You can also set an approval level in NetSuite for vendor bills. +**Vendor Bills:** Expensify transactions export as vendor bills in NetSuite and are mapped to the subsidiary associated with the corresponding workspace. Each report is posted as payable to the vendor associated with the employee who submitted it. You can also set an approval level in NetSuite for vendor bills. **Journal Entries:** Expensify transactions that are set to export as journal entries in NetSuite will be mapped to the subsidiary associated with this workspace. All the transactions will be posted to the payable account specified in the workspace. You can also set an approval level in NetSuite for the journal entries. @@ -63,7 +63,7 @@ This dictates when reimbursable expenses will export, according to your preferre - Journal entry forms do not contain a customer column, so it is not possible to export customers or projects with this export option - The credit line and header level classifications are pulled from the employee record -**Expense Reports:** To use the expense report option for your corporate card expenses, you will need to set up your default corporate cards in NetSuite. +**Expense Reports:** To use the expense report option for your corporate card expenses, you must set up your default corporate cards in NetSuite. To use a default corporate card for non-reimbursable expenses, you must select the correct card on the employee records (for individual accounts) or the subsidiary record (If you use a non-one world account, the default is found in your accounting preferences). @@ -87,6 +87,8 @@ When selecting the option to export non-reimbursable expenses as vendor bills, t The Coding tab is where NetSuite information is configured in Expensify, which allows employees to code expenses and reports accurately. There are several coding options in NetSuite. Let’s go over each of those below. +![Insert alt text for accessibility here]({{site.url}}/assets/images/NetSuite_Configure_08.png){:width="100%"} + ## Expense Categories Expensify's integration with NetSuite automatically imports NetSuite Expense Categories as Categories in Expensify. @@ -225,6 +227,8 @@ From there, you should see the values for the Custom Lists under the Tag or Repo The NetSuite integration’s advanced configuration settings are accessed under **Settings > Workspaces > Group > _[Workspace Name]_ > Connections > NetSuite > Configure > Advanced tab**. +![Insert alt text for accessibility here]({{site.url}}/assets/images/NetSuite_Configure_09.png){:width="100%"} + Let’s review the different advanced settings and how they interact with the integration. ## Auto Sync diff --git a/docs/articles/expensify-classic/domains/SAML-SSO.md b/docs/articles/expensify-classic/domains/SAML-SSO.md index da4bd5639120..df73cf5d54c0 100644 --- a/docs/articles/expensify-classic/domains/SAML-SSO.md +++ b/docs/articles/expensify-classic/domains/SAML-SSO.md @@ -17,7 +17,7 @@ Once the domain is verified, you can access the SSO settings by navigating to Se **Below are instructions for setting up Expensify for specific SSO providers:** - [Amazon Web Services (AWS SSO)](https://static.global.sso.amazonaws.com/app-202a715cb67cddd9/instructions/index.htm) - [Google SAML](https://support.google.com/a/answer/7371682) (for GSuite, not Google SSO) -- [Microsoft Azure Active Directory](https://azure.microsoft.com/en-us/documentation/articles/active-directory-saas-expensify-tutorial/) +- [Microsoft Entra ID (formerly Azure Active Directory)](https://learn.microsoft.com/en-us/entra/identity/saas-apps/expensify-tutorial) - [Okta](https://saml-doc.okta.com/SAML_Docs/How-to-Configure-SAML-2.0-for-Expensify.html) - [OneLogin](https://onelogin.service-now.com/support?id=kb_article&sys_id=e44c9e52db187410fe39dde7489619ba) - [Oracle Identity Cloud Service](https://docs.oracle.com/en/cloud/paas/identity-cloud/idcsc/expensify.html#Expensify) @@ -39,13 +39,13 @@ The entityID for Expensify is https://expensify.com. Remember not to copy and pa ## Can you have multiple domains with only one entity ID? Yes. Please send a message to the Concierge or your account manager, and we will enable the use of the same entity ID with multiple domains. -## How can I update the Microsoft Azure SSO Certificate? +## How can I update the Microsoft Entra ID SSO Certificate? Expensify's SAML configuration doesn't support multiple active certificates. This means that if you create the new certification ahead of time without first removing the old one, the respective IDP will include two unique x509 certificates instead of one, and the connection will break. Should you need to access Expensify, switching back to the old certificate will continue to allow access while that certificate is still valid. -**To transfer from one Microsoft Azure certificate to another, please follow the below steps:** -1. In Azure Directory, create your new certificate. -2. In Azure Director, remove the old, expiring certificate. -3. In Azure Directory, activate the remaining certificate and get a new IDP for Expensify from it. +**To transfer from one Microsoft Entra certificate to another, please follow the below steps:** +1. In Microsoft Entra, create your new certificate. +2. In Microsoft Entra, remove the old, expiring certificate. +3. In Microsoft Entra, activate the remaining certificate and get a new IDP for Expensify from it. 4. In Expensify, replace the previous IDP with the new IDP. 5. Log in via SSO. If login continues to fail, write to Concierge for assistance. diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 1e81fdedcaee..6a6d5bef5d7d 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 9.0.77 + 9.0.78 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.77.6 + 9.0.78.1 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 2291b6e19e37..c996883405b2 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 9.0.77 + 9.0.78 CFBundleSignature ???? CFBundleVersion - 9.0.77.6 + 9.0.78.1 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index f94a9a34f558..4e4305e1cbab 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 9.0.77 + 9.0.78 CFBundleVersion - 9.0.77.6 + 9.0.78.1 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 51773c06935e..a1e8c86e2a78 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.77-6", + "version": "9.0.78-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.77-6", + "version": "9.0.78-1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index c3f6d8e730d8..b2248ab75cc2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.77-6", + "version": "9.0.78-1", "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.", diff --git a/scripts/applyPatches.sh b/scripts/applyPatches.sh index 9ba8360ea39f..fa87b4540b38 100755 --- a/scripts/applyPatches.sh +++ b/scripts/applyPatches.sh @@ -16,10 +16,13 @@ function patchPackage { OS="$(uname)" if [[ "$OS" == "Darwin" || "$OS" == "Linux" ]]; then npx patch-package --error-on-fail --color=always + EXIT_CODE=$? if [[ "$IS_HYBRID_APP_REPO" == "true" && "$NEW_DOT_FLAG" == "false" ]]; then echo -e "\n${GREEN}Applying HybridApp patches!${NC}" npx patch-package --patch-dir 'Mobile-Expensify/patches' --error-on-fail --color=always + EXIT_CODE+=$? fi + exit $EXIT_CODE else error "Unsupported OS: $OS" exit 1 diff --git a/src/CONST.ts b/src/CONST.ts index e317c19d96d2..740ee2206876 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1152,6 +1152,7 @@ const CONST = { UPDATE_TIME_RATE: 'POLICYCHANGELOG_UPDATE_TIME_RATE', LEAVE_POLICY: 'POLICYCHANGELOG_LEAVE_POLICY', CORPORATE_UPGRADE: 'POLICYCHANGELOG_CORPORATE_UPGRADE', + TEAM_DOWNGRADE: 'POLICYCHANGELOG_TEAM_DOWNGRADE', }, ROOM_CHANGE_LOG: { INVITE_TO_ROOM: 'INVITETOROOM', @@ -1335,6 +1336,9 @@ const CONST = { SEARCH_OPTION_LIST_DEBOUNCE_TIME: 300, RESIZE_DEBOUNCE_TIME: 100, UNREAD_UPDATE_DEBOUNCE_TIME: 300, + SEARCH_CONVERT_SEARCH_VALUES: 'search_convert_search_values', + SEARCH_MAKE_TREE: 'search_make_tree', + SEARCH_BUILD_TREE: 'search_build_tree', SEARCH_FILTER_OPTIONS: 'search_filter_options', USE_DEBOUNCED_STATE_DELAY: 300, LIST_SCROLLING_DEBOUNCE_TIME: 200, diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index a43f1622ec9a..86d4c90d67ed 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -890,8 +890,7 @@ type OnyxValuesMapping = { [ONYXKEYS.ACCOUNT_MANAGER_REPORT_ID]: string; [ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER]: boolean; - // NVP_ONBOARDING is an array for old users. - [ONYXKEYS.NVP_ONBOARDING]: Onboarding | []; + [ONYXKEYS.NVP_ONBOARDING]: Onboarding; // ONYXKEYS.NVP_TRYNEWDOT is HybridApp onboarding data [ONYXKEYS.NVP_TRYNEWDOT]: OnyxTypes.TryNewDot; diff --git a/src/components/EmptySelectionListContent.tsx b/src/components/EmptySelectionListContent.tsx index 67a9a2fc83f3..5281b1c33b4b 100644 --- a/src/components/EmptySelectionListContent.tsx +++ b/src/components/EmptySelectionListContent.tsx @@ -3,8 +3,10 @@ import {View} from 'react-native'; import type {TupleToUnion} from 'type-fest'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; import variables from '@styles/variables'; import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; import BlockingView from './BlockingViews/BlockingView'; import * as Illustrations from './Icon/Illustrations'; import ScrollView from './ScrollView'; @@ -34,7 +36,13 @@ function EmptySelectionListContent({contentType}: EmptySelectionListContentProps const EmptySubtitle = ( {translate(`emptyList.${contentType}.subtitleText1`)} - {translate(`emptyList.${contentType}.subtitleText2`)} + { + Navigation.navigate(ROUTES.REFERRAL_DETAILS_MODAL.getRoute(CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SUBMIT_EXPENSE, Navigation.getActiveRouteWithoutParams())); + }} + > + {translate(`emptyList.${contentType}.subtitleText2`)} + {translate(`emptyList.${contentType}.subtitleText3`)} ); diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index 4093b44743fe..02a6843dc11f 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -32,6 +32,7 @@ import Box from '@assets/images/box.svg'; import Briefcase from '@assets/images/briefcase.svg'; import Bug from '@assets/images/bug.svg'; import Building from '@assets/images/building.svg'; +import Buildings from '@assets/images/buildings.svg'; import CalendarSolid from '@assets/images/calendar-solid.svg'; import Calendar from '@assets/images/calendar.svg'; import Camera from '@assets/images/camera.svg'; @@ -235,6 +236,7 @@ export { Briefcase, Bug, Building, + Buildings, Calendar, Camera, Car, diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index 4379142619ff..0debd4585e7b 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -74,6 +74,8 @@ import BankArrow from '@assets/images/simple-illustrations/simple-illustration__ import BigRocket from '@assets/images/simple-illustrations/simple-illustration__bigrocket.svg'; import PinkBill from '@assets/images/simple-illustrations/simple-illustration__bill.svg'; import Binoculars from '@assets/images/simple-illustrations/simple-illustration__binoculars.svg'; +import Building from '@assets/images/simple-illustrations/simple-illustration__building.svg'; +import Buildings from '@assets/images/simple-illustrations/simple-illustration__buildings.svg'; import CarIce from '@assets/images/simple-illustrations/simple-illustration__car-ice.svg'; import Car from '@assets/images/simple-illustrations/simple-illustration__car.svg'; import ChatBubbles from '@assets/images/simple-illustrations/simple-illustration__chatbubbles.svg'; @@ -228,6 +230,8 @@ export { PendingBank, ThreeLeggedLaptopWoman, House, + Building, + Buildings, Alert, TeachersUnite, Abacus, diff --git a/src/components/ReportActionItem/TaskPreview.tsx b/src/components/ReportActionItem/TaskPreview.tsx index f6f436cbd51e..2ea295d16143 100644 --- a/src/components/ReportActionItem/TaskPreview.tsx +++ b/src/components/ReportActionItem/TaskPreview.tsx @@ -75,7 +75,8 @@ function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, che ? taskReport?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && taskReport.statusNum === CONST.REPORT.STATUS_NUM.APPROVED : action?.childStateNum === CONST.REPORT.STATE_NUM.APPROVED && action?.childStatusNum === CONST.REPORT.STATUS_NUM.APPROVED; const taskTitle = Str.htmlEncode(TaskUtils.getTaskTitleFromReport(taskReport, action?.childReportName ?? '')); - const taskAssigneeAccountID = Task.getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? -1; + const taskAssigneeAccountID = Task.getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? CONST.DEFAULT_NUMBER_ID; + const taskOwnerAccountID = taskReport?.ownerAccountID ?? action?.actorAccountID ?? CONST.DEFAULT_NUMBER_ID; const hasAssignee = taskAssigneeAccountID > 0; const personalDetails = usePersonalDetails(); const avatar = personalDetails?.[taskAssigneeAccountID]?.avatar ?? Expensicons.FallbackAvatar; @@ -106,12 +107,12 @@ function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, che { if (isTaskCompleted) { - Task.reopenTask(taskReport); + Task.reopenTask(taskReport, taskReportID); } else { - Task.completeTask(taskReport); + Task.completeTask(taskReport, taskReportID); } })} accessibilityLabel={translate('task.task')} diff --git a/src/components/ReportActionItem/TaskView.tsx b/src/components/ReportActionItem/TaskView.tsx index 2b0dc9387927..7901426b33e0 100644 --- a/src/components/ReportActionItem/TaskView.tsx +++ b/src/components/ReportActionItem/TaskView.tsx @@ -10,8 +10,7 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {usePersonalDetails} from '@components/OnyxProvider'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; import Text from '@components/Text'; -import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; -import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -27,27 +26,28 @@ import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {Report} from '@src/types/onyx'; -type TaskViewProps = WithCurrentUserPersonalDetailsProps & { +type TaskViewProps = { /** The report currently being looked at */ report: Report; }; -function TaskView({report, ...props}: TaskViewProps) { +function TaskView({report}: TaskViewProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); + const personalDetails = usePersonalDetails(); useEffect(() => { Task.setTaskReport(report); }, [report]); - const personalDetails = usePersonalDetails(); const taskTitle = convertToLTR(report.reportName ?? ''); const assigneeTooltipDetails = ReportUtils.getDisplayNamesWithTooltips( OptionsListUtils.getPersonalDetailsForAccountIDs(report.managerID ? [report.managerID] : [], personalDetails), false, ); - const isCompleted = ReportUtils.isCompletedTaskReport(report); const isOpen = ReportUtils.isOpenTaskReport(report); - const canModifyTask = Task.canModifyTask(report, props.currentUserPersonalDetails.accountID); - const canActionTask = Task.canActionTask(report, props.currentUserPersonalDetails.accountID); + const isCompleted = ReportUtils.isCompletedTaskReport(report); + const canModifyTask = Task.canModifyTask(report, currentUserPersonalDetails.accountID); + const canActionTask = Task.canActionTask(report, currentUserPersonalDetails.accountID); const disableState = !canModifyTask; const isDisableInteractive = !canModifyTask || !isOpen; const {translate} = useLocalize(); @@ -77,10 +77,10 @@ function TaskView({report, ...props}: TaskViewProps) { styles.ph5, styles.pv2, StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, false, disableState, !isDisableInteractive), true), - isDisableInteractive && !disableState && styles.cursorDefault, + isDisableInteractive && styles.cursorDefault, ]} - disabled={disableState} accessibilityLabel={taskTitle || translate('task.task')} + disabled={isDisableInteractive} > {({pressed}) => ( @@ -104,7 +104,7 @@ function TaskView({report, ...props}: TaskViewProps) { containerBorderRadius={8} caretSize={16} accessibilityLabel={taskTitle || translate('task.task')} - disabled={!canModifyTask || !canActionTask} + disabled={!canActionTask} /> - {isOpen && ( + {!isDisableInteractive && ( Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report.reportID, Navigation.getReportRHPActiveRoute()))} - shouldShowRightIcon={isOpen} + shouldShowRightIcon={!isDisableInteractive} disabled={disableState} wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} shouldGreyOutWhenDisabled={false} numberOfLinesTitle={0} interactive={!isDisableInteractive} + shouldUseDefaultCursorWhenDisabled /> @@ -153,23 +154,25 @@ function TaskView({report, ...props}: TaskViewProps) { avatarSize={CONST.AVATAR_SIZE.SMALLER} titleStyle={styles.assigneeTextStyle} onPress={() => Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report.reportID, Navigation.getReportRHPActiveRoute()))} - shouldShowRightIcon={isOpen} + shouldShowRightIcon={!isDisableInteractive} disabled={disableState} wrapperStyle={[styles.pv2]} isSmallAvatarSubscriptMenu shouldGreyOutWhenDisabled={false} interactive={!isDisableInteractive} titleWithTooltips={assigneeTooltipDetails} + shouldUseDefaultCursorWhenDisabled /> ) : ( Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report.reportID, Navigation.getReportRHPActiveRoute()))} - shouldShowRightIcon={isOpen} + shouldShowRightIcon={!isDisableInteractive} disabled={disableState} wrapperStyle={[styles.pv2]} shouldGreyOutWhenDisabled={false} interactive={!isDisableInteractive} + shouldUseDefaultCursorWhenDisabled /> )} @@ -180,4 +183,4 @@ function TaskView({report, ...props}: TaskViewProps) { TaskView.displayName = 'TaskView'; -export default withCurrentUserPersonalDetails(TaskView); +export default TaskView; diff --git a/src/components/Search/SearchRouter/SearchRouterList.tsx b/src/components/Search/SearchRouter/SearchRouterList.tsx index a53e49374d81..6210c8882136 100644 --- a/src/components/Search/SearchRouter/SearchRouterList.tsx +++ b/src/components/Search/SearchRouter/SearchRouterList.tsx @@ -12,6 +12,7 @@ import type {SearchQueryItem, SearchQueryListItemProps} from '@components/Select import type {SectionListDataType, SelectionListHandle, UserListItemProps} from '@components/SelectionList/types'; import UserListItem from '@components/SelectionList/UserListItem'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; +import useFastSearchFromOptions from '@hooks/useFastSearchFromOptions'; import useLocalize from '@hooks/useLocalize'; import usePolicy from '@hooks/usePolicy'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -179,7 +180,7 @@ function SearchRouterList( if (currentUser) { autocompleteOptions.push({ name: currentUser.displayName ?? Str.removeSMSDomain(currentUser.login ?? ''), - accountID: currentUser.accountID?.toString() ?? '-1', + accountID: currentUser.accountID?.toString(), }); } @@ -382,21 +383,30 @@ function SearchRouterList( }; }); + /** + * Builds a suffix tree and returns a function to search in it. + */ + const filterOptions = useFastSearchFromOptions(searchOptions, {includeUserToInvite: true}); + const recentReportsOptions = useMemo(() => { if (autocompleteQueryValue.trim() === '') { return searchOptions.recentReports.slice(0, 20); } Timing.start(CONST.TIMING.SEARCH_FILTER_OPTIONS); - const filteredOptions = OptionsListUtils.filterAndOrderOptions(searchOptions, autocompleteQueryValue, {sortByReportTypeInSearch: true, preferChatroomsOverThreads: true}); + const filteredOptions = filterOptions(autocompleteQueryValue); + const orderedOptions = OptionsListUtils.combineOrderingOfReportsAndPersonalDetails(filteredOptions, autocompleteQueryValue, { + sortByReportTypeInSearch: true, + preferChatroomsOverThreads: true, + }); Timing.end(CONST.TIMING.SEARCH_FILTER_OPTIONS); - const reportOptions: OptionData[] = [...filteredOptions.recentReports, ...filteredOptions.personalDetails]; + const reportOptions: OptionData[] = [...orderedOptions.recentReports, ...orderedOptions.personalDetails]; if (filteredOptions.userToInvite) { reportOptions.push(filteredOptions.userToInvite); } return reportOptions.slice(0, 20); - }, [autocompleteQueryValue, searchOptions]); + }, [autocompleteQueryValue, filterOptions, searchOptions]); useEffect(() => { ReportUserActions.searchInServer(autocompleteQueryValue.trim()); diff --git a/src/components/TabSelector/TabSelector.tsx b/src/components/TabSelector/TabSelector.tsx index b05e633842b1..16c29c7f51c9 100644 --- a/src/components/TabSelector/TabSelector.tsx +++ b/src/components/TabSelector/TabSelector.tsx @@ -28,16 +28,6 @@ type IconAndTitle = { function getIconAndTitle(route: string, translate: LocaleContextProps['translate']): IconAndTitle { switch (route) { - case CONST.DEBUG.DETAILS: - return {icon: Expensicons.Info, title: translate('debug.details')}; - case CONST.DEBUG.JSON: - return {icon: Expensicons.Eye, title: translate('debug.JSON')}; - case CONST.DEBUG.REPORT_ACTIONS: - return {icon: Expensicons.Document, title: translate('debug.reportActions')}; - case CONST.DEBUG.REPORT_ACTION_PREVIEW: - return {icon: Expensicons.Document, title: translate('debug.reportActionPreview')}; - case CONST.DEBUG.TRANSACTION_VIOLATIONS: - return {icon: Expensicons.Exclamation, title: translate('debug.violations')}; case CONST.TAB_REQUEST.MANUAL: return {icon: Expensicons.Pencil, title: translate('tabSelector.manual')}; case CONST.TAB_REQUEST.SCAN: diff --git a/src/components/TabSelector/getBackground/index.native.ts b/src/components/TabSelector/getBackground/index.native.ts index 09a9b3f347e6..2fd2a2ef6dd3 100644 --- a/src/components/TabSelector/getBackground/index.native.ts +++ b/src/components/TabSelector/getBackground/index.native.ts @@ -1,15 +1,20 @@ import type {Animated} from 'react-native'; import type GetBackgroudColor from './types'; -const getBackgroundColor: GetBackgroudColor = ({routesLength, tabIndex, affectedTabs, theme, position}) => { +const getBackgroundColor: GetBackgroudColor = ({routesLength, tabIndex, affectedTabs, theme, position, isActive}) => { if (routesLength > 1) { const inputRange = Array.from({length: routesLength}, (v, i) => i); - return position?.interpolate({ - inputRange, - outputRange: inputRange.map((i) => { - return affectedTabs.includes(tabIndex) && i === tabIndex ? theme.border : theme.appBG; - }), - }) as unknown as Animated.AnimatedInterpolation; + + if (position) { + return position.interpolate({ + inputRange, + outputRange: inputRange.map((i) => { + return affectedTabs.includes(tabIndex) && i === tabIndex ? theme.border : theme.appBG; + }), + }) as unknown as Animated.AnimatedInterpolation; + } + + return affectedTabs.includes(tabIndex) && isActive ? theme.border : theme.appBG; } return theme.border; }; diff --git a/src/components/TabSelector/getBackground/types.ts b/src/components/TabSelector/getBackground/types.ts index f66ee37e9b73..a207c3bab35e 100644 --- a/src/components/TabSelector/getBackground/types.ts +++ b/src/components/TabSelector/getBackground/types.ts @@ -28,7 +28,7 @@ type GetBackgroudColorConfig = { /** * The animated position interpolation. */ - position: Animated.AnimatedInterpolation; + position: Animated.AnimatedInterpolation | undefined; /** * Whether the tab is active. diff --git a/src/components/TabSelector/getOpacity/index.native.ts b/src/components/TabSelector/getOpacity/index.native.ts index a59d32c2db6e..fcdb1d0fc31e 100644 --- a/src/components/TabSelector/getOpacity/index.native.ts +++ b/src/components/TabSelector/getOpacity/index.native.ts @@ -1,16 +1,20 @@ import type GetOpacity from './types'; -const getOpacity: GetOpacity = ({routesLength, tabIndex, active, affectedTabs, position}) => { +const getOpacity: GetOpacity = ({routesLength, tabIndex, active, affectedTabs, position, isActive}) => { const activeValue = active ? 1 : 0; const inactiveValue = active ? 0 : 1; if (routesLength > 1) { const inputRange = Array.from({length: routesLength}, (v, i) => i); - return position?.interpolate({ - inputRange, - outputRange: inputRange.map((i) => (affectedTabs.includes(tabIndex) && i === tabIndex ? activeValue : inactiveValue)), - }); + if (position) { + return position.interpolate({ + inputRange, + outputRange: inputRange.map((i) => (affectedTabs.includes(tabIndex) && i === tabIndex ? activeValue : inactiveValue)), + }); + } + + return affectedTabs.includes(tabIndex) && isActive ? activeValue : inactiveValue; } return activeValue; }; diff --git a/src/components/TabSelector/getOpacity/types.ts b/src/components/TabSelector/getOpacity/types.ts index 46e4568b2783..a15eacf0d8cc 100644 --- a/src/components/TabSelector/getOpacity/types.ts +++ b/src/components/TabSelector/getOpacity/types.ts @@ -27,7 +27,7 @@ type GetOpacityConfig = { /** * Scene's position, value which we would like to interpolate. */ - position: Animated.AnimatedInterpolation; + position: Animated.AnimatedInterpolation | undefined; /** * Whether the tab is active. diff --git a/src/components/TaskHeaderActionButton.tsx b/src/components/TaskHeaderActionButton.tsx index 3841d559311f..dd2ba54c2f6d 100644 --- a/src/components/TaskHeaderActionButton.tsx +++ b/src/components/TaskHeaderActionButton.tsx @@ -1,30 +1,25 @@ import React from 'react'; import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportUtils from '@libs/ReportUtils'; import * as TaskUtils from '@libs/TaskUtils'; import * as Session from '@userActions/Session'; import * as Task from '@userActions/Task'; -import ONYXKEYS from '@src/ONYXKEYS'; +import CONST from '@src/CONST'; import type * as OnyxTypes from '@src/types/onyx'; import Button from './Button'; +import {useSession} from './OnyxProvider'; -type TaskHeaderActionButtonOnyxProps = { - /** Current user session */ - session: OnyxEntry; -}; - -type TaskHeaderActionButtonProps = TaskHeaderActionButtonOnyxProps & { +type TaskHeaderActionButtonProps = { /** The report currently being looked at */ report: OnyxTypes.Report; }; -function TaskHeaderActionButton({report, session}: TaskHeaderActionButtonProps) { +function TaskHeaderActionButton({report}: TaskHeaderActionButtonProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); + const session = useSession(); if (!ReportUtils.canWriteInReport(report)) { return null; @@ -34,7 +29,7 @@ function TaskHeaderActionButton({report, session}: TaskHeaderActionButtonProps)