diff --git a/.eslintrc.js b/.eslintrc.js
index c1b6a9676052..c8a842fa4650 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -78,7 +78,7 @@ const restrictedImportPatterns = [
module.exports = {
extends: ['expensify', 'plugin:storybook/recommended', 'plugin:react-native-a11y/basic', 'plugin:@dword-design/import-alias/recommended', 'prettier'],
- plugins: ['react-native-a11y'],
+ plugins: ['react-native-a11y', 'testing-library'],
parser: 'babel-eslint',
ignorePatterns: ['!.*', 'src/vendor', '.github/actions/**/index.js', 'desktop/dist/*.js', 'dist/*.js', 'node_modules/.bin/**', 'node_modules/.cache/**', '.git/**'],
env: {
@@ -130,6 +130,20 @@ module.exports = {
files: ['tests/**/*.js', 'tests/**/*.ts', 'tests/**/*.jsx', 'assets/**/*.js', '.storybook/**/*.js'],
rules: {'@dword-design/import-alias/prefer-alias': ['off']},
},
+ {
+ files: ['tests/**/*.js', 'tests/**/*.ts', 'tests/**/*.jsx', 'tests/**/*.tsx'],
+ extends: ['plugin:testing-library/react'],
+ rules: {
+ 'testing-library/await-async-queries': 'error',
+ 'testing-library/await-async-utils': 'error',
+ 'testing-library/no-debugging-utils': 'error',
+ 'testing-library/no-manual-cleanup': 'error',
+ 'testing-library/no-unnecessary-act': 'error',
+ 'testing-library/prefer-find-by': 'error',
+ 'testing-library/prefer-presence-queries': 'error',
+ 'testing-library/prefer-screen-queries': 'error',
+ },
+ },
{
files: ['*.js', '*.jsx'],
settings: {
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 98e20bf15e5d..b50980e2ab9d 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -98,8 +98,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001046900
- versionName "1.4.69-0"
+ versionCode 1001046902
+ versionName "1.4.69-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/docs/articles/new-expensify/chat/Send-and-format-chat-messages.md b/docs/articles/new-expensify/chat/Send-and-format-chat-messages.md
index edef142a80bf..2aa2b026fdd6 100644
--- a/docs/articles/new-expensify/chat/Send-and-format-chat-messages.md
+++ b/docs/articles/new-expensify/chat/Send-and-format-chat-messages.md
@@ -42,6 +42,9 @@ You can format the text in a chat message using markdown.
- **Bold**: Add two asterisks ** on both sides of the text.
- ~~Strikethrough~~: Add two tildes ~~ on both sides of the text.
- Heading: Add a number sign # in front of the text.
+- Inline image: Add `![Alt text](image URL)` and add the URL to the image and alt text that describes the image.
+- Tag another member: Add an at symbol @ and enter the member's name, username, or email address.
+- Mention a room: Add a number sign # and enter the room name
- > Blockquote: Add an angled bracket > in front of the text.
- `Code block for a small amount of text`: Add a backtick ` on both sides of the text.
- Code block for the entire message: Add three backticks ``` at the beginning and the end of the message.
diff --git a/docs/articles/new-expensify/expenses/Create-an-expense.md b/docs/articles/new-expensify/expenses/Create-an-expense.md
new file mode 100644
index 000000000000..cf6a13f9d5ac
--- /dev/null
+++ b/docs/articles/new-expensify/expenses/Create-an-expense.md
@@ -0,0 +1,125 @@
+---
+title: Create an expense
+description: Request payment from an employer or a friend
+redirect_from: articles/request-money/Request-and-Split-Bills/
+---
+
+
+You can create an expense to request payment from an employer’s workspace or from a friend using any of the following options:
+- **SmartScan**: Take a picture of a receipt to capture the expense details automatically.
+- **Add manually**: Manually enter the expense details.
+- **Create a distance expense**: Capture mileage expenses by entering the addresses you traveled to. Expensify automatically calculates the distance, the rate per mile, and the total cost.
+
+# SmartScan a receipt
+
+{% include selector.html values="desktop, mobile" %}
+
+{% include option.html value="desktop" %}
+1. Click the + icon in the bottom left menu and select **Submit Expense**.
+2. Click **Scan**.
+3. Drag and drop the receipt into Expensify, or click **Choose File** to select it from your saved files. *Note: The SmartScan process will auto-populate the merchant, date, and amount.*
+4. Use the search field to find the desired workspace or an individual’s name, email, or phone number.
+5. Add a description, category, tags, or tax as desired, or as required by your workspace.
+6. (Optional) Enable the expense as billable if it should be billed to a client.
+7. Click **Submit Expense**.
+{% include end-option.html %}
+
+{% include option.html value="mobile" %}
+1. Tap the + icon at the bottom of the screen and select **Submit Expense**.
+2. Tap **Scan**.
+3. Tap the green button to take a photo of a receipt, or tap the Image icon to the left of it to upload a receipt from your phone. *Note: The SmartScan process will auto-populate the merchant, date, and amount.*
+4. Use the search field to find the desired workspace or an individual’s name, email, or phone number.
+5. Add a description, category, tags, or tax as desired, or as required by your workspace.
+6. (Optional) Enable the expense as billable if it should be billed to a client.
+7. Tap **Submit**.
+{% include end-option.html %}
+
+{% include end-selector.html %}
+
+{% include info.html %}
+You can also forward receipts to receipts@expensify.com using an email address that is your primary or secondary email address. SmartScan will automatically pull all of the details from the receipt and add it to your expenses.
+{% include end-info.html %}
+
+# Manually add an expense
+
+{% include selector.html values="desktop, mobile" %}
+
+{% include option.html value="desktop" %}
+1. Click the + icon in the bottom left menu and select **Submit Expense**.
+2. Click **Manual**.
+3. Enter the amount on the receipt and click **Next**. *Note: Click the currency symbol to select a different currency.*
+4. Use the search field to find the desired workspace or an individual’s name, email, or phone number.
+5. (Optional) Add a description.
+6. Add a merchant.
+7. Click **Show more** to add additional fields (like a category) as desired, or as required by your workspace.
+8. Click **Submit**.
+{% include end-option.html %}
+
+{% include option.html value="mobile" %}
+1. Tap the + icon at the bottom of the screen and select **Submit Expense**.
+2. Tap **Manual**.
+3. Enter the amount on the receipt and tap **Next**. *Note: Click the currency symbol to select a different currency.*
+4. Use the search field to find the desired workspace or an individual’s name, email, or phone number.
+5. (Optional) Add a description.
+6. Add a merchant.
+7. Tap **Show more** to add additional fields (like a category) as desired, or as required by your workspace.
+8. Tap **Submit**.
+{% include end-option.html %}
+
+{% include end-selector.html %}
+
+# Create a distance expense
+
+{% include selector.html values="desktop, mobile" %}
+
+{% include option.html value="desktop" %}
+1. Click the + icon in the bottom left menu and select **Submit Expense**.
+2. Click **Distance**.
+3. Click **Start** and enter the starting location of your trip.
+4. Click **Stop** and enter the ending location of your trip.
+5. (Optional) Click **Add stop** to add additional stops, if applicable.
+6. Tap **Next**.
+7. Use the search field to find the desired workspace or an individual’s name, email, or phone number.
+8. (Optional) Add a description.
+9. Click **Submit**.
+{% include end-option.html %}
+
+{% include option.html value="mobile" %}
+1. Tap the + icon at the bottom of the screen and select **Submit Expense**.
+2. Tap **Distance**.
+3. Tap **Start** and enter the starting location of your trip.
+4. Tap **Stop** and enter the ending location of your trip.
+5. (Optional) Tap **Add stop** to add additional stops, if applicable.
+6. Tap **Next**.
+7. Use the search field to find the desired workspace or an individual’s name, email, or phone number.
+8.(Optional) Add a description.
+9. Tap **Submit**.
+{% include end-option.html %}
+
+{% include end-selector.html %}
+
+# Next Steps
+
+The next steps for the expense depend on whether it was submitted to a workspace or to an individual:
+- **Expenses submitted to a workspace** are automatically added to a report and checked for any violations or inconsistencies. A chat thread for the expense is also added to your chat inbox. When you open the chat, the top banner will show the expense status and any next steps. By default, reports are automatically submitted for approval every Sunday. However, if it is ready for early submission, you can manually submit a report for approval. Once a report is submitted, your approver will be prompted to review your expense report. If changes are required, you will receive a notification to resolve any violations and resubmit. You will also be notified once your approver approves or denies your expenses.
+- **Expenses submitted to a friend** are sent right to that individual via email or text. You can chat with them about the expense in Expensify Chat, and you can receive payments through your Expensify Wallet or outside of Expensify.
+
+{% include faq-begin.md %}
+**Can I divide a payment between multiple people?**
+
+Yes, you can split an expense to share the cost between multiple people.
+
+**Can I pay someone in another currency?**
+
+While you can record your expenses in different currencies, Expensify wallets are only available for members who can add a U.S. personal bank account.
+
+**Can I change an expense once I’ve submitted it?**
+
+Yes, you can edit an expense until it is paid. When an expense is submitted to a workspace, you, your approvers, and admins can edit the details on an expense except for the amount and date.
+
+**What are expense reports?**
+
+In Expensify, expenses are submitted on an expense report. When a draft report is open, all new expenses are added to the draft report. Once a report is submitted, it shows what stage of the approval process the expenses are in and any required next steps.
+{% include faq-end.md %}
+
+
diff --git a/docs/articles/new-expensify/expenses/Manually-submit-reports-for-approval.md b/docs/articles/new-expensify/expenses/Manually-submit-reports-for-approval.md
new file mode 100644
index 000000000000..0bfae655835f
--- /dev/null
+++ b/docs/articles/new-expensify/expenses/Manually-submit-reports-for-approval.md
@@ -0,0 +1,43 @@
+---
+title: Manually submit reports for approval
+description: Submit a report before the submission date
+---
+
+
+By default, reports are automatically submitted for approval every Sunday. However, if it is ready for early submission, you can manually submit your report for approval.
+
+To manually submit an expense for approval,
+
+{% include selector.html values="desktop, mobile" %}
+
+{% include option.html value="desktop" %}
+1. In your chat inbox, click the expense you want to submit for approval. *Note: A green dot will appear to the right of newly created expenses.*
+2. Review the next step provided at the top of the expense report.
+3. If the next step is to submit the report, click **Submit**.
+{% include end-option.html %}
+
+{% include option.html value="mobile" %}
+1. In your chat inbox, tap the expense you want to submit for approval. *Note: A green dot will appear to the right of newly created expenses.*
+2. Review the next step provided at the top of the expense report.
+3. If the next step is to submit the report, tap **Submit**.
+{% include end-option.html %}
+
+{% include end-selector.html %}
+
+{% include faq-begin.md %}
+**How do I know the status of my expense report?**
+
+You’ll see a Next Steps prompt at the top of your expense report to guide you through the next steps and give you a status of your report. Your workspace may have an automation set up that will automatically submit your expense reports when they are due, or you may be required to manually submit your expenses.
+
+**Can I add more expenses to my expense report?**
+
+Yes, you can add expenses to a report that is in a Draft or Processing status. Once a report is Approved or Reimbursed, expenses cannot be added or edited.
+
+**How can I get reimbursed?**
+
+The reimbursement options depend on the workspace’s settings.
+- If the workspace is set up to send reimbursements directly to your personal bank account, you can connect a personal bank account to receive ACH payments.
+- You can receive reimbursements with your Expensify Wallet or outside of Expensify.
+{% include faq-end.md %}
+
+
diff --git a/docs/articles/new-expensify/expenses/Request-Money.md b/docs/articles/new-expensify/expenses/Request-Money.md
deleted file mode 100644
index 9aac4787484c..000000000000
--- a/docs/articles/new-expensify/expenses/Request-Money.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: Request Money and Split Bills with Friends
-description: Everything you need to know about Requesting Money and Splitting Bills with Friends!
-redirect_from: articles/request-money/Request-and-Split-Bills/
----
-
-
-
-# How do these Payment Features work?
-Our suite of money movement features enables you to request money owed by an individual or split a bill with a group.
-
-**Request Money** lets your friends pay you back directly in Expensify. When you send a payment request to a friend, Expensify will display the amount owed and the option to pay the corresponding request in a chat between you.
-
-**Split Bill** allows you to split payments between friends and ensures the person who settled the tab gets paid back.
-
-These two features ensure you can live in the moment and settle up afterward.
-
-# How to Request Money
-- Select the Green **+** button and choose **Request Money**
-- Enter the amount **$** they owe and click **Next**
-- Search for the user or enter their email!
-- Enter a reason for the request (optional)
-- Click **Request!**
-- If you change your mind, all you have to do is click **Cancel**
-- The user will be able to **Settle up outside of Expensify** or pay you via **Venmo** or **PayPal.me**
-
-# How to Split a Bill
-- Select the Green **+** button and choose **Split Bill**
-- Enter the total amount for the bill and click **Next**
-- Search for users or enter their emails and **Select**
-- Enter a reason for the split
-- The split is then shared equally between the attendees
-
-{% include faq-begin.md %}
-## Can I request money from more than one person at a time?
-If you need to request money for more than one person at a time, you’ll want to use the Split Bill feature. The Request Money option is for one-to-one payments between two people.
-{% include faq-end.md %}
diff --git a/docs/articles/new-expensify/expenses/Split-an-expense.md b/docs/articles/new-expensify/expenses/Split-an-expense.md
new file mode 100644
index 000000000000..d411469fc298
--- /dev/null
+++ b/docs/articles/new-expensify/expenses/Split-an-expense.md
@@ -0,0 +1,39 @@
+---
+title: Split an expense
+description: Divide an expense between multiple people
+---
+
+
+Splitting an expense allows the person who paid the bill to request money from multiple people who will split the cost with them.
+
+To split an expense with other people,
+
+{% include selector.html values="desktop, mobile" %}
+
+{% include option.html value="desktop" %}
+1. Click the + icon in the bottom left menu and select **Split Expense**.
+2. Upload a photo of your receipt, or manually enter the total bill amount.
+3. Click **Next**.
+4. Enter the names, email addresses, or phone numbers for the people you want to request money from. *Note: You can select multiple people.*
+5. Click **Next**.
+6. (Optional) Enter a reason for the request in the Description field.
+7. (Optional) If you manually entered the bill amount, add the merchant and date of purchase.
+8. Click **Split**.
+{% include end-option.html %}
+
+{% include option.html value="mobile" %}
+1. Tap the + icon at the bottom of the screen and select **Split Expense**.
+2. Take a photo of your receipt, or manually enter the total bill amount.
+3. Tap **Next**.
+4. Enter the names, email addresses, or phone numbers for the people you want to request money from. Note: You can select multiple people
+5. Tap **Next**.
+6. (Optional) Enter a reason for the request in the Description field.
+7. (Optional) If you manually entered the bill amount, add the merchant and date of purchase.
+8. Click **Split**.
+{% include end-option.html %}
+
+{% include end-selector.html %}
+
+Each person will receive an email or text with the details of the request. You can also chat with them about the expense in Expensify Chat, and you can receive payments through your Expensify Wallet or outside of Expensify.
+
+
diff --git a/docs/redirects.csv b/docs/redirects.csv
index 0536a9c0e6d4..c887b649b32d 100644
--- a/docs/redirects.csv
+++ b/docs/redirects.csv
@@ -67,6 +67,7 @@ https://help.expensify.com/articles/expensify-classic/settings/Merge-Accounts,ht
https://help.expensify.com/articles/expensify-classic/settings/Preferences,https://help.expensify.com/expensify-classic/hubs/settings/account-settings
https://help.expensify.com/articles/expensify-classic/getting-started/support/Your-Expensify-Account-Manager,https://use.expensify.com/support
https://help.expensify.com/articles/expensify-classic/settings/Copilot,https://help.expensify.com/expensify-classic/hubs/copilots-and-delegates/
+https://help.expensify.com/articles/expensify-classic/workspaces/Domains-Overview,https://help.expensify.com/expensify-classic/hubs/domains/
https://help.expensify.com/articles/expensify-classic/workspace-and-domain-settings/reports/Currency,https://help.expensify.com/articles/expensify-classic/reports/Currency
https://help.expensify.com/articles/expensify-classic/workspace-and-domain-settings/reports/Report-Fields-And-Titles,https://help.expensify.com/articles/expensify-classic/workspaces/reports/Report-Fields-And-Titles
https://help.expensify.com/articles/expensify-classic/workspace-and-domain-settings/reports/Scheduled-Submit,https://help.expensify.com/articles/expensify-classic/workspaces/reports/Scheduled-Submit
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 7f6bd89d8e9b..f9bc9e157b58 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.4.69.0
+ 1.4.69.2
FullStory
OrgId
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 0fd56ef8b8db..db5882644859 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 1.4.69.0
+ 1.4.69.2
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index cc6b4edca693..cd69c5ce7b62 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -13,7 +13,7 @@
CFBundleShortVersionString
1.4.69
CFBundleVersion
- 1.4.69.0
+ 1.4.69.2
NSExtension
NSExtensionPointIdentifier
diff --git a/package-lock.json b/package-lock.json
index 6a3ae05cae0f..5962d3b29504 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.4.69-0",
+ "version": "1.4.69-2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.4.69-0",
+ "version": "1.4.69-2",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -100,7 +100,7 @@
"react-native-linear-gradient": "^2.8.1",
"react-native-localize": "^2.2.6",
"react-native-modal": "^13.0.0",
- "react-native-onyx": "2.0.27",
+ "react-native-onyx": "^2.0.35",
"react-native-pager-view": "6.2.3",
"react-native-pdf": "6.7.3",
"react-native-performance": "^5.1.0",
@@ -125,7 +125,7 @@
"react-native-web-linear-gradient": "^1.1.2",
"react-native-web-sound": "^0.1.3",
"react-native-webview": "13.6.4",
- "react-pdf": "7.3.3",
+ "react-pdf": "^7.7.0",
"react-plaid-link": "3.3.2",
"react-web-config": "^1.0.0",
"react-webcam": "^7.1.1",
@@ -213,6 +213,7 @@
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-react-native-a11y": "^3.3.0",
"eslint-plugin-storybook": "^0.8.0",
+ "eslint-plugin-testing-library": "^6.2.2",
"eslint-plugin-you-dont-need-lodash-underscore": "^6.12.0",
"html-webpack-plugin": "^5.5.0",
"jest": "29.4.1",
@@ -14286,6 +14287,15 @@
"sprintf-js": "~1.0.2"
}
},
+ "node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
"node_modules/arr-diff": {
"version": "4.0.0",
"license": "MIT",
@@ -19600,14 +19610,6 @@
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
}
},
- "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": {
- "version": "5.3.0",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "dequal": "^2.0.3"
- }
- },
"node_modules/eslint-plugin-jsx-a11y/node_modules/axobject-query": {
"version": "3.2.1",
"dev": true,
@@ -19786,6 +19788,22 @@
"lodash": "^4.17.15"
}
},
+ "node_modules/eslint-plugin-testing-library": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-6.2.2.tgz",
+ "integrity": "sha512-1E94YOTUDnOjSLyvOwmbVDzQi/WkKm3WVrMXu6SmBr6DN95xTGZmI6HJ/eOkSXh/DlheRsxaPsJvZByDBhWLVQ==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/utils": "^5.58.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0",
+ "npm": ">=6"
+ },
+ "peerDependencies": {
+ "eslint": "^7.5.0 || ^8.0.0"
+ }
+ },
"node_modules/eslint-plugin-you-dont-need-lodash-underscore": {
"version": "6.12.0",
"dev": true,
@@ -29977,7 +29995,9 @@
},
"node_modules/path2d-polyfill": {
"version": "2.0.1",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz",
+ "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==",
+ "optional": true,
"engines": {
"node": ">=8"
}
@@ -30013,17 +30033,15 @@
}
},
"node_modules/pdfjs-dist": {
- "version": "3.6.172",
- "license": "Apache-2.0",
- "dependencies": {
- "path2d-polyfill": "^2.0.1",
- "web-streams-polyfill": "^3.2.1"
- },
+ "version": "3.11.174",
+ "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz",
+ "integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==",
"engines": {
- "node": ">=16"
+ "node": ">=18"
},
"optionalDependencies": {
- "canvas": "^2.11.2"
+ "canvas": "^2.11.2",
+ "path2d-polyfill": "^2.0.1"
}
},
"node_modules/peek-stream": {
@@ -31058,55 +31076,6 @@
"react-dom": "18.x"
}
},
- "node_modules/react-fast-pdf/node_modules/pdfjs-dist": {
- "version": "3.11.174",
- "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz",
- "integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==",
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "canvas": "^2.11.2",
- "path2d-polyfill": "^2.0.1"
- }
- },
- "node_modules/react-fast-pdf/node_modules/react-pdf": {
- "version": "7.7.1",
- "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-7.7.1.tgz",
- "integrity": "sha512-cbbf/PuRtGcPPw+HLhMI1f6NSka8OJgg+j/yPWTe95Owf0fK6gmVY7OXpTxMeh92O3T3K3EzfE0ML0eXPGwR5g==",
- "dependencies": {
- "clsx": "^2.0.0",
- "dequal": "^2.0.3",
- "make-cancellable-promise": "^1.3.1",
- "make-event-props": "^1.6.0",
- "merge-refs": "^1.2.1",
- "pdfjs-dist": "3.11.174",
- "prop-types": "^15.6.2",
- "tiny-invariant": "^1.0.0",
- "warning": "^4.0.0"
- },
- "funding": {
- "url": "https://github.com/wojtekmaj/react-pdf?sponsor=1"
- },
- "peerDependencies": {
- "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
- "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/react-fast-pdf/node_modules/warning": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
- "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
- "dependencies": {
- "loose-envify": "^1.0.0"
- }
- },
"node_modules/react-freeze": {
"version": "1.0.3",
"license": "MIT",
@@ -31461,9 +31430,9 @@
}
},
"node_modules/react-native-onyx": {
- "version": "2.0.27",
- "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.27.tgz",
- "integrity": "sha512-mNtXmJ2r7UwEym2J7Tu09M42QoxIhwEdiGYDw9v26wp/kQCJChKTP0yUrp8QdPKkcwywRFPVlNxt3Rx8Mp0hFg==",
+ "version": "2.0.35",
+ "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.35.tgz",
+ "integrity": "sha512-eQwXQoYpv6Wv1sDrR2Otl4mW34U8OZPtlpju3OyGv1KpQSQ+2q8Ivju7AHc3DG+j2QHypUKngQghKdJ9Sm3jBQ==",
"dependencies": {
"ascii-table": "0.0.9",
"fast-equals": "^4.0.3",
@@ -31989,17 +31958,19 @@
}
},
"node_modules/react-pdf": {
- "version": "7.3.3",
- "license": "MIT",
+ "version": "7.7.1",
+ "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-7.7.1.tgz",
+ "integrity": "sha512-cbbf/PuRtGcPPw+HLhMI1f6NSka8OJgg+j/yPWTe95Owf0fK6gmVY7OXpTxMeh92O3T3K3EzfE0ML0eXPGwR5g==",
"dependencies": {
"clsx": "^2.0.0",
+ "dequal": "^2.0.3",
"make-cancellable-promise": "^1.3.1",
"make-event-props": "^1.6.0",
"merge-refs": "^1.2.1",
- "pdfjs-dist": "3.6.172",
+ "pdfjs-dist": "3.11.174",
"prop-types": "^15.6.2",
"tiny-invariant": "^1.0.0",
- "tiny-warning": "^1.0.0"
+ "warning": "^4.0.0"
},
"funding": {
"url": "https://github.com/wojtekmaj/react-pdf?sponsor=1"
@@ -35578,10 +35549,6 @@
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
},
- "node_modules/tiny-warning": {
- "version": "1.0.3",
- "license": "MIT"
- },
"node_modules/tinycolor2": {
"version": "1.6.0",
"dev": true,
@@ -36632,6 +36599,14 @@
"version": "0.1.1",
"license": "MIT"
},
+ "node_modules/warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"node_modules/watchpack": {
"version": "2.4.0",
"license": "MIT",
@@ -36901,13 +36876,6 @@
"defaults": "^1.0.3"
}
},
- "node_modules/web-streams-polyfill": {
- "version": "3.2.1",
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
"node_modules/webidl-conversions": {
"version": "7.0.0",
"license": "BSD-2-Clause",
diff --git a/package.json b/package.json
index 353def8d03e0..d8d2e105d0c6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.4.69-0",
+ "version": "1.4.69-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.",
@@ -152,7 +152,7 @@
"react-native-linear-gradient": "^2.8.1",
"react-native-localize": "^2.2.6",
"react-native-modal": "^13.0.0",
- "react-native-onyx": "2.0.27",
+ "react-native-onyx": "2.0.35",
"react-native-pager-view": "6.2.3",
"react-native-pdf": "6.7.3",
"react-native-performance": "^5.1.0",
@@ -177,7 +177,7 @@
"react-native-web-linear-gradient": "^1.1.2",
"react-native-web-sound": "^0.1.3",
"react-native-webview": "13.6.4",
- "react-pdf": "7.3.3",
+ "react-pdf": "^7.7.0",
"react-plaid-link": "3.3.2",
"react-web-config": "^1.0.0",
"react-webcam": "^7.1.1",
@@ -265,6 +265,7 @@
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-react-native-a11y": "^3.3.0",
"eslint-plugin-storybook": "^0.8.0",
+ "eslint-plugin-testing-library": "^6.2.2",
"eslint-plugin-you-dont-need-lodash-underscore": "^6.12.0",
"html-webpack-plugin": "^5.5.0",
"jest": "29.4.1",
diff --git a/patches/react-pdf+7.3.3.patch b/patches/react-pdf+7.7.1.patch
similarity index 81%
rename from patches/react-pdf+7.3.3.patch
rename to patches/react-pdf+7.7.1.patch
index 6b3b4be22b6e..f6ec8d8c1685 100644
--- a/patches/react-pdf+7.3.3.patch
+++ b/patches/react-pdf+7.7.1.patch
@@ -1,8 +1,8 @@
diff --git a/node_modules/react-pdf/dist/esm/Document.js b/node_modules/react-pdf/dist/esm/Document.js
-index ac7ca31..56bc766 100644
+index 493ff15..8d5e734 100644
--- a/node_modules/react-pdf/dist/esm/Document.js
+++ b/node_modules/react-pdf/dist/esm/Document.js
-@@ -240,6 +240,7 @@ const Document = forwardRef(function Document(_a, ref) {
+@@ -261,6 +261,7 @@ const Document = forwardRef(function Document(_a, ref) {
pdfDispatch({ type: 'REJECT', error });
});
return () => {
diff --git a/src/CONST.ts b/src/CONST.ts
index 9d3042b64f80..afe9bdd1114e 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -1249,7 +1249,7 @@ const CONST = {
ENABLE_NEW_CATEGORIES: 'enableNewCategories',
SYNC_CUSTOMERS: 'syncCustomers',
SYNC_LOCATIONS: 'syncLocations',
- SYNC_TAXES: 'syncTaxes',
+ SYNC_TAX: 'syncTax',
PREFERRED_EXPORTER: 'exporter',
EXPORT_DATE: 'exportDate',
OUT_OF_POCKET_EXPENSES: 'outOfPocketExpenses',
@@ -1260,7 +1260,7 @@ const CONST = {
EXPORT_COMPANY_CARD_ACCOUNT: 'exportCompanyCardAccount',
EXPORT_COMPANY_CARD: 'exportCompanyCard',
AUTO_SYNC: 'autoSync',
- SYNCE_PEOPLE: 'syncPeople',
+ SYNC_PEOPLE: 'syncPeople',
AUTO_CREATE_VENDOR: 'autoCreateVendor',
REIMBURSEMENT_ACCOUNT_ID: 'reimbursementAccountID',
COLLECTION_ACCOUNT_ID: 'collectionAccountID',
@@ -1270,10 +1270,10 @@ const CONST = {
IMPORT_TAX_RATES: 'importTaxRates',
},
- QUICKBOOKS_EXPORT_ENTITY: {
- VENDOR_BILL: 'vendorBill',
+ QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE: {
+ VENDOR_BILL: 'bill',
CHECK: 'check',
- JOURNAL_ENTRY: 'journalEntry',
+ JOURNAL_ENTRY: 'journal_entry',
},
ACCOUNT_ID: {
@@ -3722,12 +3722,12 @@ const CONST = {
autoCompleted: false,
title: 'Submit an expense',
description:
- 'Submit an expense by entering an amount or scanning a receipt.\n' +
+ '*Submit an expense* by entering an amount or scanning a receipt.\n' +
'\n' +
'Here’s how to submit an expense:\n' +
'\n' +
- '1. Click the green + button.\n' +
- '2. Choose Submit expense.\n' +
+ '1. Click the green *+* button.\n' +
+ '2. Choose *Submit expense*.\n' +
'3. Enter an amount or scan a receipt.\n' +
'4. Add your reimburser to the request.\n' +
'\n' +
@@ -3738,12 +3738,12 @@ const CONST = {
autoCompleted: false,
title: 'Enable your wallet',
description:
- 'You’ll need to enable your Expensify Wallet to get paid back. Don’t worry, it’s easy!\n' +
+ 'You’ll need to *enable your Expensify Wallet* to get paid back. Don’t worry, it’s easy!\n' +
'\n' +
'Here’s how to set up your wallet:\n' +
'\n' +
'1. Click your profile picture.\n' +
- '2. Click Wallet > Enable wallet.\n' +
+ '2. Click *Wallet* > *Enable wallet*.\n' +
'3. Connect your bank account.\n' +
'\n' +
'Once that’s done, you can request money from anyone and get paid back right into your personal bank account.',
@@ -3765,14 +3765,14 @@ const CONST = {
autoCompleted: true,
title: 'Create a workspace',
description:
- 'Create a workspace to track expenses, scan receipts, chat, and more.\n' +
+ '*Create a workspace* to track expenses, scan receipts, chat, and more.\n' +
'\n' +
'Here’s how to create a workspace:\n' +
'\n' +
'1. Click your profile picture.\n' +
- '2. Click Workspaces > New workspace.\n' +
+ '2. Click *Workspaces* > *New workspace*.\n' +
'\n' +
- 'Your new workspace is ready! It’ll keep all of your spend (and chats) in one place.',
+ '*Your new workspace is ready! It’ll keep all of your spend (and chats) in one place.*',
},
{
type: 'meetGuide',
@@ -3788,32 +3788,32 @@ const CONST = {
autoCompleted: false,
title: 'Set up categories',
description:
- 'Set up categories so your team can code expenses for easy reporting.\n' +
+ '*Set up categories* so your team can code expenses for easy reporting.\n' +
'\n' +
'Here’s how to set up categories:\n' +
'\n' +
'1. Click your profile picture.\n' +
- '2. Go to Workspaces > [your workspace].\n' +
- '3. Click Categories.\n' +
+ '2. Go to *Workspaces* > [your workspace].\n' +
+ '3. Click *Categories*.\n' +
'4. Enable and disable default categories.\n' +
- '5. Click Add categories to make your own.\n' +
+ '5. Click *Add categories* to make your own.\n' +
'\n' +
- 'For more controls like requiring a category for every expense, click Settings.',
+ 'For more controls like requiring a category for every expense, click *Settings*.',
},
{
type: 'addExpenseApprovals',
autoCompleted: false,
title: 'Add expense approvals',
description:
- 'Add expense approvals to review your team’s spend and keep it under control.\n' +
+ '*Add expense approvals* to review your team’s spend and keep it under control.\n' +
'\n' +
'Here’s how to add expense approvals:\n' +
'\n' +
'1. Click your profile picture.\n' +
- '2. Go to Workspaces > [your workspace].\n' +
- '3. Click More features.\n' +
- '4. Enable Workflows.\n' +
- '5. In Workflows, enable Add approvals.\n' +
+ '2. Go to *Workspaces* > [your workspace].\n' +
+ '3. Click *More features*.\n' +
+ '4. Enable *Workflows*.\n' +
+ '5. In *Workflows*, enable *Add approvals*.\n' +
'\n' +
'You’ll be set as the expense approver. You can change this to any admin once you invite your team.',
},
@@ -3822,13 +3822,13 @@ const CONST = {
autoCompleted: false,
title: 'Invite your team',
description:
- 'Invite your team to Expensify so they can start tracking expenses today.\n' +
+ '*Invite your team* to Expensify so they can start tracking expenses today.\n' +
'\n' +
'Here’s how to invite your team:\n' +
'\n' +
'1. Click your profile picture.\n' +
- '2. Go to Workspaces > [your workspace].\n' +
- '3. Click Members > Invite member.\n' +
+ '2. Go to *Workspaces* > [your workspace].\n' +
+ '3. Click *Members* > *Invite member*.\n' +
'4. Enter emails or phone numbers. \n' +
'5. Add an invite message if you want.\n' +
'\n' +
@@ -3851,14 +3851,14 @@ const CONST = {
autoCompleted: false,
title: 'Track an expense',
description:
- 'Track an expense in any currency, whether you have a receipt or not.\n' +
+ '*Track an expense* in any currency, whether you have a receipt or not.\n' +
'\n' +
'Here’s how to track an expense:\n' +
'\n' +
- '1. Click the green + button.\n' +
- '2. Choose Track expense.\n' +
+ '1. Click the green *+* button.\n' +
+ '2. Choose *Track expense*.\n' +
'3. Enter an amount or scan a receipt.\n' +
- '4. Click Track.\n' +
+ '4. Click *Track*.\n' +
'\n' +
'And you’re done! Yep, it’s that easy.',
},
@@ -3879,12 +3879,12 @@ const CONST = {
autoCompleted: false,
title: 'Start a chat',
description:
- 'Start a chat with a friend or group using their email or phone number.\n' +
+ '*Start a chat* with a friend or group using their email or phone number.\n' +
'\n' +
'Here’s how to start a chat:\n' +
'\n' +
- '1. Click the green + button.\n' +
- '2. Choose Start chat.\n' +
+ '1. Click the green *+* button.\n' +
+ '2. Choose *Start chat*.\n' +
'3. Enter emails or phone numbers.\n' +
'\n' +
'If any of your friends aren’t using Expensify already, they’ll be invited automatically.\n' +
@@ -3896,12 +3896,12 @@ const CONST = {
autoCompleted: false,
title: 'Split an expense',
description:
- 'Split an expense right in your chat with one or more friends.\n' +
+ '*Split an expense* right in your chat with one or more friends.\n' +
'\n' +
'Here’s how to request money:\n' +
'\n' +
- '1. Click the green + button.\n' +
- '2. Choose Split expense.\n' +
+ '1. Click the green *+* button.\n' +
+ '2. Choose *Split expense*.\n' +
'3. Scan a receipt or enter an amount.\n' +
'4. Add your friend(s) to the request.\n' +
'\n' +
@@ -3912,12 +3912,12 @@ const CONST = {
autoCompleted: false,
title: 'Enable your wallet',
description:
- 'You’ll need to enable your Expensify Wallet to get paid back. Don’t worry, it’s easy!\n' +
+ 'You’ll need to *enable your Expensify Wallet* to get paid back. Don’t worry, it’s easy!\n' +
'\n' +
'Here’s how to enable your wallet:\n' +
'\n' +
'1. Click your profile picture.\n' +
- '2. Click Wallet > Enable wallet.\n' +
+ '2. *Click Wallet* > *Enable wallet*.\n' +
'3. Add your bank account.\n' +
'\n' +
'Once that’s done, you can request money from anyone and get paid right into your personal bank account.',
@@ -4662,10 +4662,10 @@ const CONST = {
REPORT_SUBMITTED: 'REPORT_SUBMITTED',
},
- QUICKBOOKS_EXPORT_COMPANY_CARD: {
- CREDIT_CARD: 'creditCard',
- DEBIT_CARD: 'debitCard',
- VENDOR_BILL: 'vendorBill',
+ QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE: {
+ CREDIT_CARD: 'credit_card',
+ DEBIT_CARD: 'debit_card',
+ VENDOR_BILL: 'bill',
},
SESSION_STORAGE_KEYS: {
diff --git a/src/Expensify.tsx b/src/Expensify.tsx
index 6a57d6fdcc10..c67f1400fc4b 100644
--- a/src/Expensify.tsx
+++ b/src/Expensify.tsx
@@ -41,6 +41,8 @@ Onyx.registerLogger(({level, message}) => {
if (level === 'alert') {
Log.alert(message);
console.error(message);
+ } else if (level === 'hmmm') {
+ Log.hmmm(message);
} else {
Log.info(message);
}
diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx
index 17ee0d31d63c..dd3e207c45fa 100755
--- a/src/components/MoneyRequestConfirmationList.tsx
+++ b/src/components/MoneyRequestConfirmationList.tsx
@@ -1043,7 +1043,7 @@ function MoneyRequestConfirmationList({
key={translate('workspace.invoices.sendFrom')}
shouldShowRightIcon={!isReadOnly && canUpdateSenderWorkspace}
title={senderWorkspace?.name}
- icon={senderWorkspace?.avatar ? senderWorkspace?.avatar : getDefaultWorkspaceAvatar(senderWorkspace?.name)}
+ icon={senderWorkspace?.avatarURL ? senderWorkspace?.avatarURL : getDefaultWorkspaceAvatar(senderWorkspace?.name)}
iconType={CONST.ICON_TYPE_WORKSPACE}
description={translate('workspace.common.workspace')}
label={translate('workspace.invoices.sendFrom')}
diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx
index bd2d9f73ae2a..139b5fde58b2 100644
--- a/src/components/ReportActionItem/MoneyReportView.tsx
+++ b/src/components/ReportActionItem/MoneyReportView.tsx
@@ -7,7 +7,6 @@ import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
-import SpacerView from '@components/SpacerView';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
@@ -29,12 +28,9 @@ type MoneyReportViewProps = {
/** Policy that the report belongs to */
policy: OnyxEntry;
-
- /** Whether we should display the horizontal rule below the component */
- shouldShowHorizontalRule: boolean;
};
-function MoneyReportView({report, policy, shouldShowHorizontalRule}: MoneyReportViewProps) {
+function MoneyReportView({report, policy}: MoneyReportViewProps) {
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
@@ -168,10 +164,6 @@ function MoneyReportView({report, policy, shouldShowHorizontalRule}: MoneyReport
>
)}
-
>
)}
diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 1c821fc8965a..73bc7898c6df 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -9,7 +9,6 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback';
import {useSession} from '@components/OnyxProvider';
import {ReceiptAuditHeader, ReceiptAuditMessages} from '@components/ReceiptAudit';
import ReceiptEmptyState from '@components/ReceiptEmptyState';
-import SpacerView from '@components/SpacerView';
import Switch from '@components/Switch';
import Text from '@components/Text';
import ViolationMessages from '@components/ViolationMessages';
@@ -77,9 +76,6 @@ type MoneyRequestViewPropsWithoutTransaction = MoneyRequestViewOnyxPropsWithoutT
/** The report currently being looked at */
report: OnyxTypes.Report;
- /** Whether we should display the horizontal rule below the component */
- shouldShowHorizontalRule: boolean;
-
/** Whether we should display the animated banner above the component */
shouldShowAnimatedBackground: boolean;
};
@@ -91,7 +87,6 @@ function MoneyRequestView({
parentReport,
parentReportActions,
policyCategories,
- shouldShowHorizontalRule,
transaction,
policyTagList,
policy,
@@ -566,10 +561,6 @@ function MoneyRequestView({
)}
-
);
}
diff --git a/src/components/ReportActionItem/TaskView.tsx b/src/components/ReportActionItem/TaskView.tsx
index ae361ce9dc47..ee3097e051b2 100644
--- a/src/components/ReportActionItem/TaskView.tsx
+++ b/src/components/ReportActionItem/TaskView.tsx
@@ -11,7 +11,6 @@ import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import {usePersonalDetails} from '@components/OnyxProvider';
import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction';
-import SpacerView from '@components/SpacerView';
import Text from '@components/Text';
import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails';
@@ -23,7 +22,6 @@ import getButtonState from '@libs/getButtonState';
import Navigation from '@libs/Navigation/Navigation';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as ReportUtils from '@libs/ReportUtils';
-import * as Url from '@libs/Url';
import * as Session from '@userActions/Session';
import * as Task from '@userActions/Task';
import CONST from '@src/CONST';
@@ -40,12 +38,9 @@ type TaskViewProps = TaskViewOnyxProps &
WithCurrentUserPersonalDetailsProps & {
/** The report currently being looked at */
report: Report;
-
- /** Whether we should display the horizontal rule below the component */
- shouldShowHorizontalRule: boolean;
};
-function TaskView({report, shouldShowHorizontalRule, ...props}: TaskViewProps) {
+function TaskView({report, ...props}: TaskViewProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
useEffect(() => {
@@ -150,8 +145,6 @@ function TaskView({report, shouldShowHorizontalRule, ...props}: TaskViewProps) {
shouldGreyOutWhenDisabled={false}
numberOfLinesTitle={0}
interactive={!isDisableInteractive}
- shouldRenderAsHTML
- shouldEscapeText={Url.hasURL(report.description ?? '') ? undefined : false}
/>
@@ -185,10 +178,6 @@ function TaskView({report, shouldShowHorizontalRule, ...props}: TaskViewProps) {
)}
-
);
}
diff --git a/src/components/UnreadActionIndicator.tsx b/src/components/UnreadActionIndicator.tsx
index f422ae24bd4f..c63079e69853 100755
--- a/src/components/UnreadActionIndicator.tsx
+++ b/src/components/UnreadActionIndicator.tsx
@@ -6,18 +6,24 @@ import CONST from '@src/CONST';
import Text from './Text';
type UnreadActionIndicatorProps = {
+ /** The ID of the report action */
reportActionID: string;
+
+ /** Whether we should hide thread divider line */
+ shouldHideThreadDividerLine?: boolean;
};
-function UnreadActionIndicator({reportActionID}: UnreadActionIndicatorProps) {
+function UnreadActionIndicator({reportActionID, shouldHideThreadDividerLine}: UnreadActionIndicatorProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
+ const containerStyle = shouldHideThreadDividerLine ? styles.topUnreadIndicatorContainer : styles.unreadIndicatorContainer;
+
return (
diff --git a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx
index 592cec3beca5..0f7316062027 100644
--- a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx
+++ b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx
@@ -1,6 +1,7 @@
import Str from 'expensify-common/lib/str';
import React, {useCallback} from 'react';
import {View} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
import Avatar from '@components/Avatar';
import {usePersonalDetails} from '@components/OnyxProvider';
import Text from '@components/Text';
@@ -8,19 +9,23 @@ import Tooltip from '@components/Tooltip';
import type UserDetailsTooltipProps from '@components/UserDetailsTooltip/types';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
+import {isAnonymousUser} from '@libs/actions/Session';
import * as LocalePhoneNumber from '@libs/LocalePhoneNumber';
import * as ReportUtils from '@libs/ReportUtils';
import * as UserUtils from '@libs/UserUtils';
import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
function BaseUserDetailsTooltip({accountID, fallbackUserDetails, icon, delegateAccountID, shiftHorizontal, children}: UserDetailsTooltipProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const personalDetails = usePersonalDetails();
+ const [session] = useOnyx(ONYXKEYS.SESSION);
+ const isCurrentUserAnonymous = session?.accountID === accountID && isAnonymousUser(session);
const userDetails = personalDetails?.[accountID] ?? fallbackUserDetails ?? {};
let userDisplayName = ReportUtils.getUserDetailTooltipText(accountID, userDetails.displayName ? userDetails.displayName.trim() : '');
- let userLogin = userDetails.login?.trim() && userDetails.login !== userDetails.displayName ? Str.removeSMSDomain(userDetails.login) : '';
+ let userLogin = !isCurrentUserAnonymous && userDetails.login?.trim() && userDetails.login !== userDetails.displayName ? Str.removeSMSDomain(userDetails.login) : '';
let userAvatar = userDetails.avatar;
let userAccountID = accountID;
diff --git a/src/components/WorkspaceSwitcherButton.tsx b/src/components/WorkspaceSwitcherButton.tsx
index 118fcd0f10a9..94577eecf5b2 100644
--- a/src/components/WorkspaceSwitcherButton.tsx
+++ b/src/components/WorkspaceSwitcherButton.tsx
@@ -31,7 +31,7 @@ function WorkspaceSwitcherButton({policy}: WorkspaceSwitcherButtonProps) {
return {source: Expensicons.ExpensifyAppIcon, name: CONST.WORKSPACE_SWITCHER.NAME, type: CONST.ICON_TYPE_AVATAR};
}
- const avatar = policy?.avatar ? policy.avatar : getDefaultWorkspaceAvatar(policy?.name);
+ const avatar = policy?.avatarURL ? policy.avatarURL : getDefaultWorkspaceAvatar(policy?.name);
return {
source: avatar,
name: policy?.name ?? '',
diff --git a/src/hooks/useReportIDs.tsx b/src/hooks/useReportIDs.tsx
index 58d4e42cd83b..c1503595fa24 100644
--- a/src/hooks/useReportIDs.tsx
+++ b/src/hooks/useReportIDs.tsx
@@ -13,7 +13,7 @@ import useCurrentReportID from './useCurrentReportID';
import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
type ChatReportSelector = OnyxTypes.Report & {isUnreadWithMention: boolean};
-type PolicySelector = Pick;
+type PolicySelector = Pick;
type ReportActionsSelector = Array>;
type ReportIDsContextProviderProps = {
@@ -97,7 +97,7 @@ const policySelector = (policy: OnyxEntry): PolicySelector =>
(policy && {
type: policy.type,
name: policy.name,
- avatar: policy.avatar,
+ avatarURL: policy.avatarURL,
employeeList: policy.employeeList,
}) as PolicySelector;
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 4365ce6a6a88..48df154a3a5c 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -1932,35 +1932,18 @@ export default {
archive: 'Accounts receivable archive', // This is an account name that will come directly from QBO, so I don't know why we need a translation for it. It should take whatever the name of the account is in QBO. Leaving this note for CS.
exportInvoicesDescription: 'Invoices will be exported to this account in QuickBooks Online.',
exportCompanyCardsDescription: 'Set how company card purchases export to QuickBooks Online.',
- creditCard: 'Credit card',
- debitCard: 'Debit card',
- vendorBill: 'Vendor bill',
vendor: 'Vendor',
defaultVendor: 'Default vendor',
defaultVendorDescription: 'Set a default vendor that will apply to all credit card transactions upon export.',
- debitCardDescription:
- "We'll automatically match the merchant name on the debit card transaction to any corresponding vendors in QuickBooks. If no vendors exist, we'll create a 'Debit Card Misc.' vendor for association.",
- creditCardDescription:
- "We'll automatically match the merchant name on the credit card transaction to any corresponding vendors in QuickBooks. If no vendors exist, we'll create a 'Credit Card Misc.' vendor for association.",
- vendorBillDescription:
- "We'll create a single itemized vendor bill for each Expensify report, carrying the date of the last expense on the report. If this period is closed, we'll post to the 1st of the next open period. You can add the vendor bill to your A/P account of choice (below).",
- debitCardAccountDescription: 'Debit card transactions will export to the bank account below.”',
- creditCardAccountDescription: 'Credit card transactions will export to the bank account below.',
- vendorBillAccountDescription: 'Select the vendor applied to all credit card transactions.',
exportPreferredExporterNote: 'This can be any workspace admin, but must be a Domain Admin if you set different export accounts for individual company cards in Domain Settings.',
exportPreferredExporterSubNote: 'Once set, the preferred exporter will see reports for export in their account.',
exportOutOfPocketExpensesDescription: 'Set how out-of-pocket expenses export to QuickBooks Online.',
exportVendorBillDescription:
"We'll create a single itemized vendor bill for each Expensify report. If the period of the bill is closed, we'll post to the 1st of the next open period. You can add the vendor bill to your A/P account of choice (below).",
- check: 'Check',
accountsPayable: 'Accounts payable',
account: 'Account',
accountsPayableDescription: 'This is your chosen A/P account, against which vendor bills for each report are created.',
- journalEntry: 'Journal entry',
optionBelow: 'Choose an option below:',
- vendorBillError: 'Vendor Bills are not available when locations are enabled. Please select a different export option.',
- checkError: 'Check is not available when locations are enabled. Please select a different export option.',
- journalEntryError: 'Journal entry is not available when taxes enabled. please select a different export option.',
companyCardsLocationEnabledDescription:
'Note: QuickBooks Online does not support a field for Locations as Tags on Vendor Bills exports. As you import Locations from, this this export option is unavailable.',
outOfPocketTaxEnabledDescription:
@@ -1987,6 +1970,29 @@ export default {
invoiceAccountSelectDescription:
'If you are exporting invoices from Expensify to Quickbooks Online, this is the account the invoice will appear against once marked as paid.',
},
+ accounts: {
+ [CONST.QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE.DEBIT_CARD]: 'Debit card',
+ [CONST.QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE.CREDIT_CARD]: 'Credit card',
+ [CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.VENDOR_BILL]: 'Vendor bill',
+ [CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.JOURNAL_ENTRY]: 'Journal entry',
+ [CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.CHECK]: 'Check',
+
+ [`${CONST.QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE.DEBIT_CARD}Description`]:
+ "We'll automatically match the merchant name on the debit card transaction to any corresponding vendors in QuickBooks. If no vendors exist, we'll create a 'Debit Card Misc.' vendor for association.",
+ [`${CONST.QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE.CREDIT_CARD}Description`]:
+ "We'll automatically match the merchant name on the credit card transaction to any corresponding vendors in QuickBooks. If no vendors exist, we'll create a 'Credit Card Misc.' vendor for association.",
+ [`${CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.VENDOR_BILL}Description`]:
+ "We'll create a single itemized vendor bill for each Expensify report, carrying the date of the last expense on the report. If this period is closed, we'll post to the 1st of the next open period. You can add the vendor bill to your A/P account of choice (below).",
+
+ [`${CONST.QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE.DEBIT_CARD}AccountDescription`]: 'Debit card transactions will export to the bank account below.”',
+ [`${CONST.QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE.CREDIT_CARD}AccountDescription`]: 'Credit card transactions will export to the bank account below.',
+ [`${CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.VENDOR_BILL}AccountDescription`]: 'Select the vendor applied to all credit card transactions.',
+
+ [`${CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.VENDOR_BILL}Error`]:
+ 'Vendor Bills are not available when locations are enabled. Please select a different export option.',
+ [`${CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.CHECK}Error`]: 'Check is not available when locations are enabled. Please select a different export option.',
+ [`${CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.JOURNAL_ENTRY}Error`]: 'Journal entry is not available when taxes enabled. please select a different export option.',
+ },
},
xero: {
organization: 'Xero organization',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 3301734636ef..544a1d562ced 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1956,35 +1956,18 @@ export default {
archive: 'Archivo de cuentas por cobrar', // This is an account name that will come directly from QBO, so I don't know why we need a translation for it. It should take whatever the name of the account is in QBO. Leaving this note for CS.
exportInvoicesDescription: 'Las facturas se exportarán a esta cuenta en QuickBooks Online.',
exportCompanyCardsDescription: 'Establece cómo se exportan las compras con tarjeta de empresa a QuickBooks Online.',
- debitCard: 'Tarjeta de débito',
- check: 'Cheque',
- optionBelow: 'Elija una opción a continuación:',
- creditCard: 'Tarjeta de crédito',
- vendorBill: 'Factura del proveedor',
account: 'Cuenta',
vendor: 'Proveedor',
defaultVendor: 'Proveedor predeterminado',
defaultVendorDescription: 'Establece un proveedor predeterminado que se aplicará a todas las transacciones con tarjeta de crédito al momento de exportarlas.',
- debitCardAccountDescription: 'Las transacciones con tarjeta de débito se exportarán a la cuenta bancaria que aparece a continuación.”',
- creditCardAccountDescription: 'Las transacciones con tarjeta de crédito se exportarán a la cuenta bancaria que aparece a continuación.',
- vendorBillAccountDescription: 'Selecciona el proveedor que se aplicará a todas las transacciones con tarjeta de crédito.',
- debitCardDescription:
- "Automáticamente relacionaremos el nombre del comerciante de la transacción con tarjeta de débito con cualquier proveedor correspondiente en QuickBooks. Si no existen proveedores, crearemos un proveedor asociado 'Debit Card Misc.'.",
- creditCardDescription:
- "Automáticamente relacionaremos el nombre del comerciante de la transacción con tarjeta de crédito con cualquier proveedor correspondiente en QuickBooks. Si no existen proveedores, crearemos un proveedor asociado 'Credit Card Misc.'.",
- vendorBillDescription:
- 'Crearemos una única factura detallada con los proveedores por cada informe de Expensify, con fecha del último gasto en el informe. Si este período está cerrado, la publicaremos con fecha del día 1 del próximo período abierto. Puede añadir la factura del proveedor a la cuenta A/P de su elección (a continuación).',
accountsPayable: 'Cuentas por pagar',
accountsPayableDescription: 'Esta es la cuenta de cuentas por pagar elegida, contra la cual se crean las facturas de proveedores para cada informe.',
+ optionBelow: 'Elija una opción a continuación:',
companyCardsLocationEnabledDescription:
'Nota: QuickBooks Online no admite un campo para Ubicaciones como etiquetas en las exportaciones de facturas de proveedores. A medida que importa ubicaciones, esta opción de exportación no está disponible.',
exportPreferredExporterNote:
'Puede ser cualquier administrador del espacio de trabajo, pero debe ser un administrador de dominio si configura diferentes cuentas de exportación para tarjetas de empresa individuales en la configuración del dominio.',
exportPreferredExporterSubNote: 'Una vez configurado, el exportador preferido verá los informes para exportar en su cuenta.',
- journalEntry: 'Asiento contable',
- vendorBillError: 'Las facturas de proveedores no están disponibles cuando las ubicaciones están habilitadas. Seleccione una opción de exportación diferente.',
- checkError: 'La verificación no está disponible cuando las ubicaciones están habilitadas. Seleccione una opción de exportación diferente.',
- journalEntryError: 'El asiento de diario no está disponible cuando los impuestos están habilitados. seleccione una opción de exportación diferente.',
exportOutOfPocketExpensesDescription: 'Establezca cómo se exportan los gastos de bolsillo a QuickBooks Online.',
exportVendorBillDescription:
'Crearemos una única factura de proveedor detallada para cada informe de Expensify. Si el período de la factura está cerrado, lo publicaremos en el día 1 del siguiente período abierto. Puede agregar la factura del proveedor a la cuenta A/P de su elección (a continuación).',
@@ -2014,6 +1997,34 @@ export default {
invoiceAccountSelectDescription:
'Si está exportando facturas de Expensify a Quickbooks Online, ésta es la cuenta en la que aparecerá la factura una vez marcada como pagada.',
},
+ accounts: {
+ [CONST.QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE.DEBIT_CARD]: 'Tarjeta de débito',
+ [CONST.QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE.CREDIT_CARD]: 'Tarjeta de crédito',
+ [CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.VENDOR_BILL]: 'Factura del proveedor',
+ [CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.JOURNAL_ENTRY]: 'Asiento contable',
+ [CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.CHECK]: 'Cheque',
+
+ [`${CONST.QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE.DEBIT_CARD}Description`]:
+ "Automáticamente relacionaremos el nombre del comerciante de la transacción con tarjeta de débito con cualquier proveedor correspondiente en QuickBooks. Si no existen proveedores, crearemos un proveedor asociado 'Debit Card Misc.'.",
+ [`${CONST.QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE.CREDIT_CARD}Description`]:
+ "Automáticamente relacionaremos el nombre del comerciante de la transacción con tarjeta de crédito con cualquier proveedor correspondiente en QuickBooks. Si no existen proveedores, crearemos un proveedor asociado 'Credit Card Misc.'.",
+ [`${CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.VENDOR_BILL}Description`]:
+ 'Crearemos una única factura detallada con los proveedores por cada informe de Expensify, con fecha del último gasto en el informe. Si este período está cerrado, la publicaremos con fecha del día 1 del próximo período abierto. Puede añadir la factura del proveedor a la cuenta A/P de su elección (a continuación).',
+
+ [`${CONST.QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE.DEBIT_CARD}AccountDescription`]:
+ 'Las transacciones con tarjeta de débito se exportarán a la cuenta bancaria que aparece a continuación.”',
+ [`${CONST.QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE.CREDIT_CARD}AccountDescription`]:
+ 'Las transacciones con tarjeta de crédito se exportarán a la cuenta bancaria que aparece a continuación.',
+ [`${CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.VENDOR_BILL}AccountDescription`]:
+ 'Selecciona el proveedor que se aplicará a todas las transacciones con tarjeta de crédito.',
+
+ [`${CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.VENDOR_BILL}Error`]:
+ 'Las facturas de proveedores no están disponibles cuando las ubicaciones están habilitadas. Seleccione una opción de exportación diferente.',
+ [`${CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.CHECK}Error`]:
+ 'La verificación no está disponible cuando las ubicaciones están habilitadas. Seleccione una opción de exportación diferente.',
+ [`${CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.JOURNAL_ENTRY}Error`]:
+ 'El asiento de diario no está disponible cuando los impuestos están habilitados. seleccione una opción de exportación diferente.',
+ },
},
xero: {
organization: 'Organización Xero',
diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts
index 5bbbf19f3adb..431cd1a3e820 100644
--- a/src/libs/OptionsListUtils.ts
+++ b/src/libs/OptionsListUtils.ts
@@ -1455,8 +1455,18 @@ function createOptionList(personalDetails: OnyxEntry, repor
}
const isSelfDM = ReportUtils.isSelfDM(report);
- // Currently, currentUser is not included in participants, so for selfDM we need to add the currentUser as participants.
- const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : Object.keys(report.participants ?? {}).map(Number);
+ let accountIDs = [];
+
+ if (isSelfDM) {
+ // For selfDM we need to add the currentUser as participants.
+ accountIDs = [currentUserAccountID ?? 0];
+ } else {
+ accountIDs = Object.keys(report.participants ?? {}).map(Number);
+ if (ReportUtils.isOneOnOneChat(report)) {
+ // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants
+ accountIDs = accountIDs.filter((accountID) => accountID !== currentUserAccountID);
+ }
+ }
if (!accountIDs || accountIDs.length === 0) {
return;
@@ -1676,8 +1686,18 @@ function getOptions(
const isPolicyExpenseChat = option.isPolicyExpenseChat;
const isMoneyRequestReport = option.isMoneyRequestReport;
const isSelfDM = option.isSelfDM;
- // Currently, currentUser is not included in participants, so for selfDM we need to add the currentUser as participants.
- const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : Object.keys(report.participants ?? {}).map(Number);
+ let accountIDs = [];
+
+ if (isSelfDM) {
+ // For selfDM we need to add the currentUser as participants.
+ accountIDs = [currentUserAccountID ?? 0];
+ } else {
+ accountIDs = Object.keys(report.participants ?? {}).map(Number);
+ if (ReportUtils.isOneOnOneChat(report)) {
+ // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants
+ accountIDs = accountIDs.filter((accountID) => accountID !== currentUserAccountID);
+ }
+ }
if (isPolicyExpenseChat && report.isOwnPolicyExpenseChat && !includeOwnedWorkspaceChats) {
return;
diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index 0919a93f91bb..d24b249df086 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -6,7 +6,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Policy, PolicyCategories, PolicyEmployeeList, PolicyTagList, PolicyTags, TaxRate} from '@src/types/onyx';
-import type {PolicyFeatureName, Rate} from '@src/types/onyx/Policy';
+import type {PolicyFeatureName, Rate, Tenant} from '@src/types/onyx/Policy';
import type PolicyEmployee from '@src/types/onyx/PolicyEmployee';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
@@ -390,6 +390,20 @@ function canSendInvoice(policies: OnyxCollection): boolean {
return getActiveAdminWorkspaces(policies).length > 0;
}
+/** Get the Xero organizations connected to the policy */
+function getXeroTenants(policy: Policy | undefined): Tenant[] {
+ // Due to the way optional chain is being handled in this useMemo we are forced to use this approach to properly handle undefined values
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
+ if (!policy || !policy.connections || !policy.connections.xero || !policy.connections.xero.data) {
+ return [];
+ }
+ return policy.connections.xero.data.tenants ?? [];
+}
+
+function findCurrentXeroOrganization(tenants: Tenant[] | undefined, organizationID: string | undefined): Tenant | undefined {
+ return tenants?.find((tenant) => tenant.id === organizationID);
+}
+
export {
getActivePolicies,
hasAccountingConnections,
@@ -435,6 +449,8 @@ export {
getPolicy,
getActiveAdminWorkspaces,
canSendInvoice,
+ getXeroTenants,
+ findCurrentXeroOrganization,
};
export type {MemberEmailsToAccountIDs};
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 769832747c41..9c5e437a874e 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -1669,7 +1669,7 @@ function getDefaultWorkspaceAvatarTestID(workspaceName: string): string {
function getWorkspaceAvatar(report: OnyxEntry): UserUtils.AvatarSource {
const workspaceName = getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]);
- const avatar = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]?.avatar ?? '';
+ const avatar = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]?.avatarURL ?? '';
return !isEmpty(avatar) ? avatar : getDefaultWorkspaceAvatar(workspaceName);
}
@@ -1733,8 +1733,8 @@ function getIconsForParticipants(participants: number[], personalDetails: OnyxCo
*/
function getWorkspaceIcon(report: OnyxEntry, policy: OnyxEntry = null): Icon {
const workspaceName = getPolicyName(report, false, policy);
- const policyExpenseChatAvatarSource = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]?.avatar
- ? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]?.avatar
+ const policyExpenseChatAvatarSource = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]?.avatarURL
+ ? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]?.avatarURL
: getDefaultWorkspaceAvatar(workspaceName);
const workspaceIcon: Icon = {
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index bcc065bed53b..e0b406ad9c45 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -3541,11 +3541,10 @@ function trackExpense(
API.write(WRITE_COMMANDS.TRACK_EXPENSE, parameters, onyxData);
}
}
+ Navigation.dismissModal(activeReportID);
+
if (action === CONST.IOU.ACTION.SHARE) {
- Navigation.dismissModal();
Navigation.navigate(ROUTES.ROOM_INVITE.getRoute(activeReportID ?? '', CONST.IOU.SHARE.ROLE.ACCOUNTANT));
- } else {
- Navigation.dismissModal(activeReportID);
}
Report.notifyNewAction(activeReportID, payeeAccountID);
diff --git a/src/libs/actions/PersistedRequests.ts b/src/libs/actions/PersistedRequests.ts
index 28464568219c..851e53876508 100644
--- a/src/libs/actions/PersistedRequests.ts
+++ b/src/libs/actions/PersistedRequests.ts
@@ -23,8 +23,9 @@ function getLength(): number {
}
function save(requestToPersist: Request) {
- persistedRequests.push(requestToPersist);
- Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, persistedRequests).then(() => {
+ const requests = [...persistedRequests, requestToPersist];
+ persistedRequests = requests;
+ Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests).then(() => {
Log.info(`[SequentialQueue] '${requestToPersist.command}' command queued. Queue length is ${getLength()}`);
});
}
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index 46a962cea1ad..7c1312179548 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -335,7 +335,7 @@ function deleteWorkspace(policyID: string, policyName: string) {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
- avatar: '',
+ avatarURL: '',
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
errors: null,
},
@@ -1511,13 +1511,13 @@ function updateWorkspaceAvatar(policyID: string, file: File) {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
- avatar: file.uri,
+ avatarURL: file.uri,
originalFileName: file.name,
errorFields: {
- avatar: null,
+ avatarURL: null,
},
pendingFields: {
- avatar: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ avatarURL: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
},
},
@@ -1528,7 +1528,7 @@ function updateWorkspaceAvatar(policyID: string, file: File) {
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
pendingFields: {
- avatar: null,
+ avatarURL: null,
},
},
},
@@ -1538,7 +1538,7 @@ function updateWorkspaceAvatar(policyID: string, file: File) {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
- avatar: allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.avatar,
+ avatarURL: allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.avatarURL,
},
},
];
@@ -1561,12 +1561,12 @@ function deleteWorkspaceAvatar(policyID: string) {
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
pendingFields: {
- avatar: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ avatarURL: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
errorFields: {
- avatar: null,
+ avatarURL: null,
},
- avatar: '',
+ avatarURL: '',
},
},
];
@@ -1576,7 +1576,7 @@ function deleteWorkspaceAvatar(policyID: string) {
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
pendingFields: {
- avatar: null,
+ avatarURL: null,
},
},
},
@@ -1587,7 +1587,7 @@ function deleteWorkspaceAvatar(policyID: string) {
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
errorFields: {
- avatar: ErrorUtils.getMicroSecondOnyxError('avatarWithImagePicker.deleteWorkspaceError'),
+ avatarURL: ErrorUtils.getMicroSecondOnyxError('avatarWithImagePicker.deleteWorkspaceError'),
},
},
},
@@ -1604,10 +1604,10 @@ function deleteWorkspaceAvatar(policyID: string) {
function clearAvatarErrors(policyID: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {
errorFields: {
- avatar: null,
+ avatarURL: null,
},
pendingFields: {
- avatar: null,
+ avatarURL: null,
},
});
}
diff --git a/src/libs/actions/connections/index.ts b/src/libs/actions/connections/index.ts
index 6d65c917a34c..774007db594d 100644
--- a/src/libs/actions/connections/index.ts
+++ b/src/libs/actions/connections/index.ts
@@ -118,7 +118,7 @@ function updatePolicyConnectionConfig
)}
- {login ? (
+ {/* Don't display email if current user is anonymous */}
+ {!(isCurrentUser && SessionActions.isAnonymousUser()) && login ? (
void;
+
+ /** If this is the first visible report action */
+ isFirstVisibleReportAction: boolean;
+
+ /** IF the thread divider line will be used */
+ shouldUseThreadDividerLine?: boolean;
} & ReportActionItemOnyxProps;
const isIOUReport = (actionObj: OnyxEntry): actionObj is OnyxTypes.ReportActionBase & OnyxTypes.OriginalMessageIOU =>
@@ -175,6 +182,8 @@ function ReportActionItem({
policy,
transaction,
onPress = undefined,
+ isFirstVisibleReportAction = false,
+ shouldUseThreadDividerLine = false,
}: ReportActionItemProps) {
const {translate} = useLocalize();
const {isSmallScreenWidth} = useWindowDimensions();
@@ -460,6 +469,22 @@ function ReportActionItem({
];
}, [action, report.reportID]);
+ const renderThreadDivider = useMemo(
+ () =>
+ shouldHideThreadDividerLine ? (
+
+ ) : (
+
+ ),
+ [shouldHideThreadDividerLine, styles.reportHorizontalRule, report.reportID],
+ );
+
/**
* Get the content of ReportActionItem
* @param hovered whether the ReportActionItem is hovered
@@ -765,11 +790,13 @@ function ReportActionItem({
}
return (
-
+
+
+ {renderThreadDivider}
+
);
}
@@ -797,10 +824,8 @@ function ReportActionItem({
-
+
+ {renderThreadDivider}
);
@@ -811,26 +836,32 @@ function ReportActionItem({
{transactionThreadReport && !isEmptyObject(transactionThreadReport) ? (
<>
{transactionCurrency !== report.currency && (
-
+ <>
+
+ {renderThreadDivider}
+ >
)}
-
+
+
+ {renderThreadDivider}
+
>
) : (
-
+ <>
+
+ {renderThreadDivider}
+ >
)}
);
@@ -907,7 +938,7 @@ function ReportActionItem({
>
{(hovered) => (
- {shouldDisplayNewMarker && }
+ {shouldDisplayNewMarker && (!shouldUseThreadDividerLine || !isFirstVisibleReportAction) && }
prevProps.policy?.name === nextProps.policy?.name &&
- prevProps.policy?.avatar === nextProps.policy?.avatar &&
+ prevProps.policy?.avatarURL === nextProps.policy?.avatarURL &&
prevProps.report?.stateNum === nextProps.report?.stateNum &&
prevProps.report?.statusNum === nextProps.report?.statusNum &&
prevProps.report?.lastReadTime === nextProps.report?.lastReadTime &&
diff --git a/src/pages/home/report/ReportActionItemParentAction.tsx b/src/pages/home/report/ReportActionItemParentAction.tsx
index a4592075aa0c..5de85eb5f43f 100644
--- a/src/pages/home/report/ReportActionItemParentAction.tsx
+++ b/src/pages/home/report/ReportActionItemParentAction.tsx
@@ -44,6 +44,12 @@ type ReportActionItemParentActionProps = {
/** Whether we should display "Replies" divider */
shouldDisplayReplyDivider: boolean;
+
+ /** If this is the first visible report action */
+ isFirstVisibleReportAction: boolean;
+
+ /** If the thread divider line will be used */
+ shouldUseThreadDividerLine?: boolean;
};
function ReportActionItemParentAction({
@@ -54,6 +60,8 @@ function ReportActionItemParentAction({
index = 0,
shouldHideThreadDividerLine = false,
shouldDisplayReplyDivider,
+ isFirstVisibleReportAction = false,
+ shouldUseThreadDividerLine = false,
}: ReportActionItemParentActionProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
@@ -124,6 +132,8 @@ function ReportActionItemParentAction({
isMostRecentIOUReportAction={false}
shouldDisplayNewMarker={ancestor.shouldDisplayNewMarker}
index={index}
+ isFirstVisibleReportAction={isFirstVisibleReportAction}
+ shouldUseThreadDividerLine={shouldUseThreadDividerLine}
/>
))}
diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx
index c280a093cb13..5d4ba47877d1 100644
--- a/src/pages/home/report/ReportActionsList.tsx
+++ b/src/pages/home/report/ReportActionsList.tsx
@@ -422,6 +422,8 @@ function ReportActionsList({
[sortedReportActions, isOffline, currentUnreadMarker],
);
+ const firstVisibleReportActionID = useMemo(() => ReportActionsUtils.getFirstVisibleReportActionID(sortedReportActions, isOffline), [sortedReportActions, isOffline]);
+
/**
* Evaluate new unread marker visibility for each of the report actions.
*/
@@ -451,6 +453,24 @@ function ReportActionsList({
[currentUnreadMarker, sortedVisibleReportActions, report.reportID, messageManuallyMarkedUnread],
);
+ const shouldUseThreadDividerLine = useMemo(() => {
+ const topReport = sortedVisibleReportActions[sortedVisibleReportActions.length - 1];
+
+ if (topReport.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED) {
+ return false;
+ }
+
+ if (ReportActionsUtils.isTransactionThread(parentReportAction)) {
+ return !ReportActionsUtils.isDeletedParentAction(parentReportAction) && !ReportActionsUtils.isReversedTransaction(parentReportAction);
+ }
+
+ if (ReportUtils.isTaskReport(report)) {
+ return !ReportUtils.isCanceledTaskReport(report, parentReportAction);
+ }
+
+ return ReportUtils.isExpenseReport(report) || ReportUtils.isIOUReport(report);
+ }, [parentReportAction, report, sortedVisibleReportActions]);
+
const calculateUnreadMarker = useCallback(() => {
// Iterate through the report actions and set appropriate unread marker.
// This is to avoid a warning of:
@@ -536,6 +556,8 @@ function ReportActionsList({
shouldHideThreadDividerLine={shouldHideThreadDividerLine}
shouldDisplayNewMarker={shouldDisplayNewMarker(reportAction, index)}
shouldDisplayReplyDivider={sortedReportActions.length > 1}
+ isFirstVisibleReportAction={firstVisibleReportActionID === reportAction.reportActionID}
+ shouldUseThreadDividerLine={shouldUseThreadDividerLine}
/>
),
[
@@ -550,6 +572,8 @@ function ReportActionsList({
reportActions,
transactionThreadReport,
parentReportActionForTransactionThread,
+ shouldUseThreadDividerLine,
+ firstVisibleReportActionID,
],
);
diff --git a/src/pages/home/report/ReportActionsListItemRenderer.tsx b/src/pages/home/report/ReportActionsListItemRenderer.tsx
index e35b58dd9dae..8782d6dbce55 100644
--- a/src/pages/home/report/ReportActionsListItemRenderer.tsx
+++ b/src/pages/home/report/ReportActionsListItemRenderer.tsx
@@ -46,6 +46,12 @@ type ReportActionsListItemRendererProps = {
/** Whether we should display "Replies" divider */
shouldDisplayReplyDivider: boolean;
+
+ /** If this is the first visible report action */
+ isFirstVisibleReportAction: boolean;
+
+ /** If the thread divider line will be used */
+ shouldUseThreadDividerLine?: boolean;
};
function ReportActionsListItemRenderer({
@@ -61,6 +67,8 @@ function ReportActionsListItemRenderer({
shouldDisplayNewMarker,
linkedReportActionID = '',
shouldDisplayReplyDivider,
+ isFirstVisibleReportAction = false,
+ shouldUseThreadDividerLine = false,
parentReportActionForTransactionThread,
}: ReportActionsListItemRendererProps) {
const shouldDisplayParentAction =
@@ -144,6 +152,8 @@ function ReportActionsListItemRenderer({
reportActions={reportActions}
transactionThreadReport={transactionThreadReport}
index={index}
+ isFirstVisibleReportAction={isFirstVisibleReportAction}
+ shouldUseThreadDividerLine={shouldUseThreadDividerLine}
/>
) : (
);
}
diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx
index 9429591b851f..cc61e61aa1f8 100644
--- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx
+++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx
@@ -44,7 +44,7 @@ const useIsFocused = () => {
return isFocused || (topmostCentralPane?.name === SCREENS.SEARCH.CENTRAL_PANE && isSmallScreenWidth);
};
-type PolicySelector = Pick;
+type PolicySelector = Pick;
type FloatingActionButtonAndPopoverOnyxProps = {
/** The list of policies the user has access to. */
@@ -91,7 +91,7 @@ const policySelector = (policy: OnyxEntry): PolicySelector =>
id: policy.id,
isPolicyExpenseChatEnabled: policy.isPolicyExpenseChatEnabled,
pendingAction: policy.pendingAction,
- avatar: policy.avatar,
+ avatarURL: policy.avatarURL,
name: policy.name,
}) as PolicySelector;
diff --git a/src/pages/iou/request/step/IOURequestStepSendFrom.tsx b/src/pages/iou/request/step/IOURequestStepSendFrom.tsx
index 6de3780aa6e8..a5def1072f7e 100644
--- a/src/pages/iou/request/step/IOURequestStepSendFrom.tsx
+++ b/src/pages/iou/request/step/IOURequestStepSendFrom.tsx
@@ -47,7 +47,7 @@ function IOURequestStepSendFrom({route, transaction, allPolicies}: IOURequestSte
keyForList: policy.id,
icons: [
{
- source: policy?.avatar ? policy.avatar : ReportUtils.getDefaultWorkspaceAvatar(policy.name),
+ source: policy?.avatarURL ? policy.avatarURL : ReportUtils.getDefaultWorkspaceAvatar(policy.name),
fallbackIcon: Expensicons.FallbackWorkspaceAvatar,
name: policy.name,
type: CONST.ICON_TYPE_WORKSPACE,
diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx
index d6a3e7b2d56b..488d3d48eae9 100755
--- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx
+++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx
@@ -220,6 +220,14 @@ function BaseValidateCodeForm({account, credentials, session, autoComplete, isUs
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLoadingResendValidationForm]);
+ useEffect(() => {
+ if (!hasError) {
+ return;
+ }
+
+ setFormError({});
+ }, [hasError]);
+
/**
* Check that all the form fields are valid, then trigger the submit callback
*/
@@ -227,6 +235,9 @@ function BaseValidateCodeForm({account, credentials, session, autoComplete, isUs
if (account?.isLoading) {
return;
}
+ if (account?.errors) {
+ SessionActions.clearAccountMessages();
+ }
const requiresTwoFactorAuth = account?.requiresTwoFactorAuth;
if (requiresTwoFactorAuth) {
if (input2FARef.current) {
diff --git a/src/pages/workspace/WorkspaceAvatar.tsx b/src/pages/workspace/WorkspaceAvatar.tsx
index 9ab9d0af25d5..f1aa5235b42f 100644
--- a/src/pages/workspace/WorkspaceAvatar.tsx
+++ b/src/pages/workspace/WorkspaceAvatar.tsx
@@ -19,7 +19,7 @@ type WorkspaceAvatarOnyxProps = {
type WorkspaceAvatarProps = WorkspaceAvatarOnyxProps & StackScreenProps;
function WorkspaceAvatar({policy, isLoadingApp = true}: WorkspaceAvatarProps) {
- const avatarURL = policy?.avatar ?? '' ? policy?.avatar ?? '' : ReportUtils.getDefaultWorkspaceAvatar(policy?.name ?? '');
+ const avatarURL = policy?.avatarURL ?? '' ? policy?.avatarURL ?? '' : ReportUtils.getDefaultWorkspaceAvatar(policy?.name ?? '');
return (
void;
pendingAction: PendingAction | undefined;
+ errors?: Errors;
+ onCloseError?: () => void;
};
type SectionObject = {
@@ -120,6 +123,8 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
action: (isEnabled: boolean) => {
Policy.enablePolicyConnections(policy?.id ?? '', isEnabled);
},
+ errors: ErrorUtils.getLatestErrorField(policy ?? {}, CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED),
+ onCloseError: () => Policy.clearPolicyErrorField(policy?.id ?? '', CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED),
},
];
@@ -158,6 +163,8 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
pendingAction={item.pendingAction}
onToggle={item.action}
disabled={item.disabled}
+ errors={item.errors}
+ onCloseError={item.onCloseError}
/>
),
diff --git a/src/pages/workspace/WorkspaceProfilePage.tsx b/src/pages/workspace/WorkspaceProfilePage.tsx
index ab8b587483a1..02b41518533f 100644
--- a/src/pages/workspace/WorkspaceProfilePage.tsx
+++ b/src/pages/workspace/WorkspaceProfilePage.tsx
@@ -88,7 +88,7 @@ function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfi
containerStyles={styles.avatarXLarge}
imageStyles={[styles.avatarXLarge, styles.alignSelfCenter]}
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing cannot be used if left side can be empty string
- source={policy?.avatar || ReportUtils.getDefaultWorkspaceAvatar(policyName)}
+ source={policy?.avatarURL || ReportUtils.getDefaultWorkspaceAvatar(policyName)}
fallbackIcon={Expensicons.FallbackWorkspaceAvatar}
size={CONST.AVATAR_SIZE.XLARGE}
name={policyName}
@@ -96,7 +96,7 @@ function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfi
type={CONST.ICON_TYPE_WORKSPACE}
/>
),
- [policy?.avatar, policy?.id, policyName, styles.alignSelfCenter, styles.avatarXLarge],
+ [policy?.avatarURL, policy?.id, policyName, styles.alignSelfCenter, styles.avatarXLarge],
);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
@@ -139,7 +139,7 @@ function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfi
/>
Navigation.navigate(ROUTES.WORKSPACE_AVATAR.getRoute(policy?.id ?? ''))}
- source={policy?.avatar ?? ''}
+ source={policy?.avatarURL ?? ''}
size={CONST.AVATAR_SIZE.XLARGE}
avatarStyle={styles.avatarXLarge}
enablePreview
@@ -147,20 +147,20 @@ function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfi
type={CONST.ICON_TYPE_WORKSPACE}
fallbackIcon={Expensicons.FallbackWorkspaceAvatar}
style={[
- policy?.errorFields?.avatar ?? isSmallScreenWidth ? styles.mb1 : styles.mb3,
+ policy?.errorFields?.avatarURL ?? isSmallScreenWidth ? styles.mb1 : styles.mb3,
isSmallScreenWidth ? styles.mtn17 : styles.mtn20,
styles.alignItemsStart,
styles.sectionMenuItemTopDescription,
]}
editIconStyle={styles.smallEditIconWorkspace}
- isUsingDefaultAvatar={!policy?.avatar ?? null}
+ isUsingDefaultAvatar={!policy?.avatarURL ?? false}
onImageSelected={(file) => Policy.updateWorkspaceAvatar(policy?.id ?? '', file as File)}
onImageRemoved={() => Policy.deleteWorkspaceAvatar(policy?.id ?? '')}
editorMaskImage={Expensicons.ImageCropSquareMask}
- pendingAction={policy?.pendingFields?.avatar}
- errors={policy?.errorFields?.avatar}
+ pendingAction={policy?.pendingFields?.avatarURL}
+ errors={policy?.errorFields?.avatarURL}
onErrorClose={() => Policy.clearAvatarErrors(policy?.id ?? '')}
- previewSource={UserUtils.getFullSizeAvatar(policy?.avatar ?? '')}
+ previewSource={UserUtils.getFullSizeAvatar(policy?.avatarURL ?? '')}
headerTitle={translate('workspace.common.workspaceAvatar')}
originalFileName={policy?.originalFileName}
disabled={readOnly}
diff --git a/src/pages/workspace/WorkspaceProfileSharePage.tsx b/src/pages/workspace/WorkspaceProfileSharePage.tsx
index 7e3a21a7f88d..1d7ec8c50e29 100644
--- a/src/pages/workspace/WorkspaceProfileSharePage.tsx
+++ b/src/pages/workspace/WorkspaceProfileSharePage.tsx
@@ -65,7 +65,7 @@ function WorkspaceProfileSharePage({policy}: WithPolicyProps) {
ref={qrCodeRef}
url={url}
title={policyName}
- logo={(policy?.avatar ? policy.avatar : expensifyLogo) as ImageSourcePropType}
+ logo={(policy?.avatarURL ? policy.avatarURL : expensifyLogo) as ImageSourcePropType}
logoRatio={CONST.QR.DEFAULT_LOGO_SIZE_RATIO}
logoMarginRatio={CONST.QR.DEFAULT_LOGO_MARGIN_RATIO}
/>
diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx
index 050fb72b02ac..a22a3679b435 100755
--- a/src/pages/workspace/WorkspacesListPage.tsx
+++ b/src/pages/workspace/WorkspacesListPage.tsx
@@ -334,7 +334,7 @@ function WorkspacesListPage({policies, reimbursementAccount, reports, session}:
}
return {
title: policy.name,
- icon: policy.avatar ? policy.avatar : ReportUtils.getDefaultWorkspaceAvatar(policy.name),
+ icon: policy.avatarURL ? policy.avatarURL : ReportUtils.getDefaultWorkspaceAvatar(policy.name),
action: () => Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policy.id)),
brickRoadIndicator: reimbursementAccountBrickRoadIndicator ?? PolicyUtils.getPolicyBrickRoadIndicatorStatus(policy),
pendingAction: policy.pendingAction,
@@ -346,7 +346,7 @@ function WorkspacesListPage({policies, reimbursementAccount, reports, session}:
dismissWorkspaceError(policy.id, policy.pendingAction);
},
disabled: policy.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
- iconType: policy.avatar ? CONST.ICON_TYPE_AVATAR : CONST.ICON_TYPE_ICON,
+ iconType: policy.avatarURL ? CONST.ICON_TYPE_AVATAR : CONST.ICON_TYPE_ICON,
iconFill: theme.textLight,
fallbackIcon: Expensicons.FallbackWorkspaceAvatar,
policyID: policy.id,
diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx
index 48888c054813..aa69b92f86de 100644
--- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx
+++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx
@@ -24,6 +24,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import {removePolicyConnection} from '@libs/actions/connections';
import {syncConnection} from '@libs/actions/connections/QuickBooksOnline';
+import {findCurrentXeroOrganization, getXeroTenants} from '@libs/PolicyUtils';
import Navigation from '@navigation/Navigation';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
@@ -33,7 +34,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Policy, PolicyConnectionSyncProgress} from '@src/types/onyx';
-import type {PolicyConnectionName, Tenant} from '@src/types/onyx/Policy';
+import type {PolicyConnectionName} from '@src/types/onyx/Policy';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type IconAsset from '@src/types/utils/IconAsset';
@@ -116,15 +117,9 @@ function PolicyAccountingPage({policy, connectionSyncProgress}: PolicyAccounting
const policyConnectedToXero = connectedIntegration === CONST.POLICY.CONNECTIONS.NAME.XERO;
- const tenants = useMemo(() => {
- // Due to the way optional chain is being handled in this useMemo we are forced to use this approach to properly handle undefined values
- // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
- if (!policy || !policy.connections || !policy.connections.xero || !policy.connections.xero.data) {
- return [];
- }
- return policy?.connections?.xero?.data?.tenants ?? [];
- }, [policy]);
- const currentXeroOrganization = tenants.find((tenant) => tenant.id === policy?.connections?.xero.config.tenantID);
+ const tenants = useMemo(() => getXeroTenants(policy), [policy]);
+
+ const currentXeroOrganization = findCurrentXeroOrganization(tenants, policy?.connections?.xero?.config?.tenantID);
const overflowMenu: ThreeDotsMenuProps['menuItems'] = useMemo(
() => [
diff --git a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx
index 6d9f19e936b9..a356b82a0c2b 100644
--- a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx
+++ b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx
@@ -87,10 +87,10 @@ function QuickbooksAdvancedPage({policy}: WithPolicyConnectionsProps) {
title: translate('workspace.qbo.advancedConfig.inviteEmployees'),
subtitle: translate('workspace.qbo.advancedConfig.inviteEmployeesDescription'),
isActive: Boolean(syncPeople),
- onToggle: () => Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.QBO, CONST.QUICK_BOOKS_CONFIG.SYNCE_PEOPLE, !syncPeople),
+ onToggle: () => Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.QBO, CONST.QUICK_BOOKS_CONFIG.SYNC_PEOPLE, !syncPeople),
pendingAction: pendingFields?.syncPeople,
- errors: ErrorUtils.getLatestErrorField(qboConfig ?? {}, CONST.QUICK_BOOKS_CONFIG.SYNCE_PEOPLE),
- onCloseError: () => Policy.clearQBOErrorField(policyID, CONST.QUICK_BOOKS_CONFIG.SYNCE_PEOPLE),
+ errors: ErrorUtils.getLatestErrorField(qboConfig ?? {}, CONST.QUICK_BOOKS_CONFIG.SYNC_PEOPLE),
+ onCloseError: () => Policy.clearQBOErrorField(policyID, CONST.QUICK_BOOKS_CONFIG.SYNC_PEOPLE),
wrapperStyle: styles.mv3,
},
{
diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx
index 498236d0912d..abc0b0bd2ba5 100644
--- a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx
+++ b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx
@@ -21,7 +21,7 @@ function QuickbooksCompanyCardExpenseAccountPage({policy}: WithPolicyConnections
const styles = useThemeStyles();
const policyID = policy?.id ?? '';
const {exportCompanyCardAccount, exportAccountPayable, autoCreateVendor, errorFields, pendingFields, exportCompanyCard} = policy?.connections?.quickbooksOnline?.config ?? {};
- const isVendorSelected = exportCompanyCard === CONST.QUICKBOOKS_EXPORT_COMPANY_CARD.VENDOR_BILL;
+ const isVendorSelected = exportCompanyCard === CONST.QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE.VENDOR_BILL;
return (
{translate('workspace.qbo.exportCompanyCardsDescription')}
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_SELECT.getRoute(policyID))}
@@ -46,7 +46,7 @@ function QuickbooksCompanyCardExpenseAccountPage({policy}: WithPolicyConnections
/>
{!!exportCompanyCard && (
- {translate(`workspace.qbo.${exportCompanyCard}Description`)}
+ {translate(`workspace.qbo.accounts.${exportCompanyCard}Description`)}
)}
{isVendorSelected && (
<>
diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectCardPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectCardPage.tsx
index e67922e6a773..9a18a188667b 100644
--- a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectCardPage.tsx
+++ b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectCardPage.tsx
@@ -19,10 +19,10 @@ import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
type CardListItem = ListItem & {
- value: ValueOf;
+ value: ValueOf;
};
type CardsSection = SectionListData>;
-type Card = {name: string; id: ValueOf};
+type Card = {name: string; id: ValueOf};
function QuickbooksCompanyCardExpenseAccountSelectCardPage({policy}: WithPolicyConnectionsProps) {
const {translate} = useLocalize();
@@ -34,16 +34,16 @@ function QuickbooksCompanyCardExpenseAccountSelectCardPage({policy}: WithPolicyC
const defaultCards = useMemo(
() => [
{
- name: translate(`workspace.qbo.creditCard`),
- id: CONST.QUICKBOOKS_EXPORT_COMPANY_CARD.CREDIT_CARD,
+ name: translate(`workspace.qbo.accounts.credit_card`),
+ id: CONST.QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE.CREDIT_CARD,
},
{
- name: translate(`workspace.qbo.debitCard`),
- id: CONST.QUICKBOOKS_EXPORT_COMPANY_CARD.DEBIT_CARD,
+ name: translate(`workspace.qbo.accounts.debit_card`),
+ id: CONST.QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE.DEBIT_CARD,
},
{
- name: translate(`workspace.qbo.vendorBill`),
- id: CONST.QUICKBOOKS_EXPORT_COMPANY_CARD.VENDOR_BILL,
+ name: translate(`workspace.qbo.accounts.bill`),
+ id: CONST.QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE.VENDOR_BILL,
},
],
[translate],
diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx
index 52865f9ae55c..e6f408f0b54a 100644
--- a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx
+++ b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx
@@ -31,13 +31,13 @@ function QuickbooksCompanyCardExpenseAccountSelectPage({policy}: WithPolicyConne
const data: CardListItem[] = useMemo(() => {
let accounts: Account[];
switch (exportCompanyCard) {
- case CONST.QUICKBOOKS_EXPORT_COMPANY_CARD.CREDIT_CARD:
+ case CONST.QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE.CREDIT_CARD:
accounts = creditCards ?? [];
break;
- case CONST.QUICKBOOKS_EXPORT_COMPANY_CARD.DEBIT_CARD:
+ case CONST.QUICKBOOKS_EXPORT_COMPANY_CARD_ACCOUNT_TYPE.DEBIT_CARD:
accounts = bankAccounts ?? [];
break;
- case CONST.QUICKBOOKS_EXPORT_ENTITY.VENDOR_BILL:
+ case CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.VENDOR_BILL:
accounts = vendors ?? [];
break;
default:
@@ -70,10 +70,10 @@ function QuickbooksCompanyCardExpenseAccountSelectPage({policy}: WithPolicyConne
>
{translate(`workspace.qbo.${exportCompanyCard}AccountDescription`)} : null}
+ headerContent={exportCompanyCard ? {translate(`workspace.qbo.accounts.${exportCompanyCard}AccountDescription`)} : null}
sections={[{data}]}
ListItem={RadioListItem}
onSelectRow={selectExportAccount}
diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksExportConfigurationPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksExportConfigurationPage.tsx
index a7e3916441f3..3d58b5807b16 100644
--- a/src/pages/workspace/accounting/qbo/export/QuickbooksExportConfigurationPage.tsx
+++ b/src/pages/workspace/accounting/qbo/export/QuickbooksExportConfigurationPage.tsx
@@ -47,7 +47,7 @@ function QuickbooksExportConfigurationPage({policy}: WithPolicyConnectionsProps)
description: translate('workspace.qbo.exportExpenses'),
onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES.getRoute(policyID)),
brickRoadIndicator: Boolean(errorFields?.exportEntity) || Boolean(errorFields?.exportAccount) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
- title: exportEntity ? translate(`workspace.qbo.${exportEntity}`) : undefined,
+ title: exportEntity ? translate(`workspace.qbo.accounts.${exportEntity}`) : undefined,
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
pendingAction: pendingFields?.exportEntity || pendingFields?.exportAccount,
},
@@ -63,13 +63,13 @@ function QuickbooksExportConfigurationPage({policy}: WithPolicyConnectionsProps)
description: translate('workspace.qbo.exportCompany'),
onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT.getRoute(policyID)),
brickRoadIndicator: errorFields?.exportCompanyCard ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
- title: exportCompanyCard ? translate(`workspace.qbo.${exportCompanyCard}`) : undefined,
+ title: exportCompanyCard ? translate(`workspace.qbo.accounts.${exportCompanyCard}`) : undefined,
pendingAction: pendingFields?.exportCompanyCard,
error: errorFields?.exportCompanyCard ? translate('common.genericErrorMessage') : undefined,
},
{
description: translate('workspace.qbo.exportExpensifyCard'),
- title: translate('workspace.qbo.creditCard'),
+ title: translate('workspace.qbo.accounts.credit_card'),
shouldShowRightIcon: false,
interactive: false,
},
diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseAccountSelectPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseAccountSelectPage.tsx
index 729fd6ade173..de897460fbf6 100644
--- a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseAccountSelectPage.tsx
+++ b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseAccountSelectPage.tsx
@@ -30,13 +30,13 @@ function QuickbooksOutOfPocketExpenseAccountSelectPage({policy}: WithPolicyConne
const data: CardListItem[] = useMemo(() => {
let accounts: Account[];
switch (exportEntity) {
- case CONST.QUICKBOOKS_EXPORT_ENTITY.CHECK:
+ case CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.CHECK:
accounts = bankAccounts ?? [];
break;
- case CONST.QUICKBOOKS_EXPORT_ENTITY.VENDOR_BILL:
+ case CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.VENDOR_BILL:
accounts = accountsPayable ?? [];
break;
- case CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY:
+ case CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.JOURNAL_ENTRY:
accounts = journalEntryAccounts ?? [];
break;
default:
diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx
index 2c8f210b330a..c5754f95d423 100644
--- a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx
+++ b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx
@@ -18,11 +18,11 @@ function QuickbooksOutOfPocketExpenseConfigurationPage({policy}: WithPolicyConne
const {translate} = useLocalize();
const styles = useThemeStyles();
const policyID = policy?.id ?? '';
- const {syncLocations, exportAccount, exportEntity, errorFields, syncTaxes, pendingFields} = policy?.connections?.quickbooksOnline?.config ?? {};
+ const {syncLocations, exportAccount, exportEntity, errorFields, syncTax, pendingFields} = policy?.connections?.quickbooksOnline?.config ?? {};
const isLocationEnabled = Boolean(syncLocations && syncLocations !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE);
- const isTaxesEnabled = Boolean(syncTaxes && syncTaxes !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE);
- const shouldShowTaxError = isTaxesEnabled && exportEntity === CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY;
- const shouldShowLocationError = isLocationEnabled && exportEntity !== CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY;
+ const isTaxesEnabled = Boolean(syncTax);
+ const shouldShowTaxError = isTaxesEnabled && exportEntity === CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.JOURNAL_ENTRY;
+ const shouldShowLocationError = isLocationEnabled && exportEntity !== CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.JOURNAL_ENTRY;
const hasErrors = Boolean(errorFields?.exportEntity) || shouldShowTaxError || shouldShowLocationError;
return (
@@ -40,15 +40,15 @@ function QuickbooksOutOfPocketExpenseConfigurationPage({policy}: WithPolicyConne
{!isLocationEnabled && {translate('workspace.qbo.exportOutOfPocketExpensesDescription')}}
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES_SELECT.getRoute(policyID))}
brickRoadIndicator={hasErrors ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
shouldShowRightIcon
/>
- {exportEntity === CONST.QUICKBOOKS_EXPORT_ENTITY.VENDOR_BILL && !isLocationEnabled && (
+ {exportEntity === CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.VENDOR_BILL && !isLocationEnabled && (
{translate('workspace.qbo.exportVendorBillDescription')}
)}
{isLocationEnabled && {translate('workspace.qbo.outOfPocketLocationEnabledDescription')}}
diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx
index 07bac6d07991..cb4bcb33edb8 100644
--- a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx
+++ b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx
@@ -19,7 +19,7 @@ import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
type CardListItem = ListItem & {
- value: ValueOf;
+ value: ValueOf;
isShown: boolean;
};
type CardsSection = SectionListData>;
@@ -27,11 +27,11 @@ type CardsSection = SectionListData>;
function QuickbooksOutOfPocketExpenseEntitySelectPage({policy}: WithPolicyConnectionsProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
- const {exportEntity, syncTaxes, syncLocations} = policy?.connections?.quickbooksOnline?.config ?? {};
+ const {exportEntity, syncTax, syncLocations} = policy?.connections?.quickbooksOnline?.config ?? {};
const isLocationsEnabled = Boolean(syncLocations && syncLocations !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE);
- const isTaxesEnabled = Boolean(syncTaxes && syncTaxes !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE);
- const isTaxError = isTaxesEnabled && exportEntity === CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY;
- const isLocationError = isLocationsEnabled && exportEntity !== CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY;
+ const isTaxesEnabled = Boolean(syncTax);
+ const isTaxError = isTaxesEnabled && exportEntity === CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.JOURNAL_ENTRY;
+ const isLocationError = isLocationsEnabled && exportEntity !== CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.JOURNAL_ENTRY;
const policyID = policy?.id ?? '';
useEffect(() => {
@@ -44,24 +44,24 @@ function QuickbooksOutOfPocketExpenseEntitySelectPage({policy}: WithPolicyConnec
const data: CardListItem[] = useMemo(
() => [
{
- value: CONST.QUICKBOOKS_EXPORT_ENTITY.CHECK,
- text: translate(`workspace.qbo.check`),
- keyForList: CONST.QUICKBOOKS_EXPORT_ENTITY.CHECK,
- isSelected: exportEntity === CONST.QUICKBOOKS_EXPORT_ENTITY.CHECK,
+ value: CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.CHECK,
+ text: translate(`workspace.qbo.accounts.check`),
+ keyForList: CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.CHECK,
+ isSelected: exportEntity === CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.CHECK,
isShown: !isLocationsEnabled,
},
{
- value: CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY,
- text: translate(`workspace.qbo.journalEntry`),
- keyForList: CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY,
- isSelected: exportEntity === CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY,
+ value: CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.JOURNAL_ENTRY,
+ text: translate(`workspace.qbo.accounts.journal_entry`),
+ keyForList: CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.JOURNAL_ENTRY,
+ isSelected: exportEntity === CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.JOURNAL_ENTRY,
isShown: !isTaxesEnabled || isLocationsEnabled,
},
{
- value: CONST.QUICKBOOKS_EXPORT_ENTITY.VENDOR_BILL,
- text: translate(`workspace.qbo.vendorBill`),
- keyForList: CONST.QUICKBOOKS_EXPORT_ENTITY.VENDOR_BILL,
- isSelected: exportEntity === CONST.QUICKBOOKS_EXPORT_ENTITY.VENDOR_BILL,
+ value: CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.VENDOR_BILL,
+ text: translate(`workspace.qbo.accounts.bill`),
+ keyForList: CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.VENDOR_BILL,
+ isSelected: exportEntity === CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.VENDOR_BILL,
isShown: !isLocationsEnabled,
},
],
diff --git a/src/pages/workspace/accounting/qbo/import/QuickbooksImportPage.tsx b/src/pages/workspace/accounting/qbo/import/QuickbooksImportPage.tsx
index e3e3eaf84591..2376614fcee3 100644
--- a/src/pages/workspace/accounting/qbo/import/QuickbooksImportPage.tsx
+++ b/src/pages/workspace/accounting/qbo/import/QuickbooksImportPage.tsx
@@ -26,7 +26,7 @@ function QuickbooksImportPage({policy}: WithPolicyProps) {
[CONST.INTEGRATION_ENTITY_MAP_TYPES.REPORT_FIELD]: translate('workspace.qbo.importedAsReportFields'),
};
const policyID = policy?.id ?? '';
- const {syncClasses, syncCustomers, syncLocations, syncTaxes, enableNewCategories, pendingFields} = policy?.connections?.quickbooksOnline?.config ?? {};
+ const {syncClasses, syncCustomers, syncLocations, syncTax, enableNewCategories, pendingFields} = policy?.connections?.quickbooksOnline?.config ?? {};
const sections = [
{
@@ -63,9 +63,9 @@ function QuickbooksImportPage({policy}: WithPolicyProps) {
sections.push({
description: translate('workspace.accounting.taxes'),
action: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_TAXES.getRoute(policyID)),
- hasError: Boolean(policy?.errors?.syncTaxes),
- title: syncTaxes,
- pendingAction: pendingFields?.syncTaxes,
+ hasError: Boolean(policy?.errors?.syncTax),
+ title: syncTax ? CONST.INTEGRATION_ENTITY_MAP_TYPES.IMPORTED : CONST.INTEGRATION_ENTITY_MAP_TYPES.NOT_IMPORTED,
+ pendingAction: pendingFields?.syncTax,
});
}
diff --git a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx
index e8a32a62d8b5..9cd6e9e1653f 100644
--- a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx
+++ b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx
@@ -19,8 +19,7 @@ function QuickbooksTaxesPage({policy}: WithPolicyProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const policyID = policy?.id ?? '';
- const {syncTaxes, pendingFields} = policy?.connections?.quickbooksOnline?.config ?? {};
- const isSwitchOn = Boolean(syncTaxes && syncTaxes !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE);
+ const {syncTax, pendingFields} = policy?.connections?.quickbooksOnline?.config ?? {};
return (
{translate('workspace.accounting.import')}
-
+
- Connections.updatePolicyConnectionConfig(
- policyID,
- CONST.POLICY.CONNECTIONS.NAME.QBO,
- CONST.QUICK_BOOKS_CONFIG.SYNC_TAXES,
- isSwitchOn ? CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE : CONST.INTEGRATION_ENTITY_MAP_TYPES.TAG,
- )
- }
+ isOn={!!syncTax}
+ onToggle={() => Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.QBO, CONST.QUICK_BOOKS_CONFIG.SYNC_TAX, !syncTax)}
/>
diff --git a/src/pages/workspace/accounting/xero/XeroImportPage.tsx b/src/pages/workspace/accounting/xero/XeroImportPage.tsx
index af36bfcc42cd..0f2855bdb2c5 100644
--- a/src/pages/workspace/accounting/xero/XeroImportPage.tsx
+++ b/src/pages/workspace/accounting/xero/XeroImportPage.tsx
@@ -8,12 +8,12 @@ import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
+import {getXeroTenants} from '@libs/PolicyUtils';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import withPolicy from '@pages/workspace/withPolicy';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
-import type {Tenant} from '@src/types/onyx/Policy';
function XeroImportPage({policy}: WithPolicyProps) {
const {translate} = useLocalize();
@@ -22,7 +22,7 @@ function XeroImportPage({policy}: WithPolicyProps) {
const policyID = policy?.id ?? '';
const {importCustomers, importTaxRates, importTrackingCategories, pendingFields} = policy?.connections?.xero?.config ?? {};
- const tenants = useMemo(() => policy?.connections?.xero?.data?.tenants ?? [], [policy?.connections?.xero.data.tenants]);
+ const tenants = useMemo(() => getXeroTenants(policy ?? undefined), [policy]);
const currentXeroOrganization = tenants.find((tenant) => tenant.id === policy?.connections?.xero.config.tenantID);
const sections = useMemo(
diff --git a/src/pages/workspace/accounting/xero/XeroOrganizationConfigurationPage.tsx b/src/pages/workspace/accounting/xero/XeroOrganizationConfigurationPage.tsx
index 3411f524b7d4..475bbe87e688 100644
--- a/src/pages/workspace/accounting/xero/XeroOrganizationConfigurationPage.tsx
+++ b/src/pages/workspace/accounting/xero/XeroOrganizationConfigurationPage.tsx
@@ -1,5 +1,5 @@
import type {StackScreenProps} from '@react-navigation/stack';
-import React from 'react';
+import React, {useMemo} from 'react';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
@@ -10,11 +10,14 @@ import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import {updatePolicyConnectionConfig} from '@libs/actions/connections';
+import Navigation from '@libs/Navigation/Navigation';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
+import {findCurrentXeroOrganization, getXeroTenants} from '@libs/PolicyUtils';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import withPolicy from '@pages/workspace/withPolicy';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
import CONST from '@src/CONST';
+import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
type XeroOrganizationConfigurationPageProps = WithPolicyProps & StackScreenProps;
@@ -26,6 +29,8 @@ function XeroOrganizationConfigurationPage({
}: XeroOrganizationConfigurationPageProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
+ const tenants = useMemo(() => getXeroTenants(policy ?? undefined), [policy]);
+ const currentXeroOrganization = findCurrentXeroOrganization(tenants, policy?.connections?.xero?.config?.tenantID);
const policyID = policy?.id ?? '';
@@ -42,6 +47,7 @@ function XeroOrganizationConfigurationPage({
}
updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.XERO, 'tenantID', keyForList);
+ Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING.getRoute(policyID));
};
return (
@@ -62,7 +68,7 @@ function XeroOrganizationConfigurationPage({
ListItem={RadioListItem}
onSelectRow={saveSelection}
sections={[{data: sections}]}
- initiallyFocusedOptionKey={organizationID}
+ initiallyFocusedOptionKey={currentXeroOrganization?.id}
/>
diff --git a/src/styles/index.ts b/src/styles/index.ts
index be79c3af4819..365c89ac4a18 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -2822,6 +2822,18 @@ const styles = (theme: ThemeColors) =>
...cursor.cursorDefault,
},
+ topUnreadIndicatorContainer: {
+ position: 'relative',
+ width: '100%',
+ /** 17 = height of the indicator 1px + 8px top and bottom */
+ height: 17,
+ paddingHorizontal: 20,
+ flexDirection: 'row',
+ alignItems: 'center',
+ zIndex: 1,
+ ...cursor.cursorDefault,
+ },
+
unreadIndicatorLine: {
height: 1,
backgroundColor: theme.unreadIndicator,
diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts
index 2e4c37d4c082..d64de6196985 100644
--- a/src/types/onyx/Policy.ts
+++ b/src/types/onyx/Policy.ts
@@ -178,7 +178,6 @@ type QBOConnectionConfig = OnyxCommon.OnyxValueWithOfflineFeedback<{
syncCustomers: IntegrationEntityMap;
syncLocations: IntegrationEntityMap;
syncAccounts: IntegrationEntityMap;
- syncTaxes: IntegrationEntityMap;
lastConfigurationTime: number;
exportCompanyCardAccount?: string;
syncTax: boolean;
@@ -191,8 +190,8 @@ type QBOConnectionConfig = OnyxCommon.OnyxValueWithOfflineFeedback<{
exportAccount: string;
exportAccountPayable: string;
accountPayable: string;
- exportEntity?: ValueOf;
- exportCompanyCard: ValueOf;
+ exportEntity?: ValueOf;
+ exportCompanyCard: ValueOf;
errorFields?: OnyxCommon.ErrorFields;
}>;
@@ -377,7 +376,6 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback<
address?: CompanyAddress;
/** The URL for the policy avatar */
- avatar?: string;
avatarURL?: string;
/** Error objects keyed by field name containing errors keyed by microtime */
diff --git a/src/types/onyx/PolicyJoinMember.ts b/src/types/onyx/PolicyJoinMember.ts
index 263be6c21dc2..7c540b334b4a 100644
--- a/src/types/onyx/PolicyJoinMember.ts
+++ b/src/types/onyx/PolicyJoinMember.ts
@@ -1,7 +1,7 @@
import type * as OnyxCommon from './OnyxCommon';
type PolicyJoinMember = {
- /** Role of the user in the policy */
+ /** The ID of the policy */
policyID?: string;
/** Email of the user inviting the new member */
diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx
index 6c90a3dbf841..5218c1e696c9 100644
--- a/tests/ui/UnreadIndicatorsTest.tsx
+++ b/tests/ui/UnreadIndicatorsTest.tsx
@@ -439,11 +439,11 @@ describe('Unread Indicators', () => {
expect(displayNameTexts).toHaveLength(2);
const firstReportOption = displayNameTexts[0];
expect(firstReportOption?.props?.style?.fontWeight).toBe(FontUtils.fontWeight.bold);
- expect(firstReportOption?.props?.children?.[0]).toBe('C User');
+ expect(screen.getByText('C User')).toBeOnTheScreen();
const secondReportOption = displayNameTexts[1];
expect(secondReportOption?.props?.style?.fontWeight).toBe(FontUtils.fontWeight.bold);
- expect(secondReportOption?.props?.children?.[0]).toBe('B User');
+ expect(screen.getByText('B User')).toBeOnTheScreen();
// Tap the new report option and navigate back to the sidebar again via the back button
return navigateToSidebarOption(0);
@@ -456,9 +456,9 @@ describe('Unread Indicators', () => {
const displayNameTexts = screen.queryAllByLabelText(hintText);
expect(displayNameTexts).toHaveLength(2);
expect(displayNameTexts[0]?.props?.style?.fontWeight).toBe(undefined);
- expect(displayNameTexts[0]?.props?.children?.[0]).toBe('C User');
+ expect(screen.getAllByText('C User')[0]).toBeOnTheScreen();
expect(displayNameTexts[1]?.props?.style?.fontWeight).toBe(FontUtils.fontWeight.bold);
- expect(displayNameTexts[1]?.props?.children?.[0]).toBe('B User');
+ expect(screen.getByText('B User')).toBeOnTheScreen();
}));
xit('Manually marking a chat message as unread shows the new line indicator and updates the LHN', () =>
@@ -490,7 +490,7 @@ describe('Unread Indicators', () => {
const displayNameTexts = screen.queryAllByLabelText(hintText);
expect(displayNameTexts).toHaveLength(1);
expect(displayNameTexts[0]?.props?.style?.fontWeight).toBe(FontUtils.fontWeight.bold);
- expect(displayNameTexts[0]?.props?.children?.[0]).toBe('B User');
+ expect(screen.getByText('B User')).toBeOnTheScreen();
// Navigate to the report again and back to the sidebar
return navigateToSidebarOption(0);
@@ -502,7 +502,7 @@ describe('Unread Indicators', () => {
const displayNameTexts = screen.queryAllByLabelText(hintText);
expect(displayNameTexts).toHaveLength(1);
expect(displayNameTexts[0]?.props?.style?.fontWeight).toBe(undefined);
- expect(displayNameTexts[0]?.props?.children?.[0]).toBe('B User');
+ expect(screen.getByText('B User')).toBeOnTheScreen();
// Navigate to the report again and verify the new line indicator is missing
return navigateToSidebarOption(0);
@@ -615,7 +615,7 @@ describe('Unread Indicators', () => {
const hintText = Localize.translateLocal('accessibilityHints.lastChatMessagePreview');
const alternateText = screen.queryAllByLabelText(hintText);
expect(alternateText).toHaveLength(1);
- expect(alternateText[0].props.children).toBe('Current User Comment 1');
+ expect(screen.getByText('Current User Comment 1')).toBeOnTheScreen();
if (lastReportAction) {
Report.deleteReportComment(REPORT_ID, lastReportAction);
@@ -626,7 +626,7 @@ describe('Unread Indicators', () => {
const hintText = Localize.translateLocal('accessibilityHints.lastChatMessagePreview');
const alternateText = screen.queryAllByLabelText(hintText);
expect(alternateText).toHaveLength(1);
- expect(alternateText[0].props.children).toBe('Comment 9');
+ expect(screen.getAllByText('Comment 9')[0]).toBeOnTheScreen();
})
);
});
diff --git a/tests/unit/CalendarPickerTest.tsx b/tests/unit/CalendarPickerTest.tsx
index 8beb02ec80c4..9c471be5b035 100644
--- a/tests/unit/CalendarPickerTest.tsx
+++ b/tests/unit/CalendarPickerTest.tsx
@@ -1,5 +1,5 @@
import type ReactNavigationNative from '@react-navigation/native';
-import {fireEvent, render, within} from '@testing-library/react-native';
+import {fireEvent, render, screen, within} from '@testing-library/react-native';
import {addMonths, addYears, subMonths, subYears} from 'date-fns';
import type {ComponentType} from 'react';
import CalendarPicker from '@components/DatePicker/CalendarPicker';
@@ -45,40 +45,40 @@ describe('CalendarPicker', () => {
const currentDate = new Date();
const maxDate = addYears(new Date(currentDate), 1);
const minDate = subYears(new Date(currentDate), 1);
- const {getByText} = render(
+ render(
,
);
- expect(getByText(monthNames[currentDate.getMonth()])).toBeTruthy();
- expect(getByText(currentDate.getFullYear().toString())).toBeTruthy();
+ expect(screen.getByText(monthNames[currentDate.getMonth()])).toBeTruthy();
+ expect(screen.getByText(currentDate.getFullYear().toString())).toBeTruthy();
});
test('clicking next month arrow updates the displayed month', () => {
const minDate = new Date('2022-01-01');
const maxDate = new Date('2030-01-01');
- const {getByTestId, getByText} = render(
+ render(
,
);
- fireEvent.press(getByTestId('next-month-arrow'));
+ fireEvent.press(screen.getByTestId('next-month-arrow'));
const nextMonth = addMonths(new Date(), 1).getMonth();
- expect(getByText(monthNames[nextMonth])).toBeTruthy();
+ expect(screen.getByText(monthNames[nextMonth])).toBeTruthy();
});
test('clicking previous month arrow updates the displayed month', () => {
- const {getByTestId, getByText} = render();
+ render();
- fireEvent.press(getByTestId('prev-month-arrow'));
+ fireEvent.press(screen.getByTestId('prev-month-arrow'));
const prevMonth = subMonths(new Date(), 1).getMonth();
- expect(getByText(monthNames[prevMonth])).toBeTruthy();
+ expect(screen.getByText(monthNames[prevMonth])).toBeTruthy();
});
test('clicking a day updates the selected date', () => {
@@ -86,7 +86,7 @@ describe('CalendarPicker', () => {
const minDate = new Date('2022-01-01');
const maxDate = new Date('2030-01-01');
const value = '2023-01-01';
- const {getByText} = render(
+ render(
{
/>,
);
- fireEvent.press(getByText('15'));
+ fireEvent.press(screen.getByText('15'));
expect(onSelectedMock).toHaveBeenCalledWith('2023-01-15');
expect(onSelectedMock).toHaveBeenCalledTimes(1);
@@ -106,7 +106,7 @@ describe('CalendarPicker', () => {
const value = '2022-01-01';
const minDate = new Date('2022-01-01');
const maxDate = new Date('2030-01-01');
- const {getByText, getByTestId} = render(
+ render(
{
/>,
);
- fireEvent.press(getByTestId('next-month-arrow'));
- fireEvent.press(getByText('15'));
+ fireEvent.press(screen.getByTestId('next-month-arrow'));
+ fireEvent.press(screen.getByText('15'));
expect(onSelectedMock).toHaveBeenCalledWith('2022-02-15');
});
@@ -124,126 +124,126 @@ describe('CalendarPicker', () => {
test('should block the back arrow when there is no available dates in the previous month', () => {
const minDate = new Date('2003-02-01');
const value = new Date('2003-02-17');
- const {getByTestId} = render(
+ render(
,
);
- expect(getByTestId('prev-month-arrow')).toBeDisabled();
+ expect(screen.getByTestId('prev-month-arrow')).toBeDisabled();
});
test('should block the next arrow when there is no available dates in the next month', () => {
const maxDate = new Date('2003-02-24');
const value = '2003-02-17';
- const {getByTestId} = render(
+ render(
,
);
- expect(getByTestId('next-month-arrow')).toBeDisabled();
+ expect(screen.getByTestId('next-month-arrow')).toBeDisabled();
});
test('should allow navigating to the month of the max date when it has less days than the selected date', () => {
const maxDate = new Date('2003-11-27'); // This month has 30 days
const value = '2003-10-31';
- const {getByTestId} = render(
+ render(
,
);
- expect(getByTestId('next-month-arrow')).not.toBeDisabled();
+ expect(screen.getByTestId('next-month-arrow')).not.toBeDisabled();
});
test('should open the calendar on a month from max date if it is earlier than current month', () => {
const onSelectedMock = jest.fn();
const maxDate = new Date('2011-03-01');
- const {getByText} = render(
+ render(
,
);
- fireEvent.press(getByText('1'));
+ fireEvent.press(screen.getByText('1'));
expect(onSelectedMock).toHaveBeenCalledWith('2011-03-01');
});
test('should open the calendar on a year from max date if it is earlier than current year', () => {
const maxDate = new Date('2011-03-01');
- const {getByTestId} = render();
+ render();
- expect(within(getByTestId('currentYearText')).getByText('2011')).toBeTruthy();
+ expect(within(screen.getByTestId('currentYearText')).getByText('2011')).toBeTruthy();
});
test('should open the calendar on a month from min date if it is later than current month', () => {
const minDate = new Date('2035-02-16');
const maxDate = new Date('2040-02-16');
- const {getByTestId} = render(
+ render(
,
);
- expect(within(getByTestId('currentYearText')).getByText(minDate.getFullYear().toString())).toBeTruthy();
+ expect(within(screen.getByTestId('currentYearText')).getByText(minDate.getFullYear().toString())).toBeTruthy();
});
test('should not allow to press earlier day than minDate', () => {
const value = '2003-02-17';
const minDate = new Date('2003-02-16');
- const {getByLabelText} = render(
+ render(
,
);
- expect(getByLabelText('15')).toBeDisabled();
+ expect(screen.getByLabelText('15')).toBeDisabled();
});
test('should not allow to press later day than max', () => {
const value = '2003-02-17';
const maxDate = new Date('2003-02-24');
- const {getByLabelText} = render(
+ render(
,
);
- expect(getByLabelText('25')).toBeDisabled();
+ expect(screen.getByLabelText('25')).toBeDisabled();
});
test('should allow to press min date', () => {
const value = '2003-02-17';
const minDate = new Date('2003-02-16');
- const {getByLabelText} = render(
+ render(
,
);
- expect(getByLabelText('16')).not.toBeDisabled();
+ expect(screen.getByLabelText('16')).not.toBeDisabled();
});
test('should allow to press max date', () => {
const value = '2003-02-17';
const maxDate = new Date('2003-02-24');
- const {getByLabelText} = render(
+ render(
,
);
- expect(getByLabelText('24')).not.toBeDisabled();
+ expect(screen.getByLabelText('24')).not.toBeDisabled();
});
});
diff --git a/tests/unit/ReportActionItemSingleTest.ts b/tests/unit/ReportActionItemSingleTest.ts
index 74a0b5f24777..e0bf06be1609 100644
--- a/tests/unit/ReportActionItemSingleTest.ts
+++ b/tests/unit/ReportActionItemSingleTest.ts
@@ -1,4 +1,4 @@
-import {cleanup, screen, waitFor} from '@testing-library/react-native';
+import {screen, waitFor} from '@testing-library/react-native';
import Onyx from 'react-native-onyx';
import type {PersonalDetailsList} from '@src/types/onyx';
import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet';
@@ -33,7 +33,6 @@ describe('ReportActionItemSingle', () => {
// Clear out Onyx after each test so that each test starts with a clean slate
afterEach(() => {
- cleanup();
Onyx.clear();
});
@@ -54,11 +53,8 @@ describe('ReportActionItemSingle', () => {
},
};
- beforeEach(() => {
- LHNTestUtils.getDefaultRenderedReportActionItemSingle(shouldShowSubscriptAvatar, fakeReport, fakeReportAction);
- });
-
function setup() {
+ LHNTestUtils.getDefaultRenderedReportActionItemSingle(shouldShowSubscriptAvatar, fakeReport, fakeReportAction);
const policyCollectionDataSet = toCollectionDataSet(ONYXKEYS.COLLECTION.POLICY, [fakePolicy], (item) => item.id);
return waitForBatchedUpdates().then(() =>
@@ -70,13 +66,12 @@ describe('ReportActionItemSingle', () => {
);
}
- it('renders secondary Avatar properly', () => {
+ it('renders secondary Avatar properly', async () => {
const expectedSecondaryIconTestId = 'SvgDefaultAvatar_w Icon';
- return setup().then(() => {
- waitFor(() => {
- expect(screen.getByTestId(expectedSecondaryIconTestId)).toBeDefined();
- });
+ await setup();
+ await waitFor(() => {
+ expect(screen.getByTestId(expectedSecondaryIconTestId)).toBeOnTheScreen();
});
});
@@ -84,7 +79,7 @@ describe('ReportActionItemSingle', () => {
const [expectedPerson] = fakeReportAction.person ?? [];
return setup().then(() => {
- expect(screen.getByText(expectedPerson.text ?? '')).toBeDefined();
+ expect(screen.getByText(expectedPerson.text ?? '')).toBeOnTheScreen();
});
});
});
diff --git a/tests/unit/SidebarFilterTest.ts b/tests/unit/SidebarFilterTest.ts
index 3f8678fa8cf9..09ad1c379a09 100644
--- a/tests/unit/SidebarFilterTest.ts
+++ b/tests/unit/SidebarFilterTest.ts
@@ -1,4 +1,4 @@
-import {cleanup, screen} from '@testing-library/react-native';
+import {screen} from '@testing-library/react-native';
import Onyx from 'react-native-onyx';
import DateUtils from '@libs/DateUtils';
import * as Localize from '@libs/Localize';
@@ -45,11 +45,8 @@ xdescribe('Sidebar', () => {
return Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false});
});
- // Cleanup (ie. unmount) all rendered components and clear out Onyx after each test so that each test starts with a clean slate
- afterEach(() => {
- cleanup();
- return Onyx.clear();
- });
+ // clear out Onyx after each test so that each test starts with a clean slate
+ afterEach(() => Onyx.clear());
describe('in default (most recent) mode', () => {
it('excludes a report with no participants', () => {
@@ -365,7 +362,7 @@ xdescribe('Sidebar', () => {
const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
expect(screen.queryAllByAccessibilityHint(navigatesToChatHintText)).toHaveLength(1);
expect(displayNames).toHaveLength(1);
- expect(displayNames[0].props.children[0]).toBe('Three, Four');
+ expect(screen.getByText('One, Two')).toBeOnTheScreen();
} else {
// Both reports visible
const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
@@ -410,8 +407,9 @@ xdescribe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(2);
- expect(displayNames[0].props.children[0]).toBe('One, Two');
- expect(displayNames[1].props.children[0]).toBe('Three, Four');
+
+ expect(screen.getByText('One, Two')).toBeOnTheScreen();
+ expect(screen.getByText('Three, Four')).toBeOnTheScreen();
})
// When report3 becomes unread
@@ -484,8 +482,9 @@ xdescribe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(2);
- expect(displayNames[0].props.children[0]).toBe('Three, Four');
- expect(displayNames[1].props.children[0]).toBe('One, Two');
+
+ expect(screen.getByText('One, Two')).toBeOnTheScreen();
+ expect(screen.getByText('Three, Four')).toBeOnTheScreen();
})
);
});
@@ -711,7 +710,7 @@ xdescribe('Sidebar', () => {
const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
expect(screen.queryAllByAccessibilityHint(navigatesToChatHintText)).toHaveLength(1);
expect(displayNames).toHaveLength(1);
- expect(displayNames[0].props.children[0]).toBe('Three, Four');
+ expect(screen.getByText('One, Two')).toBeOnTheScreen();
} else {
// Both reports visible
const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
diff --git a/tests/unit/SidebarOrderTest.ts b/tests/unit/SidebarOrderTest.ts
index fd39d4efef65..644bba5a589b 100644
--- a/tests/unit/SidebarOrderTest.ts
+++ b/tests/unit/SidebarOrderTest.ts
@@ -1,4 +1,4 @@
-import {cleanup, screen} from '@testing-library/react-native';
+import {screen} from '@testing-library/react-native';
import Onyx from 'react-native-onyx';
import * as Report from '@libs/actions/Report';
import DateUtils from '@libs/DateUtils';
@@ -48,7 +48,6 @@ describe('Sidebar', () => {
// Clear out Onyx after each test so that each test starts with a clean slate
afterEach(() => {
- cleanup();
Onyx.clear();
});
@@ -149,11 +148,11 @@ describe('Sidebar', () => {
.then(() => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
-
expect(displayNames).toHaveLength(3);
- expect(displayNames[0].props.children[0]).toBe('Five, Six');
- expect(displayNames[1].props.children[0]).toBe('Three, Four');
- expect(displayNames[2].props.children[0]).toBe('One, Two');
+
+ expect(displayNames[0]).toHaveTextContent('Five, Six');
+ expect(displayNames[1]).toHaveTextContent('Three, Four');
+ expect(displayNames[2]).toHaveTextContent('One, Two');
})
);
});
@@ -204,9 +203,9 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(displayNames[0].props.children[0]).toBe('One, Two'); // this has `hasDraft` flag enabled so it will be on top
- expect(displayNames[1].props.children[0]).toBe('Five, Six');
- expect(displayNames[2].props.children[0]).toBe('Three, Four');
+ expect(displayNames[0]).toHaveTextContent('One, Two'); // this has `hasDraft` flag enabled so it will be on top
+ expect(displayNames[1]).toHaveTextContent('Five, Six');
+ expect(displayNames[2]).toHaveTextContent('Three, Four');
})
);
});
@@ -255,9 +254,9 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(displayNames[0].props.children[0]).toBe('One, Two');
- expect(displayNames[1].props.children[0]).toBe('Five, Six');
- expect(displayNames[2].props.children[0]).toBe('Three, Four');
+ expect(displayNames[0]).toHaveTextContent('One, Two');
+ expect(displayNames[1]).toHaveTextContent('Five, Six');
+ expect(displayNames[2]).toHaveTextContent('Three, Four');
})
);
});
@@ -309,10 +308,10 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(4);
- expect(displayNames[0].props.children[0]).toBe(taskReportName);
- expect(displayNames[1].props.children[0]).toBe('Five, Six');
- expect(displayNames[2].props.children[0]).toBe('Three, Four');
- expect(displayNames[3].props.children[0]).toBe('One, Two');
+ expect(displayNames[0]).toHaveTextContent(taskReportName);
+ expect(displayNames[1]).toHaveTextContent('Five, Six');
+ expect(displayNames[2]).toHaveTextContent('Three, Four');
+ expect(displayNames[3]).toHaveTextContent('One, Two');
})
);
});
@@ -373,10 +372,10 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(4);
- expect(displayNames[0].props.children[0]).toBe('Email Two owes $100.00');
- expect(displayNames[1].props.children[0]).toBe('Five, Six');
- expect(displayNames[2].props.children[0]).toBe('Three, Four');
- expect(displayNames[3].props.children[0]).toBe('One, Two');
+ expect(displayNames[0]).toHaveTextContent('Email Two owes $100.00');
+ expect(displayNames[1]).toHaveTextContent('Five, Six');
+ expect(displayNames[2]).toHaveTextContent('Three, Four');
+ expect(displayNames[3]).toHaveTextContent('One, Two');
})
);
});
@@ -441,10 +440,10 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(4);
- expect(displayNames[0].props.children[0]).toBe('Workspace owes $100.00');
- expect(displayNames[1].props.children[0]).toBe('Email Five');
- expect(displayNames[2].props.children[0]).toBe('Three, Four');
- expect(displayNames[3].props.children[0]).toBe('One, Two');
+ expect(displayNames[0]).toHaveTextContent('Workspace owes $100.00');
+ expect(displayNames[1]).toHaveTextContent('Email Five');
+ expect(displayNames[2]).toHaveTextContent('Three, Four');
+ expect(displayNames[3]).toHaveTextContent('One, Two');
})
);
});
@@ -500,9 +499,9 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(displayNames[0].props.children[0]).toBe('Three, Four');
- expect(displayNames[1].props.children[0]).toBe('Five, Six');
- expect(displayNames[2].props.children[0]).toBe('One, Two');
+ expect(displayNames[0]).toHaveTextContent('Three, Four');
+ expect(displayNames[1]).toHaveTextContent('Five, Six');
+ expect(displayNames[2]).toHaveTextContent('One, Two');
})
);
});
@@ -657,9 +656,9 @@ describe('Sidebar', () => {
expect(displayNames).toHaveLength(3);
expect(screen.queryAllByTestId('Pin Icon')).toHaveLength(1);
expect(screen.queryAllByTestId('Pencil Icon')).toHaveLength(1);
- expect(displayNames[0].props.children[0]).toBe('Email Two owes $100.00');
- expect(displayNames[1].props.children[0]).toBe('One, Two');
- expect(displayNames[2].props.children[0]).toBe('Three, Four');
+ expect(displayNames[0]).toHaveTextContent('Email Two owes $100.00');
+ expect(displayNames[1]).toHaveTextContent('One, Two');
+ expect(displayNames[2]).toHaveTextContent('Three, Four');
})
);
});
@@ -709,9 +708,9 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(displayNames[0].props.children[0]).toBe('Five, Six');
- expect(displayNames[1].props.children[0]).toBe('One, Two');
- expect(displayNames[2].props.children[0]).toBe('Three, Four');
+ expect(displayNames[0]).toHaveTextContent('Five, Six');
+ expect(displayNames[1]).toHaveTextContent('One, Two');
+ expect(displayNames[2]).toHaveTextContent('Three, Four');
})
// When a new report is added
@@ -722,10 +721,10 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(4);
- expect(displayNames[0].props.children[0]).toBe('Five, Six');
- expect(displayNames[1].props.children[0]).toBe('One, Two');
- expect(displayNames[2].props.children[0]).toBe('Seven, Eight');
- expect(displayNames[3].props.children[0]).toBe('Three, Four');
+ expect(displayNames[0]).toHaveTextContent('Five, Six');
+ expect(displayNames[1]).toHaveTextContent('One, Two');
+ expect(displayNames[2]).toHaveTextContent('Seven, Eight');
+ expect(displayNames[3]).toHaveTextContent('Three, Four');
})
);
});
@@ -778,9 +777,9 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(displayNames[0].props.children[0]).toBe('Five, Six');
- expect(displayNames[1].props.children[0]).toBe('One, Two');
- expect(displayNames[2].props.children[0]).toBe('Three, Four');
+ expect(displayNames[0]).toHaveTextContent('Five, Six');
+ expect(displayNames[1]).toHaveTextContent('One, Two');
+ expect(displayNames[2]).toHaveTextContent('Three, Four');
})
// When a new report is added
@@ -798,10 +797,10 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(4);
- expect(displayNames[0].props.children[0]).toBe('Five, Six');
- expect(displayNames[1].props.children[0]).toBe('One, Two');
- expect(displayNames[2].props.children[0]).toBe('Seven, Eight');
- expect(displayNames[3].props.children[0]).toBe('Three, Four');
+ expect(displayNames[0]).toHaveTextContent('Five, Six');
+ expect(displayNames[1]).toHaveTextContent('One, Two');
+ expect(displayNames[2]).toHaveTextContent('Seven, Eight');
+ expect(displayNames[3]).toHaveTextContent('Three, Four');
})
);
});
@@ -851,9 +850,9 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(displayNames[0].props.children[0]).toBe('Five, Six');
- expect(displayNames[1].props.children[0]).toBe('Three, Four');
- expect(displayNames[2].props.children[0]).toBe('Report (archived)');
+ expect(displayNames[0]).toHaveTextContent('Five, Six');
+ expect(displayNames[1]).toHaveTextContent('Three, Four');
+ expect(displayNames[2]).toHaveTextContent('Report (archived)');
})
);
});
@@ -892,9 +891,9 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(displayNames[0].props.children[0]).toBe('Five, Six');
- expect(displayNames[1].props.children[0]).toBe('One, Two');
- expect(displayNames[2].props.children[0]).toBe('Three, Four');
+ expect(displayNames[0]).toHaveTextContent('Five, Six');
+ expect(displayNames[1]).toHaveTextContent('One, Two');
+ expect(displayNames[2]).toHaveTextContent('Three, Four');
})
// When a new report is added
@@ -905,10 +904,10 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(4);
- expect(displayNames[0].props.children[0]).toBe('Five, Six');
- expect(displayNames[1].props.children[0]).toBe('One, Two');
- expect(displayNames[2].props.children[0]).toBe('Seven, Eight');
- expect(displayNames[3].props.children[0]).toBe('Three, Four');
+ expect(displayNames[0]).toHaveTextContent('Five, Six');
+ expect(displayNames[1]).toHaveTextContent('One, Two');
+ expect(displayNames[2]).toHaveTextContent('Seven, Eight');
+ expect(displayNames[3]).toHaveTextContent('Three, Four');
})
);
});
@@ -953,9 +952,9 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(displayNames[0].props.children[0]).toBe('Five, Six');
- expect(displayNames[1].props.children[0]).toBe('Three, Four');
- expect(displayNames[2].props.children[0]).toBe('Report (archived)');
+ expect(displayNames[0]).toHaveTextContent('Five, Six');
+ expect(displayNames[1]).toHaveTextContent('Three, Four');
+ expect(displayNames[2]).toHaveTextContent('Report (archived)');
})
);
});
@@ -1094,11 +1093,11 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(5);
- expect(displayNames[0].props.children[0]).toBe('Email Five owes $100.00');
- expect(displayNames[1].props.children[0]).toBe('Email Four owes $1,000.00');
- expect(displayNames[2].props.children[0]).toBe('Email Six owes $100.00');
- expect(displayNames[3].props.children[0]).toBe('Email Three owes $100.00');
- expect(displayNames[4].props.children[0]).toBe('Email Two owes $100.00');
+ expect(displayNames[0]).toHaveTextContent('Email Five owes $100.00');
+ expect(displayNames[1]).toHaveTextContent('Email Four owes $1,000.00');
+ expect(displayNames[2]).toHaveTextContent('Email Six owes $100.00');
+ expect(displayNames[3]).toHaveTextContent('Email Three owes $100.00');
+ expect(displayNames[4]).toHaveTextContent('Email Two owes $100.00');
})
);
});
@@ -1149,9 +1148,9 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(displayNames[0].props.children[0]).toBe('Five, Six');
- expect(displayNames[1].props.children[0]).toBe('One, Two');
- expect(displayNames[2].props.children[0]).toBe('Three, Four');
+ expect(displayNames[0]).toHaveTextContent('Five, Six');
+ expect(displayNames[1]).toHaveTextContent('One, Two');
+ expect(displayNames[2]).toHaveTextContent('Three, Four');
})
);
});
diff --git a/tests/unit/SidebarTest.ts b/tests/unit/SidebarTest.ts
index 23ea0d377634..c037c1ced3f0 100644
--- a/tests/unit/SidebarTest.ts
+++ b/tests/unit/SidebarTest.ts
@@ -1,4 +1,4 @@
-import {cleanup, screen} from '@testing-library/react-native';
+import {screen} from '@testing-library/react-native';
import Onyx from 'react-native-onyx';
import CONST from '@src/CONST';
import * as Localize from '@src/libs/Localize';
@@ -31,7 +31,6 @@ describe('Sidebar', () => {
// Clear out Onyx after each test so that each test starts with a clean slate
afterEach(() => {
- cleanup();
Onyx.clear();
});
@@ -79,11 +78,11 @@ describe('Sidebar', () => {
.then(() => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
- expect(displayNames[0].props.children[0]).toBe('Report (archived)');
+ expect(displayNames[0]).toHaveTextContent('Report (archived)');
const hintMessagePreviewText = Localize.translateLocal('accessibilityHints.lastChatMessagePreview');
const messagePreviewTexts = screen.queryAllByLabelText(hintMessagePreviewText);
- expect(messagePreviewTexts[0].props.children).toBe('This chat room has been archived.');
+ expect(messagePreviewTexts[0]).toHaveTextContent('This chat room has been archived.');
})
);
});
@@ -131,11 +130,11 @@ describe('Sidebar', () => {
.then(() => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
- expect(displayNames[0].props.children[0]).toBe('Report (archived)');
+ expect(displayNames[0]).toHaveTextContent('Report (archived)');
const hintMessagePreviewText = Localize.translateLocal('accessibilityHints.lastChatMessagePreview');
const messagePreviewTexts = screen.queryAllByLabelText(hintMessagePreviewText);
- expect(messagePreviewTexts[0].props.children).toBe('This chat is no longer active because Vikings Policy is no longer an active workspace.');
+ expect(messagePreviewTexts[0]).toHaveTextContent('This chat is no longer active because Vikings Policy is no longer an active workspace.');
})
);
});
diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx
index e3daa93a3179..abfaf9bd8b00 100644
--- a/tests/utils/LHNTestUtils.tsx
+++ b/tests/utils/LHNTestUtils.tsx
@@ -248,7 +248,7 @@ function getFakePolicy(id = '1', name = 'Workspace-Test-001'): Policy {
type: 'free',
owner: 'myuser@gmail.com',
outputCurrency: 'BRL',
- avatar: '',
+ avatarURL: '',
employeeList: {},
isPolicyExpenseChatEnabled: true,
lastModified: '1697323926777105',
diff --git a/tests/utils/collections/policies.ts b/tests/utils/collections/policies.ts
index 8dd04f4750a9..5507c9e75436 100644
--- a/tests/utils/collections/policies.ts
+++ b/tests/utils/collections/policies.ts
@@ -20,7 +20,7 @@ export default function createRandomPolicy(index: number): Policy {
role: rand(Object.values(CONST.POLICY.ROLE)),
owner: randEmail(),
ownerAccountID: index,
- avatar: randAvatar(),
+ avatarURL: randAvatar(),
isFromFullPolicy: randBoolean(),
lastModified: randPastDate().toISOString(),
pendingAction: rand(Object.values(CONST.RED_BRICK_ROAD_PENDING_ACTION)),