diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml
index 4031d6c0c119..34a5c356356e 100644
--- a/.github/workflows/cla.yml
+++ b/.github/workflows/cla.yml
@@ -4,39 +4,9 @@ on:
issue_comment:
types: [created]
pull_request_target:
- types: [opened, synchronize]
+ types: [opened, closed, synchronize]
jobs:
CLA:
- runs-on: ubuntu-latest
- # This job only runs for pull request comments or pull request target events (not issue comments)
- # It does not run for pull requests created by OSBotify
- if: ${{ github.event.issue.pull_request || (github.event_name == 'pull_request_target' && github.event.pull_request.user.login != 'OSBotify' && github.event.pull_request.user.login != 'imgbot[bot]') }}
- steps:
- - name: CLA comment check
- uses: actions-ecosystem/action-regex-match@9c35fe9ac1840239939c59e5db8839422eed8a73
- id: sign
- with:
- text: ${{ github.event.comment.body }}
- regex: '\s*I have read the CLA Document and I hereby sign the CLA\s*'
- - name: CLA comment re-check
- uses: actions-ecosystem/action-regex-match@9c35fe9ac1840239939c59e5db8839422eed8a73
- id: recheck
- with:
- text: ${{ github.event.comment.body }}
- regex: '\s*recheck\s*'
- - name: CLA Assistant
- if: ${{ steps.recheck.outputs.match != '' || steps.sign.outputs.match != '' || github.event_name == 'pull_request_target' }}
- # Version: 2.1.2-beta
- uses: cla-assistant/github-action@948230deb0d44dd38957592f08c6bd934d96d0cf
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- PERSONAL_ACCESS_TOKEN : ${{ secrets.CLA_BOTIFY_TOKEN }}
- with:
- path-to-signatures: '${{ github.repository }}/cla.json'
- path-to-document: 'https://github.com/${{ github.repository }}/blob/main/contributingGuides/CLA.md'
- branch: 'main'
- remote-organization-name: 'Expensify'
- remote-repository-name: 'CLA'
- lock-pullrequest-aftermerge: false
- allowlist: OSBotify,snyk-bot
+ uses: Expensify/GitHub-Actions/.github/workflows/cla.yml@main
+ secrets: inherit
diff --git a/android/app/build.gradle b/android/app/build.gradle
index b07de7a4da33..40de69b4b7be 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -110,8 +110,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1009005001
- versionName "9.0.50-1"
+ versionCode 1009005101
+ versionName "9.0.51-1"
// Supported language variants must be declared here to avoid from being removed during the compilation.
// This also helps us to not include unnecessary language variants in the APK.
resConfigs "en", "es"
diff --git a/docs/articles/expensify-classic/connections/netsuite/Connect-To-NetSuite.md b/docs/articles/expensify-classic/connections/netsuite/Connect-To-NetSuite.md
index 1f96d9b8a633..6cc69fccccc1 100644
--- a/docs/articles/expensify-classic/connections/netsuite/Connect-To-NetSuite.md
+++ b/docs/articles/expensify-classic/connections/netsuite/Connect-To-NetSuite.md
@@ -1,12 +1,11 @@
---
title: NetSuite
-description: Set up the direct connection from Expensify to NetSuite.
+description: Connect NetSuite to Expensify for streamlined expense reporting and accounting integration.
order: 1
---
-# Overview
-Expensify's integration with NetSuite allows you to automate report exports, tailor your coding preferences, and tap into NetSuite's array of advanced features. By correctly configuring your NetSuite settings in Expensify, you can leverage the connection's settings to automate most of the tasks, making your workflow more efficient.
+Expensify's direct integration with NetSuite allows you to automate report exports, tailor your coding preferences, and tap into NetSuite's array of advanced features.
-**Before connecting NetSuite to Expensify, a few things to note:**
+## Before connecting NetSuite to Expensify, review the following details:
- Token-based authentication works by ensuring that each request to NetSuite is accompanied by a signed token which is verified for authenticity
- You must be able to login to NetSuite as an administrator to initiate the connection
- You must have a Control Plan in Expensify to integrate with NetSuite
@@ -15,9 +14,7 @@ Expensify's integration with NetSuite allows you to automate report exports, tai
- Ensure that your workspace's report output currency setting matches the NetSuite Subsidiary default currency
- Make sure your page size is set to 1000 for importing your customers and vendors. You can check this in NetSuite under **Setup > Integration > Web Services Preferences > 'Search Page Size'**
-# Connect to NetSuite
-
-## Step 1: Install the Expensify Bundle in NetSuite
+# Step 1: Install the Expensify Bundle in NetSuite
1. While logged into NetSuite as an administrator, go to Customization > SuiteBundler > Search & Install Bundles, then search for "Expensify"
2. Click on the Expensify Connect bundle (Bundle ID 283395)
@@ -25,13 +22,13 @@ Expensify's integration with NetSuite allows you to automate report exports, tai
4. If you already have the Expensify Connect bundle installed, head to _Customization > SuiteBundler > Search & Install Bundles > List_ and update it to the latest version
5. Select **Show on Existing Custom Forms** for all available fields
-## Step 2: Enable Token-Based Authentication
+# Step 2: Enable Token-Based Authentication
1. Head to _Setup > Company > Enable Features > SuiteCloud > Manage Authentication_
2. Make sure “Token Based Authentication” is enabled
3. Click **Save**
-## Step 3: Add Expensify Integration Role to a User
+# Step 3: Add Expensify Integration Role to a User
The user you select must have access to at least the permissions included in the Expensify Integration Role, but they're not required to be an Admin.
1. In NetSuite, head to Lists > Employees, and find the user you want to add the Expensify Integration role to
@@ -40,7 +37,7 @@ The user you select must have access to at least the permissions included in the
Remember that Tokens are linked to a User and a Role, not solely to a User. It's important to note that you cannot establish a connection with tokens using one role and then switch to another role afterward. Once you've initiated a connection with tokens, you must continue using the same token/user/role combination for all subsequent sync or export actions.
-## Step 4: Create Access Tokens
+# Step 4: Create Access Tokens
1. Using Global Search in NetSuite, enter “page: tokens”
2. Click **New Access Token**
@@ -49,21 +46,20 @@ Remember that Tokens are linked to a User and a Role, not solely to a User. It's
5. Press **Save**
6. Copy and Paste the token and token ID to a saved location on your computer (this is the only time you will see these details)
-## Step 5: Confirm Expense Reports are Enabled in NetSuite.
+# Step 5: Confirm Expense Reports are Enabled in NetSuite.
Enabling Expense Reports is required as part of Expensify's integration with NetSuite:
1. Logged into NetSuite as an administrator, go to Setup > Company > Enable Features > Employees
2. Confirm the checkbox next to Expense Reports is checked
3. If not, click the checkbox and then Save to enable Expense Reports
-## Step 6: Confirm Expense Categories are set up in NetSuite.
+# Step 6: Confirm Expense Categories are set up in NetSuite.
Once Expense Reports are enabled, Expense Categories can be set up in NetSuite. Expense Categories are an alias for General Ledger accounts used to code expenses.
-
1. Logged into NetSuite as an administrator, go to Setup > Accounting > Expense Categories (a list of Expense Categories should show)
2. If no Expense Categories are visible, click **New** to create new ones
-## Step 7: Confirm Journal Entry Transaction Forms are Configured Properly
+# Step 7: Confirm Journal Entry Transaction Forms are Configured Properly
1. Logged into NetSuite as an administrator, go to _Customization > Forms > Transaction Forms_
2. Click **Customize** or **Edit** next to the Standard Journal Entry form
@@ -71,7 +67,7 @@ Once Expense Reports are enabled, Expense Categories can be set up in NetSuite.
4. Click the sub-header Lines and verify that the "Show" column for "Receipt URL" is checked
5. Go to _Customization > Forms > Transaction Forms_ and ensure all other transaction forms with the journal type have this same configuration
-## Step 8: Confirm Expense Report Transaction Forms are Configured Properly
+# Step 8: Confirm Expense Report Transaction Forms are Configured Properly
1. Logged into NetSuite as an administrator, go to _Customization > Forms > Transaction Forms_
2. Click **Customize** or **Edit** next to the Standard Expense Report form, then click **Screen Fields > Main**
@@ -79,7 +75,7 @@ Once Expense Reports are enabled, Expense Categories can be set up in NetSuite.
4. Click the second sub-header, Expenses, and verify that the 'Show' column for 'Receipt URL' is checked
5. Go to _Customization > Forms > Transaction Forms_ and ensure all other transaction forms with the expense report type have this same configuration
-## Step 9: Confirm Vendor Bill Transactions Forms are Configured Properly
+# Step 9: Confirm Vendor Bill Transactions Forms are Configured Properly
1. Logged into NetSuite as an administrator, go to _Customization > Forms > Transaction Forms_
2. Click **Customize** or **Edit** next to your preferred Vendor Bill form
@@ -87,20 +83,20 @@ Once Expense Reports are enabled, Expense Categories can be set up in NetSuite.
4. Under the Expenses sub-header (make sure to click the "Expenses" sub-header at the very bottom and not "Expenses & Items"), ensure "Show" is checked for Receipt URL, Department, Location, and Class
5. Go to _Customization > Forms > Transaction Forms_ and provide all other transaction forms with the vendor bill type have this same configuration
-## Step 10: Confirm Vendor Credit Transactions Forms are Configured Properly
+# Step 10: Confirm Vendor Credit Transactions Forms are Configured Properly
1. While logged in as an administrator, go to _Customization > Forms > Transaction Forms_
2. Click **Customize** or **Edit** next to your preferred Vendor Credit form, then click _Screen Fields > Main_ and verify that the "Created From" label has "Show" checked and that Departments, Classes, and Locations have the "Show" label unchecked
3. Under the Expenses sub-header (make sure to click the "Expenses" sub-header at the very bottom and not "Expenses & Items"), ensure "Show" is checked for Receipt URL, Department, Location, and Class
4. Go to _Customization > Forms > Transaction Forms_ and ensure all other transaction forms with the vendor credit type have this same configuration
-## Step 11: Set up Tax Groups (only applicable if tracking taxes)
+# Step 11: Set up Tax Groups (only applicable if tracking taxes)
Expensify imports NetSuite Tax Groups (not Tax Codes), which you can find in NetSuite under _Setup > Accounting > Tax Groups_.
Tax Groups are an alias for Tax Codes in NetSuite and can contain one or more Tax Codes (Please note: for UK and Ireland subsidiaries, please ensure your Tax Groups do not have more than one Tax Code). We recommend naming Tax Groups so your employees can easily understand them, as the name and rate will be displayed in Expensify.
-Before importing NetSuite Tax Groups into Expensify:
+## Before importing NetSuite Tax Groups into Expensify:
1. Create your Tax Groups in NetSuite by going to _Setup > Accounting > Tax Groups_
2. Click **New**
3. Select the country for your Tax Group
@@ -115,9 +111,9 @@ Ensure Tax Groups can be applied to expenses by going to _Setup > Accounting > S
If this field does not display, it’s not needed for that specific country.
-## Step 12: Connect Expensify and NetSuite
+# Step 12: Connect Expensify and NetSuite
-1. Log into Expensify as a Policy Admin and go to **Settings > Workspaces > _[Workspace Name]_ > Connections > NetSuite**
+1. Log into Expensify as a Workspace Admin and go to **Settings > Workspaces > _[Workspace Name]_ > Connections > NetSuite**
2. Click **Connect to NetSuite**
3. Enter your Account ID (Account ID can be found in NetSuite by going to _Setup > Integration > Web Services Preferences_)
4. Then, enter the token and token secret
diff --git a/docs/assets/images/NetSuite_Configure_06.png b/docs/assets/images/NetSuite_Configure_06.png
new file mode 100644
index 000000000000..cddfe2fabcd6
Binary files /dev/null and b/docs/assets/images/NetSuite_Configure_06.png differ
diff --git a/docs/assets/images/NetSuite_Configure_08.png b/docs/assets/images/NetSuite_Configure_08.png
new file mode 100644
index 000000000000..77690a2c3aa1
Binary files /dev/null and b/docs/assets/images/NetSuite_Configure_08.png differ
diff --git a/docs/assets/images/NetSuite_Configure_09.png b/docs/assets/images/NetSuite_Configure_09.png
new file mode 100644
index 000000000000..8da56f22838d
Binary files /dev/null and b/docs/assets/images/NetSuite_Configure_09.png differ
diff --git a/docs/assets/images/NetSuite_Configure_Advanced_10.png b/docs/assets/images/NetSuite_Configure_Advanced_10.png
new file mode 100644
index 000000000000..23fe99498052
Binary files /dev/null and b/docs/assets/images/NetSuite_Configure_Advanced_10.png differ
diff --git a/docs/assets/images/NetSuite_Connect_Bundle_02.png b/docs/assets/images/NetSuite_Connect_Bundle_02.png
new file mode 100644
index 000000000000..c015178873ad
Binary files /dev/null and b/docs/assets/images/NetSuite_Connect_Bundle_02.png differ
diff --git a/docs/assets/images/NetSuite_Connect_Categories_05.png b/docs/assets/images/NetSuite_Connect_Categories_05.png
new file mode 100644
index 000000000000..e71341170129
Binary files /dev/null and b/docs/assets/images/NetSuite_Connect_Categories_05.png differ
diff --git a/docs/assets/images/NetSuite_Connect_Customization_01.png b/docs/assets/images/NetSuite_Connect_Customization_01.png
new file mode 100644
index 000000000000..8a0c53b45d7f
Binary files /dev/null and b/docs/assets/images/NetSuite_Connect_Customization_01.png differ
diff --git a/docs/assets/images/NetSuite_Connect_Expense_Reports_03.png b/docs/assets/images/NetSuite_Connect_Expense_Reports_03.png
new file mode 100644
index 000000000000..44c8fe6c993d
Binary files /dev/null and b/docs/assets/images/NetSuite_Connect_Expense_Reports_03.png differ
diff --git a/docs/assets/images/NetSuite_Expense_Categories_04.png b/docs/assets/images/NetSuite_Expense_Categories_04.png
new file mode 100644
index 000000000000..d13e9f95cfea
Binary files /dev/null and b/docs/assets/images/NetSuite_Expense_Categories_04.png differ
diff --git a/docs/assets/images/NetSuite_HelpScreenshot_07.png b/docs/assets/images/NetSuite_HelpScreenshot_07.png
new file mode 100644
index 000000000000..55cfe532f890
Binary files /dev/null and b/docs/assets/images/NetSuite_HelpScreenshot_07.png differ
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 1cd1cdf3ee77..36a986b37b89 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 9.0.50
+ 9.0.51
CFBundleSignature
????
CFBundleURLTypes
@@ -40,7 +40,7 @@
CFBundleVersion
- 9.0.50.1
+ 9.0.51.1
FullStory
OrgId
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 7fd36223c03d..f5bd7a20c34a 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 9.0.50
+ 9.0.51
CFBundleSignature
????
CFBundleVersion
- 9.0.50.1
+ 9.0.51.1
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index 078041a521b9..7c5da25860f0 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -11,9 +11,9 @@
CFBundleName
$(PRODUCT_NAME)
CFBundleShortVersionString
- 9.0.50
+ 9.0.51
CFBundleVersion
- 9.0.50.1
+ 9.0.51.1
NSExtension
NSExtensionPointIdentifier
diff --git a/package-lock.json b/package-lock.json
index b7e42c99e7d0..2ef609e9ef94 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "9.0.50-1",
+ "version": "9.0.51-1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "9.0.50-1",
+ "version": "9.0.51-1",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 1705abd4cce5..bad9d3b31469 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "9.0.50-1",
+ "version": "9.0.51-1",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
diff --git a/src/CONST.ts b/src/CONST.ts
index 171dc7ff2c8a..84003710938a 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -5,18 +5,11 @@ import Config from 'react-native-config';
import * as KeyCommand from 'react-native-key-command';
import type {ValueOf} from 'type-fest';
import type {Video} from './libs/actions/Report';
+import type {MileageRate} from './libs/DistanceRequestUtils';
import BankAccount from './libs/models/BankAccount';
import * as Url from './libs/Url';
import SCREENS from './SCREENS';
import type PlaidBankAccount from './types/onyx/PlaidBankAccount';
-import type {Unit} from './types/onyx/Policy';
-
-type RateAndUnit = {
- unit: Unit;
- rate: number;
- currency: string;
-};
-type CurrencyDefaultMileageRate = Record;
// Creating a default array and object this way because objects ({}) and arrays ([]) are not stable types.
// Freezing the array ensures that it cannot be unintentionally modified.
@@ -754,6 +747,11 @@ const CONST = {
DELAYED_SUBMISSION_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/reports/Automatically-submit-employee-reports',
// Use Environment.getEnvironmentURL to get the complete URL with port number
DEV_NEW_EXPENSIFY_URL: 'https://dev.new.expensify.com:',
+ NAVATTIC: {
+ ADMIN_TOUR: 'https://expensify.navattic.com/kh204a7',
+ EMPLOYEE_TOUR: 'https://expensify.navattic.com/35609gb',
+ },
+
OLDDOT_URLS: {
ADMIN_POLICIES_URL: 'admin_policies',
ADMIN_DOMAINS_URL: 'admin_domains',
@@ -825,6 +823,7 @@ const CONST = {
CARD_MISSING_ADDRESS: 'CARDMISSINGADDRESS',
CARD_ISSUED: 'CARDISSUED',
CARD_ISSUED_VIRTUAL: 'CARDISSUEDVIRTUAL',
+ CARD_ASSIGNED: 'CARDASSIGNED',
CHANGE_FIELD: 'CHANGEFIELD', // OldDot Action
CHANGE_POLICY: 'CHANGEPOLICY', // OldDot Action
CHANGE_TYPE: 'CHANGETYPE', // OldDot Action
@@ -1131,6 +1130,9 @@ const CONST = {
SEARCH_OPTION_LIST_DEBOUNCE_TIME: 300,
RESIZE_DEBOUNCE_TIME: 100,
UNREAD_UPDATE_DEBOUNCE_TIME: 300,
+ SEARCH_CONVERT_SEARCH_VALUES: 'search_convert_search_values',
+ SEARCH_MAKE_TREE: 'search_make_tree',
+ SEARCH_BUILD_TREE: 'search_build_tree',
SEARCH_FILTER_OPTIONS: 'search_filter_options',
USE_DEBOUNCED_STATE_DELAY: 300,
},
@@ -1492,14 +1494,18 @@ const CONST = {
EXPORTER: 'exporter',
MARK_CHECKS_TO_BE_PRINTED: 'markChecksToBePrinted',
REIMBURSABLE_ACCOUNT: 'reimbursableAccount',
+ NON_REIMBURSABLE_ACCOUNT: 'nonReimbursableAccount',
REIMBURSABLE: 'reimbursable',
+ NON_REIMBURSABLE: 'nonReimbursable',
+ SHOULD_AUTO_CREATE_VENDOR: 'shouldAutoCreateVendor',
+ NON_REIMBURSABLE_BILL_DEFAULT_VENDOR: 'nonReimbursableBillDefaultVendor',
AUTO_SYNC: 'autoSync',
ENABLE_NEW_CATEGORIES: 'enableNewCategories',
- SHOULD_AUTO_CREATE_VENDOR: 'shouldAutoCreateVendor',
MAPPINGS: {
CLASSES: 'classes',
CUSTOMERS: 'customers',
},
+ IMPORT_ITEMS: 'importItems',
},
QUICKBOOKS_CONFIG: {
@@ -1614,7 +1620,6 @@ const CONST = {
VENDOR_BILL: 'VENDOR_BILL',
CHECK: 'CHECK',
JOURNAL_ENTRY: 'JOURNAL_ENTRY',
- NOTHING: 'NOTHING',
},
SAGE_INTACCT_REIMBURSABLE_EXPENSE_TYPE: {
@@ -1889,7 +1894,7 @@ const CONST = {
QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE: {
CREDIT_CARD: 'CREDIT_CARD_CHARGE',
- JOURNAL_ENTRY: 'JOURNAL_ENTRY',
+ CHECK: 'CHECK',
VENDOR_BILL: 'VENDOR_BILL',
},
@@ -2382,6 +2387,7 @@ const CONST = {
SYNC_STAGE_NAME: {
STARTING_IMPORT_QBO: 'startingImportQBO',
STARTING_IMPORT_XERO: 'startingImportXero',
+ STARTING_IMPORT_QBD: 'startingImportQBD',
QBO_IMPORT_MAIN: 'quickbooksOnlineImportMain',
QBO_IMPORT_CUSTOMERS: 'quickbooksOnlineImportCustomers',
QBO_IMPORT_EMPLOYEES: 'quickbooksOnlineImportEmployees',
@@ -2398,6 +2404,17 @@ const CONST = {
QBO_SYNC_APPLY_CUSTOMERS: 'quickbooksOnlineSyncApplyCustomers',
QBO_SYNC_APPLY_PEOPLE: 'quickbooksOnlineSyncApplyEmployees',
QBO_SYNC_APPLY_CLASSES_LOCATIONS: 'quickbooksOnlineSyncApplyClassesLocations',
+ QBD_IMPORT_TITLE: 'quickbooksDesktopImportTitle',
+ QBD_IMPORT_ACCOUNTS: 'quickbooksDesktopImportAccounts',
+ QBD_IMPORT_APPROVE_CERTIFICATE: 'quickbooksDesktopImportApproveCertificate',
+ QBD_IMPORT_DIMENSIONS: 'quickbooksDesktopImportDimensions',
+ QBD_IMPORT_CLASSES: 'quickbooksDesktopImportClasses',
+ QBD_IMPORT_CUSTOMERS: 'quickbooksDesktopImportCustomers',
+ QBD_IMPORT_VENDORS: 'quickbooksDesktopImportVendors',
+ QBD_IMPORT_EMPLOYEES: 'quickbooksDesktopImportEmployees',
+ QBD_IMPORT_MORE: 'quickbooksDesktopImportMore',
+ QBD_IMPORT_GENERIC: 'quickbooksDesktopImportSavePolicy',
+ QBD_WEB_CONNECTOR_REMINDER: 'quickbooksDesktopWebConnectorReminder',
JOB_DONE: 'jobDone',
XERO_SYNC_STEP: 'xeroSyncStep',
XERO_SYNC_XERO_REIMBURSED_REPORTS: 'xeroSyncXeroReimbursedReports',
@@ -2466,6 +2483,8 @@ const CONST = {
DEFAULT_RATE: 'Default Rate',
RATE_DECIMALS: 3,
FAKE_P2P_ID: '_FAKE_P2P_ID_',
+ MILES_TO_KILOMETERS: 1.609344,
+ KILOMETERS_TO_MILES: 0.621371,
},
TERMS: {
@@ -2509,6 +2528,7 @@ const CONST = {
MASTER_CARD: 'cdf',
VISA: 'vcf',
AMEX: 'gl1025',
+ STRIPE: 'stripe',
},
STEP_NAMES: ['1', '2', '3', '4'],
STEP: {
@@ -2907,6 +2927,7 @@ const CONST = {
SETTINGS: 'settings',
LEAVE_ROOM: 'leaveRoom',
PRIVATE_NOTES: 'privateNotes',
+ DOWNLOAD: 'download',
EXPORT: 'export',
DELETE: 'delete',
MARK_AS_INCOMPLETE: 'markAsIncomplete',
@@ -5525,7 +5546,7 @@ const CONST = {
"rate": 2377,
"unit": "km"
}
- }`) as CurrencyDefaultMileageRate,
+ }`) as Record,
EXIT_SURVEY: {
REASONS: {
@@ -5683,7 +5704,6 @@ const CONST = {
KEYWORD: 'keyword',
IN: 'in',
},
- EMPTY_VALUE: 'none',
},
REFERRER: {
@@ -5768,6 +5788,14 @@ const CONST = {
description: `workspace.upgrade.${this.POLICY.CONNECTIONS.NAME.SAGE_INTACCT}.description` as const,
icon: 'IntacctSquare',
},
+ [this.POLICY.CONNECTIONS.NAME.QBD]: {
+ id: this.POLICY.CONNECTIONS.NAME.QBD,
+ alias: 'qbd',
+ name: this.POLICY.CONNECTIONS.NAME_USER_FRIENDLY.quickbooksDesktop,
+ title: `workspace.upgrade.${this.POLICY.CONNECTIONS.NAME.QBD}.title` as const,
+ description: `workspace.upgrade.${this.POLICY.CONNECTIONS.NAME.QBD}.description` as const,
+ icon: 'QBDSquare',
+ },
approvals: {
id: 'approvals' as const,
alias: 'approvals' as const,
@@ -5920,7 +5948,6 @@ export type {
Country,
IOUAction,
IOUType,
- RateAndUnit,
OnboardingPurposeType,
OnboardingCompanySizeType,
IOURequestType,
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 0b69fe9be80b..14c0dc4abc50 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -441,6 +441,12 @@ const ONYXKEYS = {
/** Stores recently used currencies */
RECENTLY_USED_CURRENCIES: 'nvp_recentlyUsedCurrencies',
+ /** States whether we transitioned from OldDot to show only certain group of screens. It should be undefined on pure NewDot. */
+ IS_SINGLE_NEW_DOT_ENTRY: 'isSingleNewDotEntry',
+
+ /** Company cards custom names */
+ NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES: 'nvp_expensify_ccCustomNames',
+
/** Collection Keys */
COLLECTION: {
DOWNLOAD: 'download_',
@@ -849,7 +855,7 @@ type OnyxCollectionValuesMapping = {
[ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST]: OnyxTypes.WorkspaceCardsList;
[ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION]: OnyxTypes.PolicyConnectionName;
[ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION]: boolean;
- [ONYXKEYS.COLLECTION.LAST_SELECTED_FEED]: string;
+ [ONYXKEYS.COLLECTION.LAST_SELECTED_FEED]: OnyxTypes.CompanyCardFeed;
};
type OnyxValuesMapping = {
@@ -1001,8 +1007,10 @@ type OnyxValuesMapping = {
[ONYXKEYS.APPROVAL_WORKFLOW]: OnyxTypes.ApprovalWorkflowOnyx;
[ONYXKEYS.IMPORTED_SPREADSHEET]: OnyxTypes.ImportedSpreadsheet;
[ONYXKEYS.LAST_ROUTE]: string;
+ [ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY]: boolean | undefined;
[ONYXKEYS.IS_USING_IMPORTED_STATE]: boolean;
[ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP]: boolean;
+ [ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES]: Record;
};
type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping;
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 98ea64bc65b4..a8d562c8f244 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -669,6 +669,22 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export/date-select',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/export/date-select` as const,
},
+ POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT: {
+ route: 'settings/workspaces/:policyID/accounting/quickbooks-desktop/export/company-card-expense-account/account-select',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-desktop/export/company-card-expense-account/account-select` as const,
+ },
+ POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_SELECT: {
+ route: 'settings/workspaces/:policyID/accounting/quickbooks-desktop/export/company-card-expense-account/card-select',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-desktop/export/company-card-expense-account/card-select` as const,
+ },
+ POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_DEFAULT_VENDOR_SELECT: {
+ route: 'settings/workspaces/:policyID/accounting/quickbooks-desktop/export/company-card-expense-account/default-vendor-select',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-desktop/export/company-card-expense-account/default-vendor-select` as const,
+ },
+ POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_ACCOUNT: {
+ route: 'settings/workspaces/:policyID/accounting/quickbooks-desktop/export/company-card-expense-account',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-desktop/export/company-card-expense-account` as const,
+ },
WORKSPACE_ACCOUNTING_QUICKBOOKS_DESKTOP_ADVANCED: {
route: 'settings/workspaces/:policyID/accounting/quickbooks-desktop/advanced',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-desktop/advanced` as const,
@@ -733,6 +749,10 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/accounting/quickbooks-desktop/import/customers/displayed_as',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-desktop/import/customers/displayed_as` as const,
},
+ POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_ITEMS: {
+ route: 'settings/workspaces/:policyID/accounting/quickbooks-desktop/import/items',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-desktop/import/items` as const,
+ },
WORKSPACE_PROFILE_NAME: {
route: 'settings/workspaces/:policyID/profile/name',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/profile/name` as const,
@@ -1072,7 +1092,7 @@ const ROUTES = {
},
WORKSPACE_COMPANY_CARDS_ASSIGN_CARD: {
route: 'settings/workspaces/:policyID/company-cards/:feed/assign-card',
- getRoute: (policyID: string, feed: string) => `settings/workspaces/${policyID}/company-cards/${feed}/assign-card` as const,
+ getRoute: (policyID: string, feed: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/company-cards/${feed}/assign-card`, backTo),
},
WORKSPACE_COMPANY_CARD_DETAILS: {
route: 'settings/workspaces/:policyID/company-cards/:bank/:cardID',
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 719c67f0365b..39df4594c277 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -316,6 +316,10 @@ const SCREENS = {
QUICKBOOKS_ONLINE_ADVANCED: 'Policy_Accounting_Quickbooks_Online_Advanced',
QUICKBOOKS_ONLINE_ACCOUNT_SELECTOR: 'Policy_Accounting_Quickbooks_Online_Account_Selector',
QUICKBOOKS_ONLINE_INVOICE_ACCOUNT_SELECTOR: 'Policy_Accounting_Quickbooks_Online_Invoice_Account_Selector',
+ QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT: 'Workspace_Accounting_Quickbooks_Desktop_Export_Company_Card_Expense_Account_Select',
+ QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_ACCOUNT_COMPANY_CARD_SELECT: 'Workspace_Accounting_Quickbooks_Desktop_Export_Company_Card_Expense_Select',
+ QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_ACCOUNT: 'Workspace_Accounting_Quickbooks_Desktop_Export_Company_Card_Expense',
+ QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_DEFAULT_VENDOR_SELECT: 'Workspace_Accounting_Quickbooks_Desktop_Export_Non_Reimbursable_Default_Vendor_Select',
QUICKBOOKS_DESKTOP_ADVANCED: 'Policy_Accounting_Quickbooks_Desktop_Advanced',
QUICKBOOKS_DESKTOP_EXPORT_DATE_SELECT: 'Workspace_Accounting_Quickbooks_Desktop_Export_Date_Select',
QUICKBOOKS_DESKTOP_EXPORT_PREFERRED_EXPORTER: 'Workspace_Accounting_Quickbooks_Desktop_Export_Preferred_Exporter',
@@ -332,6 +336,7 @@ const SCREENS = {
QUICKBOOKS_DESKTOP_CLASSES_DISPLAYED_AS: 'Policy_Accounting_Quickbooks_Desktop_Import_Classes_Dipslayed_As',
QUICKBOOKS_DESKTOP_CUSTOMERS: 'Policy_Accounting_Quickbooks_Desktop_Import_Customers',
QUICKBOOKS_DESKTOP_CUSTOMERS_DISPLAYED_AS: 'Policy_Accounting_Quickbooks_Desktop_Import_Customers_Dipslayed_As',
+ QUICKBOOKS_DESKTOP_ITEMS: 'Policy_Accounting_Quickbooks_Desktop_Import_Items',
XERO_IMPORT: 'Policy_Accounting_Xero_Import',
XERO_ORGANIZATION: 'Policy_Accounting_Xero_Customers',
XERO_CHART_OF_ACCOUNTS: 'Policy_Accounting_Xero_Import_Chart_Of_Accounts',
diff --git a/src/components/CategoryPicker.tsx b/src/components/CategoryPicker.tsx
index a8117fc1f4a0..33d97c6909f5 100644
--- a/src/components/CategoryPicker.tsx
+++ b/src/components/CategoryPicker.tsx
@@ -44,20 +44,14 @@ function CategoryPicker({selectedCategory, policyID, onSubmit}: CategoryPickerPr
const [sections, headerMessage, shouldShowTextInput] = useMemo(() => {
const categories = policyCategories ?? policyCategoriesDraft ?? {};
const validPolicyRecentlyUsedCategories = policyRecentlyUsedCategories?.filter?.((p) => !isEmptyObject(p));
- const {categoryOptions} = OptionsListUtils.getFilteredOptions(
- [],
- [],
- [],
- debouncedSearchValue,
+ const {categoryOptions} = OptionsListUtils.getFilteredOptions({
+ searchValue: debouncedSearchValue,
selectedOptions,
- [],
- false,
- false,
- true,
+ includeP2P: false,
+ includeCategories: true,
categories,
- validPolicyRecentlyUsedCategories,
- false,
- );
+ recentlyUsedCategories: validPolicyRecentlyUsedCategories,
+ });
const categoryData = categoryOptions?.at(0)?.data ?? [];
const header = OptionsListUtils.getHeaderMessageForNonUserList(categoryData.length > 0, debouncedSearchValue);
diff --git a/src/components/ConnectToQuickbooksDesktopFlow/index.tsx b/src/components/ConnectToQuickbooksDesktopFlow/index.tsx
index bf1315b452c6..07ca376a7449 100644
--- a/src/components/ConnectToQuickbooksDesktopFlow/index.tsx
+++ b/src/components/ConnectToQuickbooksDesktopFlow/index.tsx
@@ -1,7 +1,6 @@
import {useEffect} from 'react';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import Navigation from '@libs/Navigation/Navigation';
-import * as PolicyAction from '@userActions/Policy/Policy';
import ROUTES from '@src/ROUTES';
import type {ConnectToQuickbooksDesktopFlowProps} from './types';
@@ -12,8 +11,6 @@ function ConnectToQuickbooksDesktopFlow({policyID}: ConnectToQuickbooksDesktopFl
if (isSmallScreenWidth) {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_SETUP_REQUIRED_DEVICE_MODAL.getRoute(policyID));
} else {
- // Since QBD doesn't support Taxes, we should disable them from the LHN when connecting to QBD
- PolicyAction.enablePolicyTaxes(policyID, false);
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_SETUP_MODAL.getRoute(policyID));
}
}, [isSmallScreenWidth, policyID]);
diff --git a/src/components/EmojiPicker/EmojiPicker.tsx b/src/components/EmojiPicker/EmojiPicker.tsx
index aa6c3e8dfdfb..706265f2e11a 100644
--- a/src/components/EmojiPicker/EmojiPicker.tsx
+++ b/src/components/EmojiPicker/EmojiPicker.tsx
@@ -56,7 +56,7 @@ function EmojiPicker({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef emojiPopoverAnchorRef.current ?? emojiPopoverAnchorRef?.current, []);
+ const getEmojiPopoverAnchor = useCallback(() => emojiPopoverAnchorRef.current ?? (emojiPopoverAnchorRef as EmojiPopoverAnchor), []);
/**
* Show the emoji picker menu.
diff --git a/src/components/EmptyStateComponent/index.tsx b/src/components/EmptyStateComponent/index.tsx
index 8eb991b17b63..81a31174a2ce 100644
--- a/src/components/EmptyStateComponent/index.tsx
+++ b/src/components/EmptyStateComponent/index.tsx
@@ -18,8 +18,7 @@ function EmptyStateComponent({
SkeletonComponent,
headerMediaType,
headerMedia,
- buttonText,
- buttonAction,
+ buttons,
containerStyles,
title,
titleStyles,
@@ -99,15 +98,22 @@ function EmptyStateComponent({
{title}
{typeof subtitle === 'string' ? {subtitle} : subtitle}
- {!!buttonText && !!buttonAction && (
-
- )}
+
+ {buttons?.map(({buttonText, buttonAction, success}, index) => (
+
+
+
+ ))}
+
diff --git a/src/components/EmptyStateComponent/types.ts b/src/components/EmptyStateComponent/types.ts
index 16c65781b461..354141ae672c 100644
--- a/src/components/EmptyStateComponent/types.ts
+++ b/src/components/EmptyStateComponent/types.ts
@@ -9,14 +9,14 @@ import type IconAsset from '@src/types/utils/IconAsset';
type ValidSkeletons = typeof SearchRowSkeleton | typeof TableRowSkeleton;
type MediaTypes = ValueOf;
+type Button = {buttonText?: string; buttonAction?: () => void; success?: boolean};
type SharedProps = {
SkeletonComponent: ValidSkeletons;
title: string;
titleStyles?: StyleProp;
subtitle: string | React.ReactNode;
- buttonText?: string;
- buttonAction?: () => void;
+ buttons?: Button[];
containerStyles?: StyleProp;
headerStyles?: StyleProp;
headerMediaType: T;
diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx
index 7c2f5579332a..ecf72f89134b 100644
--- a/src/components/FloatingActionButton.tsx
+++ b/src/components/FloatingActionButton.tsx
@@ -5,12 +5,10 @@ import type {GestureResponderEvent, Role, Text, View} from 'react-native';
import {Platform} from 'react-native';
import Animated, {createAnimatedPropAdapter, Easing, interpolateColor, processColor, useAnimatedProps, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import Svg, {Path} from 'react-native-svg';
-import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';
import {PressableWithoutFeedback} from './Pressable';
-import Tooltip from './Tooltip/PopoverAnchorTooltip';
const AnimatedPath = Animated.createAnimatedComponent(Path);
AnimatedPath.displayName = 'AnimatedPath';
@@ -57,7 +55,6 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo
const {success, buttonDefaultBG, textLight, textDark} = useTheme();
const styles = useThemeStyles();
const borderRadius = styles.floatingActionButton.borderRadius;
- const {translate} = useLocalize();
const fabPressable = useRef(null);
const sharedValue = useSharedValue(isActive ? 1 : 0);
const buttonRef = ref;
@@ -99,34 +96,32 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo
};
return (
-
- {
- fabPressable.current = el ?? null;
- if (buttonRef && 'current' in buttonRef) {
- buttonRef.current = el ?? null;
- }
- }}
- style={[styles.h100, styles.bottomTabBarItem]}
- accessibilityLabel={accessibilityLabel}
- onPress={toggleFabAction}
- onLongPress={() => {}}
- role={role}
- shouldUseHapticsOnLongPress={false}
- >
-
-
-
-
-
+ {
+ fabPressable.current = el ?? null;
+ if (buttonRef && 'current' in buttonRef) {
+ buttonRef.current = el ?? null;
+ }
+ }}
+ style={[styles.h100, styles.bottomTabBarItem]}
+ accessibilityLabel={accessibilityLabel}
+ onPress={toggleFabAction}
+ onLongPress={() => {}}
+ role={role}
+ shouldUseHapticsOnLongPress={false}
+ >
+
+
+
+
);
}
diff --git a/src/components/HeaderWithBackButton/index.tsx b/src/components/HeaderWithBackButton/index.tsx
index e1843ee506d5..891a68cb38c4 100755
--- a/src/components/HeaderWithBackButton/index.tsx
+++ b/src/components/HeaderWithBackButton/index.tsx
@@ -191,7 +191,7 @@ function HeaderWithBackButton({
/>
)}
{middleContent}
-
+
{children}
{shouldShowDownloadButton && (
@@ -263,7 +263,7 @@ function HeaderWithBackButton({
)}
- {shouldDisplaySearchRouter && }
+ {shouldDisplaySearchRouter && }
diff --git a/src/components/ImportColumn.tsx b/src/components/ImportColumn.tsx
index 1f8dbe729578..d3f5eb75f7e8 100644
--- a/src/components/ImportColumn.tsx
+++ b/src/components/ImportColumn.tsx
@@ -163,6 +163,7 @@ function ImportColumn({column, columnName, columnRoles, columnIndex}: ImportColu
const colName = findColumnName(column.at(0) ?? '');
const defaultSelectedIndex = columnRoles.findIndex((item) => item.value === colName);
+ const finalIndex = defaultSelectedIndex !== -1 ? defaultSelectedIndex : 0;
useEffect(() => {
if (defaultSelectedIndex === -1) {
@@ -201,7 +202,7 @@ function ImportColumn({column, columnName, columnRoles, columnIndex}: ImportColu
onOptionSelected={(option) => {
setColumnName(columnIndex, option.value);
}}
- defaultSelectedIndex={defaultSelectedIndex}
+ defaultSelectedIndex={finalIndex}
options={options}
/>
diff --git a/src/components/InitialURLContextProvider.tsx b/src/components/InitialURLContextProvider.tsx
index 85ad54ca6c94..f026f2de53f9 100644
--- a/src/components/InitialURLContextProvider.tsx
+++ b/src/components/InitialURLContextProvider.tsx
@@ -31,9 +31,10 @@ function InitialURLContextProvider({children, url}: InitialURLContextProviderPro
useEffect(() => {
if (url) {
- const route = signInAfterTransitionFromOldDot(url);
- setInitialURL(route);
- setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN);
+ signInAfterTransitionFromOldDot(url).then((route) => {
+ setInitialURL(route);
+ setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN);
+ });
return;
}
Linking.getInitialURL().then((initURL) => {
diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index 6fbe8bd33839..caa50abfca46 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -8,7 +8,8 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import * as CurrencyUtils from '@libs/CurrencyUtils';
-import Navigation from '@libs/Navigation/Navigation';
+import isReportOpenInRHP from '@libs/Navigation/isReportOpenInRHP';
+import Navigation, {navigationRef} from '@libs/Navigation/Navigation';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
@@ -90,6 +91,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(moneyRequestReport);
const isOnHold = TransactionUtils.isOnHold(transaction);
const isDeletedParentAction = !!requestParentReportAction && ReportActionsUtils.isDeletedAction(requestParentReportAction);
+ const isDuplicate = TransactionUtils.isDuplicate(transaction?.transactionID ?? '');
// Only the requestor can delete the request, admins can only edit it.
const isActionOwner =
@@ -147,7 +149,13 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
hasAllPendingRTERViolations || shouldShowBrokenConnectionViolation || hasOnlyHeldExpenses || hasScanningReceipt || isPayAtEndExpense || hasOnlyPendingTransactions;
const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !shouldShowStatusBar;
const shouldShowAnyButton =
- shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep || shouldShowMarkAsCashButton || shouldShowExportIntegrationButton;
+ isDuplicate ||
+ shouldShowSettlementButton ||
+ shouldShowApproveButton ||
+ shouldShowSubmitButton ||
+ shouldShowNextStep ||
+ shouldShowMarkAsCashButton ||
+ shouldShowExportIntegrationButton;
const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport);
const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport?.currency);
const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(moneyRequestReport, policy);
@@ -157,6 +165,9 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
const {isDelegateAccessRestricted, delegatorEmail} = useDelegateUserDetails();
const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false);
+ const isReportInRHP = isReportOpenInRHP(navigationRef?.getRootState());
+ const shouldDisplaySearchRouter = !isReportInRHP;
+
const confirmPayment = useCallback(
(type?: PaymentMethodType | undefined, payAsBusiness?: boolean) => {
if (!type || !chatReport) {
@@ -260,6 +271,10 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
};
const statusBarProps = getStatusBarProps();
+ const shouldAddGapToContents =
+ shouldUseNarrowLayout &&
+ (isDuplicate || shouldShowSettlementButton || !!shouldShowExportIntegrationButton || shouldShowSubmitButton || shouldShowMarkAsCashButton) &&
+ (!!statusBarProps || shouldShowNextStep);
// The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on
const isWaitingForSubmissionFromCurrentUser = useMemo(
@@ -267,6 +282,11 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
[chatReport?.isOwnPolicyExpenseChat, policy?.harvesting?.enabled],
);
+ const shouldDuplicateButtonBeSuccess = useMemo(
+ () => isDuplicate && !shouldShowSettlementButton && !shouldShowExportIntegrationButton && !shouldShowSubmitButton && !shouldShowMarkAsCashButton,
+ [isDuplicate, shouldShowSettlementButton, shouldShowExportIntegrationButton, shouldShowSubmitButton, shouldShowMarkAsCashButton],
+ );
+
useEffect(() => {
if (isLoadingHoldUseExplained) {
return;
@@ -309,11 +329,23 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
report={moneyRequestReport}
policy={policy}
shouldShowBackButton={shouldUseNarrowLayout}
- shouldDisplaySearchRouter
+ shouldDisplaySearchRouter={shouldDisplaySearchRouter}
onBackButtonPress={onBackButtonPress}
// Shows border if no buttons or banners are showing below the header
shouldShowBorderBottom={!isMoreContentShown}
>
+ {isDuplicate && !shouldUseNarrowLayout && (
+
+
+ )}
{shouldShowSettlementButton && !shouldUseNarrowLayout && (
{isMoreContentShown && (
-
- {shouldShowSettlementButton && shouldUseNarrowLayout && (
-
- )}
- {shouldShowExportIntegrationButton && shouldUseNarrowLayout && (
-
- )}
- {shouldShowSubmitButton && shouldUseNarrowLayout && (
-
+ {startLocationPermissionFlow && fileResize && (
+ setStartLocationPermissionFlow(false)}
+ onGrant={() => navigateToConfirmationStep(fileResize, fileSource, true)}
+ onDeny={() => {
+ IOU.updateLastLocationPermissionPrompt();
+ navigateToConfirmationStep(fileResize, fileSource, false);
+ }}
+ />
+ )}
);
}
diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx
index 0b90598e9c7f..99ddd1d413e1 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx
+++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx
@@ -16,6 +16,7 @@ import {DragAndDropContext} from '@components/DragAndDrop/Provider';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
+import LocationPermissionModal from '@components/LocationPermissionModal';
import PDFThumbnail from '@components/PDFThumbnail';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import Text from '@components/Text';
@@ -29,6 +30,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import * as Browser from '@libs/Browser';
import * as FileUtils from '@libs/fileDownload/FileUtils';
import getCurrentPosition from '@libs/getCurrentPosition';
+import * as IOUUtils from '@libs/IOUUtils';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import * as OptionsListUtils from '@libs/OptionsListUtils';
@@ -43,6 +45,7 @@ import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type {Participant} from '@src/types/onyx/IOU';
import type {Receipt} from '@src/types/onyx/Transaction';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import NavigationAwareCamera from './NavigationAwareCamera/WebCamera';
@@ -64,6 +67,9 @@ function IOURequestStepScan({
const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState();
const [attachmentInvalidReason, setAttachmentValidReason] = useState();
const [pdfFile, setPdfFile] = useState(null);
+ const [startLocationPermissionFlow, setStartLocationPermissionFlow] = useState(false);
+ const [fileResize, setFileResize] = useState(null);
+ const [fileSource, setFileSource] = useState('');
const [receiptImageTopPosition, setReceiptImageTopPosition] = useState(0);
// we need to use isSmallScreenWidth instead of shouldUseNarrowLayout because drag and drop is not supported on mobile
const {isSmallScreenWidth} = useResponsiveLayout();
@@ -259,8 +265,41 @@ function IOURequestStepScan({
}
}, [iouType, reportID, transactionID]);
+ const createTransaction = useCallback(
+ (receipt: Receipt, participant: Participant) => {
+ if (iouType === CONST.IOU.TYPE.TRACK && report) {
+ IOU.trackExpense(
+ report,
+ 0,
+ transaction?.currency ?? 'USD',
+ transaction?.created ?? '',
+ '',
+ currentUserPersonalDetails.login,
+ currentUserPersonalDetails.accountID,
+ participant,
+ '',
+ receipt,
+ );
+ } else {
+ IOU.requestMoney(
+ report,
+ 0,
+ transaction?.currency ?? 'USD',
+ transaction?.created ?? '',
+ '',
+ currentUserPersonalDetails.login,
+ currentUserPersonalDetails.accountID,
+ participant,
+ '',
+ receipt,
+ );
+ }
+ },
+ [currentUserPersonalDetails.accountID, currentUserPersonalDetails.login, iouType, report, transaction?.created, transaction?.currency],
+ );
+
const navigateToConfirmationStep = useCallback(
- (file: FileObject, source: string) => {
+ (file: FileObject, source: string, locationPermissionGranted = false) => {
if (backTo) {
Navigation.goBack(backTo);
return;
@@ -303,123 +342,101 @@ function IOURequestStepScan({
});
return;
}
- getCurrentPosition(
- (successData) => {
- const participant = participants.at(0);
- if (!participant) {
- return;
- }
- if (iouType === CONST.IOU.TYPE.TRACK && report) {
- IOU.trackExpense(
- report,
- 0,
- transaction?.currency ?? 'USD',
- transaction?.created ?? '',
- '',
- currentUserPersonalDetails.login,
- currentUserPersonalDetails.accountID,
- participant,
- '',
- receipt,
- '',
- '',
- '',
- 0,
- false,
- policy,
- {},
- {},
- {
- lat: successData.coords.latitude,
- long: successData.coords.longitude,
- },
- );
- } else {
- IOU.requestMoney(
- report,
- 0,
- transaction?.currency ?? 'USD',
- transaction?.created ?? '',
- '',
- currentUserPersonalDetails.login,
- currentUserPersonalDetails.accountID,
- participant,
- '',
- receipt,
- '',
- '',
- '',
- 0,
- false,
- policy,
- {},
- {},
- {
- lat: successData.coords.latitude,
- long: successData.coords.longitude,
- },
- );
- }
- },
- (errorData) => {
- const participant = participants.at(0);
- if (!participant) {
- return;
- }
- Log.info('[IOURequestStepScan] getCurrentPosition failed', false, errorData);
- // When there is an error, the money can still be requested, it just won't include the GPS coordinates
- if (iouType === CONST.IOU.TYPE.TRACK && report) {
- IOU.trackExpense(
- report,
- 0,
- transaction?.currency ?? 'USD',
- transaction?.created ?? '',
- '',
- currentUserPersonalDetails.login,
- currentUserPersonalDetails.accountID,
- participant,
- '',
- receipt,
- );
- } else {
- IOU.requestMoney(
- report,
- 0,
- transaction?.currency ?? 'USD',
- transaction?.created ?? '',
- '',
- currentUserPersonalDetails.login,
- currentUserPersonalDetails.accountID,
- participant,
- '',
- receipt,
- );
- }
- },
- {
- maximumAge: CONST.GPS.MAX_AGE,
- timeout: CONST.GPS.TIMEOUT,
- },
- );
+ const participant = participants.at(0);
+ if (!participant) {
+ return;
+ }
+ if (locationPermissionGranted) {
+ getCurrentPosition(
+ (successData) => {
+ if (iouType === CONST.IOU.TYPE.TRACK && report) {
+ IOU.trackExpense(
+ report,
+ 0,
+ transaction?.currency ?? 'USD',
+ transaction?.created ?? '',
+ '',
+ currentUserPersonalDetails.login,
+ currentUserPersonalDetails.accountID,
+ participant,
+ '',
+ receipt,
+ '',
+ '',
+ '',
+ 0,
+ false,
+ policy,
+ {},
+ {},
+ {
+ lat: successData.coords.latitude,
+ long: successData.coords.longitude,
+ },
+ );
+ } else {
+ IOU.requestMoney(
+ report,
+ 0,
+ transaction?.currency ?? 'USD',
+ transaction?.created ?? '',
+ '',
+ currentUserPersonalDetails.login,
+ currentUserPersonalDetails.accountID,
+ participant,
+ '',
+ receipt,
+ '',
+ '',
+ '',
+ 0,
+ false,
+ policy,
+ {},
+ {},
+ {
+ lat: successData.coords.latitude,
+ long: successData.coords.longitude,
+ },
+ );
+ }
+ },
+ (errorData) => {
+ Log.info('[IOURequestStepScan] getCurrentPosition failed', false, errorData);
+ // When there is an error, the money can still be requested, it just won't include the GPS coordinates
+ createTransaction(receipt, participant);
+ },
+ {
+ maximumAge: CONST.GPS.MAX_AGE,
+ timeout: CONST.GPS.TIMEOUT,
+ },
+ );
+ return;
+ }
+ createTransaction(receipt, participant);
return;
}
navigateToConfirmationPage();
},
[
+ backTo,
+ transaction?.isFromGlobalCreate,
+ transaction?.currency,
+ transaction?.created,
iouType,
report,
- reportID,
transactionID,
- backTo,
- currentUserPersonalDetails,
- personalDetails,
shouldSkipConfirmation,
- transaction,
navigateToConfirmationPage,
navigateToParticipantPage,
- policy,
+ personalDetails,
+ currentUserPersonalDetails.login,
+ currentUserPersonalDetails.accountID,
+ reportID,
transactionTaxCode,
transactionTaxAmount,
+ policy,
+ createTransaction,
],
);
@@ -462,7 +479,20 @@ function IOURequestStepScan({
updateScanAndNavigate(file, source);
return;
}
- navigateToConfirmationStep(file, source);
+ if (shouldSkipConfirmation) {
+ setFileResize(file);
+ setFileSource(source);
+ const gpsRequired = transaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT && file;
+ if (gpsRequired) {
+ const shouldStartLocationPermissionFlow = IOUUtils.shouldStartLocationPermissionFlow();
+
+ if (shouldStartLocationPermissionFlow) {
+ setStartLocationPermissionFlow(true);
+ return;
+ }
+ }
+ }
+ navigateToConfirmationStep(file, source, false);
});
});
};
@@ -496,9 +526,20 @@ function IOURequestStepScan({
updateScanAndNavigate(file, source);
return;
}
-
- navigateToConfirmationStep(file, source);
- }, [isEditing, transactionID, updateScanAndNavigate, navigateToConfirmationStep, requestCameraPermission]);
+ if (shouldSkipConfirmation) {
+ setFileResize(file);
+ setFileSource(source);
+ const gpsRequired = transaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT && file;
+ if (gpsRequired) {
+ const shouldStartLocationPermissionFlow = IOUUtils.shouldStartLocationPermissionFlow();
+ if (shouldStartLocationPermissionFlow) {
+ setStartLocationPermissionFlow(true);
+ return;
+ }
+ }
+ }
+ navigateToConfirmationStep(file, source, false);
+ }, [transactionID, isEditing, shouldSkipConfirmation, navigateToConfirmationStep, requestCameraPermission, updateScanAndNavigate, transaction?.amount, iouType]);
const clearTorchConstraints = useCallback(() => {
if (!trackRef.current) {
@@ -734,6 +775,17 @@ function IOURequestStepScan({
confirmText={translate('common.close')}
shouldShowCancelButton={false}
/>
+ {startLocationPermissionFlow && fileResize && (
+ setStartLocationPermissionFlow(false)}
+ onGrant={() => navigateToConfirmationStep(fileResize, fileSource, true)}
+ onDeny={() => {
+ IOU.updateLastLocationPermissionPrompt();
+ navigateToConfirmationStep(fileResize, fileSource, false);
+ }}
+ />
+ )}
>
)}
diff --git a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx
index 66736dc80b52..0ddddf7ff878 100644
--- a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx
+++ b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx
@@ -2,8 +2,8 @@ import type {RouteProp} from '@react-navigation/native';
import {useIsFocused} from '@react-navigation/native';
import type {ComponentType, ForwardedRef, RefAttributes} from 'react';
import React, {forwardRef} from 'react';
+import {useOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
-import {withOnyx} from 'react-native-onyx';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import getComponentDisplayName from '@libs/getComponentDisplayName';
import * as IOUUtils from '@libs/IOUUtils';
@@ -38,14 +38,25 @@ type MoneyRequestRouteName =
| typeof SCREENS.MONEY_REQUEST.STEP_SEND_FROM
| typeof SCREENS.MONEY_REQUEST.STEP_COMPANY_INFO;
-type Route = RouteProp;
+type Route = RouteProp;
-type WithFullTransactionOrNotFoundProps = WithFullTransactionOrNotFoundOnyxProps & {route: Route};
+type WithFullTransactionOrNotFoundProps = WithFullTransactionOrNotFoundOnyxProps & {
+ route: Route;
+};
-export default function , TRef>(WrappedComponent: ComponentType>) {
+export default function , TRef>(
+ WrappedComponent: ComponentType>,
+): React.ComponentType & RefAttributes> {
// eslint-disable-next-line rulesdir/no-negated-variables
- function WithFullTransactionOrNotFound(props: TProps, ref: ForwardedRef) {
- const transactionID = props.transaction?.transactionID;
+ function WithFullTransactionOrNotFound(props: Omit, ref: ForwardedRef) {
+ const {route} = props;
+ const transactionID = route.params.transactionID ?? -1;
+ const userAction = 'action' in route.params && route.params.action ? route.params.action : CONST.IOU.ACTION.CREATE;
+
+ const shouldUseTransactionDraft = IOUUtils.shouldUseTransactionDraft(userAction);
+
+ const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`);
+ const [transactionDraft] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`);
const isFocused = useIsFocused();
@@ -59,27 +70,16 @@ export default function
);
}
WithFullTransactionOrNotFound.displayName = `withFullTransactionOrNotFound(${getComponentDisplayName(WrappedComponent)})`;
- // eslint-disable-next-line deprecation/deprecation
- return withOnyx, WithFullTransactionOrNotFoundOnyxProps>({
- transaction: {
- key: ({route}) => {
- const transactionID = route.params.transactionID ?? -1;
- const userAction = 'action' in route.params && route.params.action ? route.params.action : CONST.IOU.ACTION.CREATE;
- if (IOUUtils.shouldUseTransactionDraft(userAction)) {
- return `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}` as `${typeof ONYXKEYS.COLLECTION.TRANSACTION}${string}`;
- }
- return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`;
- },
- },
- })(forwardRef(WithFullTransactionOrNotFound));
+ return forwardRef(WithFullTransactionOrNotFound);
}
export type {WithFullTransactionOrNotFoundProps};
diff --git a/src/pages/settings/InitialSettingsPage.tsx b/src/pages/settings/InitialSettingsPage.tsx
index 48873a342a6f..9ffac3e49e41 100755
--- a/src/pages/settings/InitialSettingsPage.tsx
+++ b/src/pages/settings/InitialSettingsPage.tsx
@@ -28,6 +28,7 @@ import useSubscriptionPlan from '@hooks/useSubscriptionPlan';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWaitForNavigation from '@hooks/useWaitForNavigation';
+import {resetExitSurveyForm} from '@libs/actions/ExitSurvey';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as SubscriptionUtils from '@libs/SubscriptionUtils';
@@ -237,7 +238,9 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr
},
}
: {
- routeName: ROUTES.SETTINGS_EXIT_SURVEY_REASON,
+ action() {
+ resetExitSurveyForm(() => Navigation.navigate(ROUTES.SETTINGS_EXIT_SURVEY_REASON));
+ },
}),
},
{
diff --git a/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx b/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx
index ade900f365a7..6f433957015f 100644
--- a/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx
+++ b/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx
@@ -27,26 +27,15 @@ function useOptions() {
const existingDelegates = useMemo(() => account?.delegatedAccess?.delegates?.map((delegate) => delegate.email) ?? [], [account?.delegatedAccess?.delegates]);
const defaultOptions = useMemo(() => {
- const {recentReports, personalDetails, userToInvite, currentUserOption} = OptionsListUtils.getFilteredOptions(
- optionsList.reports,
- optionsList.personalDetails,
+ const {recentReports, personalDetails, userToInvite, currentUserOption} = OptionsListUtils.getFilteredOptions({
+ reports: optionsList.reports,
+ personalDetails: optionsList.personalDetails,
betas,
- '',
- [],
- [...CONST.EXPENSIFY_EMAILS, ...existingDelegates],
- false,
- true,
- false,
- {},
- [],
- false,
- {},
- [],
- true,
- false,
- false,
- 0,
- );
+
+ excludeLogins: [...CONST.EXPENSIFY_EMAILS, ...existingDelegates],
+
+ maxRecentReportsToShow: 0,
+ });
const headerMessage = OptionsListUtils.getHeaderMessage((recentReports?.length || 0) + (personalDetails?.length || 0) !== 0, !!userToInvite, '');
diff --git a/src/pages/tasks/NewTaskPage.tsx b/src/pages/tasks/NewTaskPage.tsx
index faf45df8b51d..b6ce2f14bafd 100644
--- a/src/pages/tasks/NewTaskPage.tsx
+++ b/src/pages/tasks/NewTaskPage.tsx
@@ -2,8 +2,7 @@ import {useFocusEffect} from '@react-navigation/native';
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {InteractionManager, View} from 'react-native';
-import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
-import {withOnyx} from 'react-native-onyx';
+import {useOnyx} from 'react-native-onyx';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton';
import FormHelpMessage from '@components/FormHelpMessage';
@@ -27,35 +26,27 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
-import type {PersonalDetailsList, Report, Task} from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
-type NewTaskPageOnyxProps = {
- /** Task Creation Data */
- task: OnyxEntry;
+type NewTaskPageProps = StackScreenProps;
- /** All of the personal details for everyone */
- personalDetails: OnyxEntry;
-
- /** All reports shared with the user */
- reports: OnyxCollection;
-};
-
-type NewTaskPageProps = NewTaskPageOnyxProps & StackScreenProps;
-
-function NewTaskPage({task, reports, personalDetails, route}: NewTaskPageProps) {
+function NewTaskPage({route}: NewTaskPageProps) {
+ const [task] = useOnyx(ONYXKEYS.TASK);
+ const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
+ const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
const styles = useThemeStyles();
const {translate} = useLocalize();
- const [assignee, setAssignee] = useState();
+ const assignee = useMemo(() => TaskActions.getAssignee(task?.assigneeAccountID ?? -1, personalDetails), [task?.assigneeAccountID, personalDetails]);
const assigneeTooltipDetails = ReportUtils.getDisplayNamesWithTooltips(
OptionsListUtils.getPersonalDetailsForAccountIDs(task?.assigneeAccountID ? [task.assigneeAccountID] : [], personalDetails),
false,
);
- const [shareDestination, setShareDestination] = useState();
- const [title, setTitle] = useState('');
- const [description, setDescription] = useState('');
+ const shareDestination = useMemo(
+ () => (task?.shareDestination ? TaskActions.getShareDestination(task.shareDestination, reports, personalDetails) : undefined),
+ [task?.shareDestination, reports, personalDetails],
+ );
+ const parentReport = useMemo(() => (task?.shareDestination ? reports?.[`${ONYXKEYS.COLLECTION.REPORT}${task.shareDestination}`] : undefined), [reports, task?.shareDestination]);
const [errorMessage, setErrorMessage] = useState('');
- const [parentReport, setParentReport] = useState>();
const hasDestinationError = task?.skipConfirmation && !task?.parentReportID;
const isAllowedToCreateTask = useMemo(() => isEmptyObject(parentReport) || ReportUtils.isAllowedToComment(parentReport), [parentReport]);
@@ -79,38 +70,13 @@ function NewTaskPage({task, reports, personalDetails, route}: NewTaskPageProps)
useEffect(() => {
setErrorMessage('');
- // If we have an assignee, we want to set the assignee data
- // If there's an issue with the assignee chosen, we want to notify the user
- if (task?.assignee) {
- const displayDetails = TaskActions.getAssignee(task?.assigneeAccountID ?? -1, personalDetails);
- setAssignee(displayDetails);
- }
-
// We only set the parentReportID if we are creating a task from a report
// this allows us to go ahead and set that report as the share destination
// and disable the share destination selector
if (task?.parentReportID) {
TaskActions.setShareDestinationValue(task.parentReportID);
}
-
- // If we have a share destination, we want to set the parent report and
- // the share destination data
- if (task?.shareDestination) {
- setParentReport(reports?.[`report_${task.shareDestination}`]);
- const displayDetails = TaskActions.getShareDestination(task.shareDestination, reports, personalDetails);
- setShareDestination(displayDetails);
- }
-
- // If we have a title, we want to set the title
- if (task?.title !== undefined) {
- setTitle(task.title);
- }
-
- // If we have a description, we want to set the description
- if (task?.description !== undefined) {
- setDescription(task.description);
- }
- }, [personalDetails, reports, task?.assignee, task?.assigneeAccountID, task?.description, task?.parentReportID, task?.shareDestination, task?.title]);
+ }, [task?.assignee, task?.assigneeAccountID, task?.description, task?.parentReportID, task?.shareDestination, task?.title]);
// On submit, we want to call the createTask function and wait to validate
// the response
@@ -179,14 +145,14 @@ function NewTaskPage({task, reports, personalDetails, route}: NewTaskPageProps)
Navigation.navigate(ROUTES.NEW_TASK_TITLE.getRoute(backTo))}
shouldShowRightIcon
rightLabel={translate('common.required')}
/>
Navigation.navigate(ROUTES.NEW_TASK_DESCRIPTION.getRoute(backTo))}
shouldShowRightIcon
shouldParseTitle
@@ -234,14 +200,4 @@ function NewTaskPage({task, reports, personalDetails, route}: NewTaskPageProps)
NewTaskPage.displayName = 'NewTaskPage';
-export default withOnyx({
- task: {
- key: ONYXKEYS.TASK,
- },
- reports: {
- key: ONYXKEYS.COLLECTION.REPORT,
- },
- personalDetails: {
- key: ONYXKEYS.PERSONAL_DETAILS_LIST,
- },
-})(NewTaskPage);
+export default NewTaskPage;
diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.tsx b/src/pages/tasks/TaskAssigneeSelectorModal.tsx
index 3f44882e2fd0..5a0912de45a5 100644
--- a/src/pages/tasks/TaskAssigneeSelectorModal.tsx
+++ b/src/pages/tasks/TaskAssigneeSelectorModal.tsx
@@ -40,26 +40,14 @@ function useOptions() {
const {options: optionsList, areOptionsInitialized} = useOptionsList();
const defaultOptions = useMemo(() => {
- const {recentReports, personalDetails, userToInvite, currentUserOption} = OptionsListUtils.getFilteredOptions(
- optionsList.reports,
- optionsList.personalDetails,
+ const {recentReports, personalDetails, userToInvite, currentUserOption} = OptionsListUtils.getFilteredOptions({
+ reports: optionsList.reports,
+ personalDetails: optionsList.personalDetails,
betas,
- '',
- [],
- CONST.EXPENSIFY_EMAILS,
- false,
- true,
- false,
- {},
- [],
- false,
- {},
- [],
- true,
- false,
- false,
- 0,
- );
+ excludeLogins: CONST.EXPENSIFY_EMAILS,
+
+ maxRecentReportsToShow: 0,
+ });
const headerMessage = OptionsListUtils.getHeaderMessage((recentReports?.length || 0) + (personalDetails?.length || 0) !== 0 || !!currentUserOption, !!userToInvite, '');
diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx
index 3db7623bfad1..6cfc66466da4 100644
--- a/src/pages/workspace/WorkspaceInitialPage.tsx
+++ b/src/pages/workspace/WorkspaceInitialPage.tsx
@@ -217,7 +217,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
icon: Expensicons.CreditCard,
action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID)))),
routeName: SCREENS.WORKSPACE.COMPANY_CARDS,
- brickRoadIndicator: PolicyUtils.hasPolicyFeedsError(cardFeeds?.companyCards ?? {}) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
+ brickRoadIndicator: PolicyUtils.hasPolicyFeedsError(cardFeeds?.settings?.companyCards ?? {}) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
});
}
diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx
index 54d8401a99f8..0bc44a1d2298 100644
--- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx
+++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx
@@ -126,7 +126,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
subtitleTranslationKey: 'workspace.moreFeatures.companyCards.subtitle',
isActive: policy?.areCompanyCardsEnabled ?? false,
pendingAction: policy?.pendingFields?.areCompanyCardsEnabled,
- disabled: !isEmptyObject(cardFeeds?.companyCards),
+ disabled: !isEmptyObject(cardFeeds?.settings?.companyCards),
action: (isEnabled: boolean) => {
if (!policyID) {
return;
diff --git a/src/pages/workspace/accounting/AccountingContext.tsx b/src/pages/workspace/accounting/AccountingContext.tsx
index 6250f99b21f2..98a804d0ba66 100644
--- a/src/pages/workspace/accounting/AccountingContext.tsx
+++ b/src/pages/workspace/accounting/AccountingContext.tsx
@@ -4,6 +4,7 @@ import type {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import AccountingConnectionConfirmationModal from '@components/AccountingConnectionConfirmationModal';
import useLocalize from '@hooks/useLocalize';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
import {removePolicyConnection} from '@libs/actions/connections';
import Navigation from '@libs/Navigation/Navigation';
import {isControlPolicy} from '@libs/PolicyUtils';
@@ -56,6 +57,7 @@ function AccountingContextProvider({children, policy}: AccountingContextProvider
const [activeIntegration, setActiveIntegration] = useState();
const {translate} = useLocalize();
const policyID = policy?.id ?? '-1';
+ const {isSmallScreenWidth} = useResponsiveLayout();
const startIntegrationFlow = React.useCallback(
(newActiveIntegration: ActiveIntegration) => {
@@ -67,6 +69,8 @@ function AccountingContextProvider({children, policy}: AccountingContextProvider
undefined,
newActiveIntegration.integrationToDisconnect,
newActiveIntegration.shouldDisconnectIntegrationBeforeConnecting,
+ undefined,
+ isSmallScreenWidth,
);
const workspaceUpgradeNavigationDetails = accountingIntegrationData?.workspaceUpgradeNavigationDetails;
if (workspaceUpgradeNavigationDetails && !isControlPolicy(policy)) {
@@ -80,7 +84,7 @@ function AccountingContextProvider({children, policy}: AccountingContextProvider
key: Math.random(),
});
},
- [policy, policyID, translate],
+ [isSmallScreenWidth, policy, policyID, translate],
);
const closeConfirmationModal = () => {
diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx
index 99979e359c20..3e1117d4a12a 100644
--- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx
+++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx
@@ -100,13 +100,27 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) {
);
const hasSyncError = PolicyUtils.hasSyncError(policy, isSyncInProgress);
- const hasUnsupportedNDIntegration = PolicyUtils.hasUnsupportedIntegration(policy, accountingIntegrations);
+ const hasUnsupportedNDIntegration = !isEmptyObject(policy?.connections) && PolicyUtils.hasUnsupportedIntegration(policy, accountingIntegrations);
const tenants = useMemo(() => getXeroTenants(policy), [policy]);
const currentXeroOrganization = findCurrentXeroOrganization(tenants, policy?.connections?.xero?.config?.tenantID);
+ const shouldShowSynchronizationError = !!synchronizationError;
+ const shouldShowReinstallConnectorMenuItem = shouldShowSynchronizationError && connectedIntegration === CONST.POLICY.CONNECTIONS.NAME.QBD;
const overflowMenu: ThreeDotsMenuProps['menuItems'] = useMemo(
() => [
+ ...(shouldShowReinstallConnectorMenuItem
+ ? [
+ {
+ icon: Expensicons.CircularArrowBackwards,
+ text: translate('workspace.accounting.reinstall'),
+ onSelected: () => startIntegrationFlow({name: CONST.POLICY.CONNECTIONS.NAME.QBD}),
+ shouldCallAfterModalHide: true,
+ disabled: isOffline,
+ iconRight: Expensicons.NewWindow,
+ },
+ ]
+ : []),
...(shouldShowEnterCredentials
? [
{
@@ -133,7 +147,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) {
shouldCallAfterModalHide: true,
},
],
- [shouldShowEnterCredentials, translate, isOffline, policyID, connectedIntegration, startIntegrationFlow],
+ [shouldShowEnterCredentials, shouldShowReinstallConnectorMenuItem, translate, isOffline, policyID, connectedIntegration, startIntegrationFlow],
);
useFocusEffect(
@@ -269,7 +283,6 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) {
if (!connectedIntegration) {
return [];
}
- const shouldShowSynchronizationError = !!synchronizationError;
const shouldHideConfigurationOptions = isConnectionUnverified(policy, connectedIntegration);
const integrationData = getAccountingIntegrationData(connectedIntegration, policyID, translate, policy, undefined, undefined, undefined, canUseNetSuiteUSATax);
const iconProps = integrationData?.icon ? {icon: integrationData.icon, iconType: CONST.ICON_TYPE_AVATAR} : {};
@@ -364,6 +377,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) {
isSyncInProgress,
connectedIntegration,
synchronizationError,
+ shouldShowSynchronizationError,
policyID,
translate,
styles.sectionMenuItemTopDescription,
@@ -385,7 +399,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) {
]);
const otherIntegrationsItems = useMemo(() => {
- if (isEmptyObject(policy?.connections) && !isSyncInProgress && !(hasUnsupportedNDIntegration && hasSyncError)) {
+ if (isEmptyObject(policy?.connections) && !isSyncInProgress) {
return;
}
const otherIntegrations = accountingIntegrations.filter(
@@ -442,8 +456,6 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) {
isOffline,
startIntegrationFlow,
popoverAnchorRefs,
- hasUnsupportedNDIntegration,
- hasSyncError,
]);
return (
@@ -473,7 +485,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) {
titleStyles={styles.accountSettingsSectionTitle}
childrenStyles={styles.pt5}
>
- {!(hasUnsupportedNDIntegration && hasSyncError) &&
+ {!hasUnsupportedNDIntegration &&
connectionsMenuItems.map((menuItem) => (
@@ -507,6 +518,20 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) {
)}
+ {hasUnsupportedNDIntegration && !hasSyncError && (
+
+
+ {
+ // Go to Expensify Classic.
+ Link.openOldDotLink(CONST.OLDDOT_URLS.POLICY_CONNECTIONS_URL(policyID));
+ }}
+ >
+ {translate('workspace.accounting.goToODToSettings')}
+
+
+
+ )}
{otherIntegrationsItems && (
;
+type QuickBooksDesktopSetupFlowSyncPageProps = StackScreenProps;
+
+function QuickBooksDesktopSetupFlowSyncPage({route}: QuickBooksDesktopSetupFlowSyncPageProps) {
+ const policyID: string = route.params.policyID;
+ const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID ?? '-1'}`);
+ const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policyID ?? '-1'}`);
+
+ useEffect(() => {
+ if (!policyID) {
+ return;
+ }
+
+ const isSyncInProgress = isConnectionInProgress(connectionSyncProgress, policy);
+ if (!isSyncInProgress) {
+ syncConnection(policyID, CONST.POLICY.CONNECTIONS.NAME.QBD, true);
+ }
+
+ Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING.getRoute(policyID));
+
+ // disabling this rule, as we want this to run only on the first render
+ // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
+ }, []);
+
+ return null;
}
QuickBooksDesktopSetupFlowSyncPage.displayName = 'QuickBooksDesktopSetupFlowSyncPage';
diff --git a/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx b/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx
index abc98f1c4d42..1aa80231fc7d 100644
--- a/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx
+++ b/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx
@@ -15,6 +15,7 @@ import * as QuickbooksDesktop from '@libs/actions/connections/QuickbooksDesktop'
import Navigation from '@libs/Navigation/Navigation';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
import LoadingPage from '@pages/LoadingPage';
+import * as PolicyAction from '@userActions/Policy/Policy';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
@@ -36,6 +37,9 @@ function RequireQuickBooksDesktopModal({route}: RequireQuickBooksDesktopModalPro
});
};
+ // Since QBD doesn't support Taxes, we should disable them from the LHN when connecting to QBD
+ PolicyAction.enablePolicyTaxes(policyID, false);
+
fetchSetupLink();
// disabling this rule, as we want this to run only on the first render
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
diff --git a/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopCompanyCardExpenseAccountPage.tsx b/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopCompanyCardExpenseAccountPage.tsx
new file mode 100644
index 000000000000..cdf958caf9a4
--- /dev/null
+++ b/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopCompanyCardExpenseAccountPage.tsx
@@ -0,0 +1,128 @@
+import React, {useMemo} from 'react';
+import ConnectionLayout from '@components/ConnectionLayout';
+import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
+import OfflineWithFeedback from '@components/OfflineWithFeedback';
+import useLocalize from '@hooks/useLocalize';
+import usePermissions from '@hooks/usePermissions';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as QuickbooksDesktop from '@libs/actions/connections/QuickbooksDesktop';
+import * as ConnectionUtils from '@libs/ConnectionUtils';
+import * as ErrorUtils from '@libs/ErrorUtils';
+import * as PolicyUtils from '@libs/PolicyUtils';
+import Navigation from '@navigation/Navigation';
+import {getQBDReimbursableAccounts} from '@pages/workspace/accounting/utils';
+import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections';
+import withPolicyConnections from '@pages/workspace/withPolicyConnections';
+import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow';
+import {clearQBDErrorField} from '@userActions/Policy/Policy';
+import CONST from '@src/CONST';
+import ROUTES from '@src/ROUTES';
+
+function QuickbooksDesktopCompanyCardExpenseAccountPage({policy}: WithPolicyConnectionsProps) {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const policyID = policy?.id ?? '-1';
+ const qbdConfig = policy?.connections?.quickbooksDesktop?.config;
+ const {vendors} = policy?.connections?.quickbooksDesktop?.data ?? {};
+ const nonReimbursableBillDefaultVendorObject = vendors?.find((vendor) => vendor.id === qbdConfig?.export?.nonReimbursableBillDefaultVendor);
+ const {canUseNewDotQBD} = usePermissions();
+ const nonReimbursable = qbdConfig?.export?.nonReimbursable;
+ const nonReimbursableAccount = qbdConfig?.export?.nonReimbursableAccount;
+
+ const accountName = useMemo(
+ () => getQBDReimbursableAccounts(policy?.connections?.quickbooksDesktop, nonReimbursable).find(({id}) => nonReimbursableAccount === id)?.name,
+ [policy?.connections?.quickbooksDesktop, nonReimbursable, nonReimbursableAccount],
+ );
+
+ const sections = [
+ {
+ title: nonReimbursable ? translate(`workspace.qbd.accounts.${nonReimbursable}`) : undefined,
+ description: translate('workspace.accounting.exportAs'),
+ onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_SELECT.getRoute(policyID)),
+ hintText: nonReimbursable ? translate(`workspace.qbd.accounts.${nonReimbursable}Description`) : undefined,
+ subscribedSettings: [CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE],
+ keyForList: translate('workspace.accounting.exportAs'),
+ },
+ {
+ title: accountName ?? translate('workspace.qbd.notConfigured'),
+ description: ConnectionUtils.getQBDNonReimbursableExportAccountType(nonReimbursable),
+ onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT.getRoute(policyID)),
+ subscribedSettings: [CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE_ACCOUNT],
+ keyForList: ConnectionUtils.getQBDNonReimbursableExportAccountType(nonReimbursable),
+ },
+ ];
+
+ return (
+ Navigation.goBack(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_EXPORT.getRoute(policyID))}
+ >
+ {sections.map((section) => (
+
+
+
+ ))}
+ {nonReimbursable === CONST.QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.VENDOR_BILL && (
+ <>
+ QuickbooksDesktop.updateQuickbooksDesktopShouldAutoCreateVendor(policyID, isOn)}
+ onCloseError={() => clearQBDErrorField(policyID, CONST.QUICKBOOKS_DESKTOP_CONFIG.SHOULD_AUTO_CREATE_VENDOR)}
+ />
+ {!!qbdConfig?.shouldAutoCreateVendor && (
+
+ Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_DEFAULT_VENDOR_SELECT.getRoute(policyID))}
+ brickRoadIndicator={
+ PolicyUtils.areSettingsInErrorFields([CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE_BILL_DEFAULT_VENDOR], qbdConfig?.errorFields)
+ ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR
+ : undefined
+ }
+ shouldShowRightIcon
+ />
+
+ )}
+ >
+ )}
+
+ );
+}
+
+QuickbooksDesktopCompanyCardExpenseAccountPage.displayName = 'QuickbooksDesktopCompanyCardExpenseAccountPage';
+
+export default withPolicyConnections(QuickbooksDesktopCompanyCardExpenseAccountPage);
diff --git a/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopCompanyCardExpenseAccountSelectCardPage.tsx b/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopCompanyCardExpenseAccountSelectCardPage.tsx
new file mode 100644
index 000000000000..285d50e77022
--- /dev/null
+++ b/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopCompanyCardExpenseAccountSelectCardPage.tsx
@@ -0,0 +1,113 @@
+import React, {useCallback, useMemo} from 'react';
+import RadioListItem from '@components/SelectionList/RadioListItem';
+import type {ListItem} from '@components/SelectionList/types';
+import SelectionScreen from '@components/SelectionScreen';
+import type {SelectorType} from '@components/SelectionScreen';
+import useLocalize from '@hooks/useLocalize';
+import usePermissions from '@hooks/usePermissions';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as QuickbooksDesktop from '@libs/actions/connections/QuickbooksDesktop';
+import * as ErrorUtils from '@libs/ErrorUtils';
+import * as PolicyUtils from '@libs/PolicyUtils';
+import Navigation from '@navigation/Navigation';
+import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections';
+import withPolicyConnections from '@pages/workspace/withPolicyConnections';
+import {clearQBDErrorField} from '@userActions/Policy/Policy';
+import CONST from '@src/CONST';
+import ROUTES from '@src/ROUTES';
+import type {Account, QBDNonReimbursableExportAccountType} from '@src/types/onyx/Policy';
+
+type MenuItem = ListItem & {
+ value: QBDNonReimbursableExportAccountType;
+ accounts: Account[];
+ defaultVendor: string;
+};
+
+function QuickbooksDesktopCompanyCardExpenseAccountSelectCardPage({policy}: WithPolicyConnectionsProps) {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const policyID = policy?.id ?? '-1';
+ const qbdConfig = policy?.connections?.quickbooksDesktop?.config;
+ const {creditCardAccounts, payableAccounts, vendors, bankAccounts} = policy?.connections?.quickbooksDesktop?.data ?? {};
+ const {canUseNewDotQBD} = usePermissions();
+ const nonReimbursable = qbdConfig?.export?.nonReimbursable;
+ const nonReimbursableAccount = qbdConfig?.export?.nonReimbursableAccount;
+ const nonReimbursableBillDefaultVendor = qbdConfig?.export?.nonReimbursableBillDefaultVendor;
+
+ const sections = useMemo(() => {
+ const options: MenuItem[] = [
+ {
+ text: translate(`workspace.qbd.accounts.${CONST.QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.CREDIT_CARD}`),
+ value: CONST.QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.CREDIT_CARD,
+ keyForList: CONST.QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.CREDIT_CARD,
+ isSelected: CONST.QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.CREDIT_CARD === nonReimbursable,
+ accounts: creditCardAccounts ?? [],
+ defaultVendor: CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE,
+ },
+ {
+ text: translate(`workspace.qbd.accounts.${CONST.QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.CHECK}`),
+ value: CONST.QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.CHECK,
+ keyForList: CONST.QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.CHECK,
+ isSelected: CONST.QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.CHECK === nonReimbursable,
+ accounts: bankAccounts ?? [],
+ defaultVendor: CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE,
+ },
+ {
+ text: translate(`workspace.qbd.accounts.${CONST.QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.VENDOR_BILL}`),
+ value: CONST.QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.VENDOR_BILL,
+ keyForList: CONST.QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.VENDOR_BILL,
+ isSelected: CONST.QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.VENDOR_BILL === nonReimbursable,
+ accounts: payableAccounts ?? [],
+ defaultVendor: vendors?.[0]?.id ?? CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE,
+ },
+ ];
+ return [{data: options}];
+ }, [translate, nonReimbursable, creditCardAccounts, bankAccounts, payableAccounts, vendors]);
+
+ const selectExportCompanyCard = useCallback(
+ (row: MenuItem) => {
+ if (row.value !== nonReimbursable) {
+ QuickbooksDesktop.updateQuickbooksCompanyCardExpenseAccount(
+ policyID,
+ {
+ [CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE]: row.value,
+ [CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE_ACCOUNT]: row.accounts.at(0)?.id ?? '',
+ [CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE_BILL_DEFAULT_VENDOR]: row.defaultVendor,
+ },
+ {
+ [CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE]: nonReimbursable,
+ [CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE_ACCOUNT]: nonReimbursableAccount,
+ [CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE_BILL_DEFAULT_VENDOR]: nonReimbursableBillDefaultVendor,
+ },
+ );
+ }
+ Navigation.goBack(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_ACCOUNT.getRoute(policyID));
+ },
+ [nonReimbursable, nonReimbursableAccount, nonReimbursableBillDefaultVendor, policyID],
+ );
+ return (
+ selectExportCompanyCard(selection as MenuItem)}
+ shouldSingleExecuteRowSelect
+ initiallyFocusedOptionKey={sections.at(0)?.data.find((mode) => mode.isSelected)?.keyForList}
+ connectionName={CONST.POLICY.CONNECTIONS.NAME.QBD}
+ onBackButtonPress={() => Navigation.goBack(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_ACCOUNT.getRoute(policyID))}
+ errors={ErrorUtils.getLatestErrorField(qbdConfig, CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE)}
+ errorRowStyles={[styles.ph5, styles.pv3]}
+ pendingAction={PolicyUtils.settingsPendingAction([CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE], qbdConfig?.pendingFields)}
+ onClose={() => clearQBDErrorField(policyID, CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE)}
+ />
+ );
+}
+
+QuickbooksDesktopCompanyCardExpenseAccountSelectCardPage.displayName = 'QuickbooksDesktopCompanyCardExpenseAccountSelectCardPage';
+
+export default withPolicyConnections(QuickbooksDesktopCompanyCardExpenseAccountSelectCardPage);
diff --git a/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopCompanyCardExpenseAccountSelectPage.tsx b/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopCompanyCardExpenseAccountSelectPage.tsx
new file mode 100644
index 000000000000..c8da940f5209
--- /dev/null
+++ b/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopCompanyCardExpenseAccountSelectPage.tsx
@@ -0,0 +1,98 @@
+import React, {useCallback, useMemo} from 'react';
+import BlockingView from '@components/BlockingViews/BlockingView';
+import * as Illustrations from '@components/Icon/Illustrations';
+import RadioListItem from '@components/SelectionList/RadioListItem';
+import type {ListItem} from '@components/SelectionList/types';
+import SelectionScreen from '@components/SelectionScreen';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import usePermissions from '@hooks/usePermissions';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as QuickbooksDesktop from '@libs/actions/connections/QuickbooksDesktop';
+import * as ConnectionUtils from '@libs/ConnectionUtils';
+import * as ErrorUtils from '@libs/ErrorUtils';
+import * as PolicyUtils from '@libs/PolicyUtils';
+import Navigation from '@navigation/Navigation';
+import {getQBDReimbursableAccounts} from '@pages/workspace/accounting/utils';
+import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections';
+import withPolicyConnections from '@pages/workspace/withPolicyConnections';
+import variables from '@styles/variables';
+import {clearQBDErrorField} from '@userActions/Policy/Policy';
+import CONST from '@src/CONST';
+import ROUTES from '@src/ROUTES';
+import type {Account} from '@src/types/onyx/Policy';
+
+type CardListItem = ListItem & {
+ value: Account;
+};
+
+function QuickbooksDesktopCompanyCardExpenseAccountSelectPage({policy}: WithPolicyConnectionsProps) {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const policyID = policy?.id ?? '-1';
+ const qbdConfig = policy?.connections?.quickbooksDesktop?.config;
+ const {canUseNewDotQBD} = usePermissions();
+ const nonReimbursable = qbdConfig?.export?.nonReimbursable;
+ const nonReimbursableAccount = qbdConfig?.export?.nonReimbursableAccount;
+
+ const data: CardListItem[] = useMemo(() => {
+ const accounts = getQBDReimbursableAccounts(policy?.connections?.quickbooksDesktop, nonReimbursable);
+ return accounts.map((card) => ({
+ value: card,
+ text: card.name,
+ keyForList: card.name,
+ isSelected: card.id === nonReimbursableAccount,
+ }));
+ }, [policy?.connections?.quickbooksDesktop, nonReimbursable, nonReimbursableAccount]);
+
+ const selectExportAccount = useCallback(
+ (row: CardListItem) => {
+ if (row.value.id !== nonReimbursableAccount) {
+ QuickbooksDesktop.updateQuickbooksDesktopNonReimbursableExpensesAccount(policyID, row.value.id, nonReimbursableAccount);
+ }
+ Navigation.goBack(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_ACCOUNT.getRoute(policyID));
+ },
+ [nonReimbursableAccount, policyID],
+ );
+
+ const listEmptyContent = useMemo(
+ () => (
+
+ ),
+ [translate, styles.pb10],
+ );
+ return (
+ {translate(`workspace.qbd.accounts.${nonReimbursable}AccountDescription`)} : null}
+ shouldBeBlocked={!canUseNewDotQBD} // TODO: [QBD] remove it once the QBD beta is done
+ sections={data.length ? [{data}] : []}
+ listItem={RadioListItem}
+ onSelectRow={selectExportAccount}
+ shouldSingleExecuteRowSelect
+ initiallyFocusedOptionKey={data.find((mode) => mode.isSelected)?.keyForList}
+ listEmptyContent={listEmptyContent}
+ connectionName={CONST.POLICY.CONNECTIONS.NAME.QBD}
+ onBackButtonPress={() => Navigation.goBack(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_ACCOUNT.getRoute(policyID))}
+ errors={ErrorUtils.getLatestErrorField(qbdConfig, CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE_ACCOUNT)}
+ errorRowStyles={[styles.ph5, styles.pv3]}
+ pendingAction={PolicyUtils.settingsPendingAction([CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE_ACCOUNT], qbdConfig?.pendingFields)}
+ onClose={() => clearQBDErrorField(policyID, CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE_ACCOUNT)}
+ />
+ );
+}
+
+QuickbooksDesktopCompanyCardExpenseAccountSelectPage.displayName = 'QuickbooksDesktopCompanyCardExpenseAccountSelectPage';
+
+export default withPolicyConnections(QuickbooksDesktopCompanyCardExpenseAccountSelectPage);
diff --git a/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopExportPage.tsx b/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopExportPage.tsx
index dbd07f8b87a2..72d4c75b761c 100644
--- a/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopExportPage.tsx
+++ b/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopExportPage.tsx
@@ -54,14 +54,14 @@ function QuickbooksDesktopExportPage({policy}: WithPolicyConnectionsProps) {
},
{
description: translate('workspace.accounting.exportCompanyCard'),
- onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT.getRoute(policyID)), // TODO: [QBD] should be updated to use new routes
+ onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_ACCOUNT.getRoute(policyID)),
brickRoadIndicator: qbdConfig?.errorFields?.exportCompanyCard ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
- title: qbdConfig?.export.nonReimbursable ? translate(`workspace.qbd.accounts.${qbdConfig?.export.nonReimbursable}`) : undefined,
+ title: qbdConfig?.export?.nonReimbursable ? translate(`workspace.qbd.accounts.${qbdConfig?.export?.nonReimbursable}`) : undefined,
subscribedSettings: [
- CONST.QUICKBOOKS_CONFIG.NON_REIMBURSABLE_EXPENSES_EXPORT_DESTINATION,
- CONST.QUICKBOOKS_CONFIG.NON_REIMBURSABLE_EXPENSE_ACCOUNT,
- ...(shouldShowVendorMenuItems ? [CONST.QUICKBOOKS_CONFIG.AUTO_CREATE_VENDOR] : []),
- ...(shouldShowVendorMenuItems && qbdConfig?.shouldAutoCreateVendor ? [CONST.QUICKBOOKS_CONFIG.NON_REIMBURSABLE_BILL_DEFAULT_VENDOR] : []),
+ CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE,
+ CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE_ACCOUNT,
+ ...(shouldShowVendorMenuItems ? [CONST.QUICKBOOKS_DESKTOP_CONFIG.SHOULD_AUTO_CREATE_VENDOR] : []),
+ ...(shouldShowVendorMenuItems && qbdConfig?.shouldAutoCreateVendor ? [CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE_BILL_DEFAULT_VENDOR] : []),
],
},
{
diff --git a/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopNonReimbursableDefaultVendorSelectPage.tsx b/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopNonReimbursableDefaultVendorSelectPage.tsx
new file mode 100644
index 000000000000..b1f2823795c1
--- /dev/null
+++ b/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopNonReimbursableDefaultVendorSelectPage.tsx
@@ -0,0 +1,95 @@
+import React, {useCallback, useMemo} from 'react';
+import BlockingView from '@components/BlockingViews/BlockingView';
+import * as Illustrations from '@components/Icon/Illustrations';
+import RadioListItem from '@components/SelectionList/RadioListItem';
+import type {ListItem} from '@components/SelectionList/types';
+import SelectionScreen from '@components/SelectionScreen';
+import useLocalize from '@hooks/useLocalize';
+import usePermissions from '@hooks/usePermissions';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as QuickbooksDesktop from '@libs/actions/connections/QuickbooksDesktop';
+import * as ErrorUtils from '@libs/ErrorUtils';
+import * as PolicyUtils from '@libs/PolicyUtils';
+import Navigation from '@navigation/Navigation';
+import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections';
+import withPolicyConnections from '@pages/workspace/withPolicyConnections';
+import variables from '@styles/variables';
+import {clearQBDErrorField} from '@userActions/Policy/Policy';
+import CONST from '@src/CONST';
+import ROUTES from '@src/ROUTES';
+
+type CardListItem = ListItem & {
+ value: string;
+};
+
+function QuickbooksDesktopNonReimbursableDefaultVendorSelectPage({policy}: WithPolicyConnectionsProps) {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const {vendors} = policy?.connections?.quickbooksDesktop?.data ?? {};
+ const qbdConfig = policy?.connections?.quickbooksDesktop?.config;
+ const nonReimbursableBillDefaultVendor = qbdConfig?.export?.nonReimbursableBillDefaultVendor;
+ const {canUseNewDotQBD} = usePermissions();
+
+ const policyID = policy?.id ?? '-1';
+ const sections = useMemo(() => {
+ const data: CardListItem[] =
+ vendors?.map((vendor) => ({
+ value: vendor.id,
+ text: vendor.name,
+ keyForList: vendor.name,
+ isSelected: vendor.id === nonReimbursableBillDefaultVendor,
+ })) ?? [];
+ return data.length ? [{data}] : [];
+ }, [nonReimbursableBillDefaultVendor, vendors]);
+
+ const selectVendor = useCallback(
+ (row: CardListItem) => {
+ if (row.value !== nonReimbursableBillDefaultVendor) {
+ QuickbooksDesktop.updateQuickbooksDesktopNonReimbursableBillDefaultVendor(policyID, row.value, nonReimbursableBillDefaultVendor);
+ }
+ Navigation.goBack(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_ACCOUNT.getRoute(policyID));
+ },
+ [nonReimbursableBillDefaultVendor, policyID],
+ );
+
+ const listEmptyContent = useMemo(
+ () => (
+
+ ),
+ [translate, styles.pb10],
+ );
+
+ return (
+ mode.isSelected)?.keyForList}
+ listEmptyContent={listEmptyContent}
+ connectionName={CONST.POLICY.CONNECTIONS.NAME.QBD}
+ onBackButtonPress={() => Navigation.goBack(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_ACCOUNT.getRoute(policyID))}
+ pendingAction={PolicyUtils.settingsPendingAction([CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE_BILL_DEFAULT_VENDOR], qbdConfig?.pendingFields)}
+ errors={ErrorUtils.getLatestErrorField(qbdConfig, CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE_BILL_DEFAULT_VENDOR)}
+ errorRowStyles={[styles.ph5, styles.pv3]}
+ onClose={() => clearQBDErrorField(policyID, CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE_BILL_DEFAULT_VENDOR)}
+ />
+ );
+}
+
+QuickbooksDesktopNonReimbursableDefaultVendorSelectPage.displayName = 'QuickbooksDesktopNonReimbursableDefaultVendorSelectPage';
+
+export default withPolicyConnections(QuickbooksDesktopNonReimbursableDefaultVendorSelectPage);
diff --git a/src/pages/workspace/accounting/qbd/import/QuickbooksDesktopImportPage.tsx b/src/pages/workspace/accounting/qbd/import/QuickbooksDesktopImportPage.tsx
index 3697dfeb0b22..1f8a4dde2bbd 100644
--- a/src/pages/workspace/accounting/qbd/import/QuickbooksDesktopImportPage.tsx
+++ b/src/pages/workspace/accounting/qbd/import/QuickbooksDesktopImportPage.tsx
@@ -47,8 +47,8 @@ function QuickbooksDesktopImportPage({policy}: WithPolicyProps) {
},
{
description: translate('workspace.qbd.items'),
- action: () => {}, // TODO: [QBD] will be implemented in https://github.com/Expensify/App/issues/49706
- subscribedSettings: [CONST.QUICKBOOKS_CONFIG.SYNC_LOCATIONS],
+ action: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_ITEMS.getRoute(policyID)),
+ subscribedSettings: [CONST.QUICKBOOKS_DESKTOP_CONFIG.IMPORT_ITEMS],
},
];
diff --git a/src/pages/workspace/accounting/qbd/import/QuickbooksDesktopItemsPage.tsx b/src/pages/workspace/accounting/qbd/import/QuickbooksDesktopItemsPage.tsx
new file mode 100644
index 000000000000..c73ae2d8e754
--- /dev/null
+++ b/src/pages/workspace/accounting/qbd/import/QuickbooksDesktopItemsPage.tsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import ConnectionLayout from '@components/ConnectionLayout';
+import useLocalize from '@hooks/useLocalize';
+import usePermissions from '@hooks/usePermissions';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as QuickbooksDesktop from '@libs/actions/connections/QuickbooksDesktop';
+import * as ErrorUtils from '@libs/ErrorUtils';
+import Navigation from '@libs/Navigation/Navigation';
+import * as PolicyUtils from '@libs/PolicyUtils';
+import type {WithPolicyProps} from '@pages/workspace/withPolicy';
+import withPolicyConnections from '@pages/workspace/withPolicyConnections';
+import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow';
+import {clearQBDErrorField} from '@userActions/Policy/Policy';
+import CONST from '@src/CONST';
+import ROUTES from '@src/ROUTES';
+
+function QuickbooksDesktopItemsPage({policy}: WithPolicyProps) {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const {canUseNewDotQBD} = usePermissions();
+ const policyID = policy?.id ?? '-1';
+ const qbdConfig = policy?.connections?.quickbooksDesktop?.config;
+
+ return (
+ Navigation.goBack(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_IMPORT.getRoute(policyID))}
+ >
+ QuickbooksDesktop.updateQuickbooksDesktopSyncItems(policyID, isEnabled, !!qbdConfig?.importItems)}
+ pendingAction={PolicyUtils.settingsPendingAction([CONST.QUICKBOOKS_DESKTOP_CONFIG.IMPORT_ITEMS], qbdConfig?.pendingFields)}
+ errors={ErrorUtils.getLatestErrorField(qbdConfig, CONST.QUICKBOOKS_DESKTOP_CONFIG.IMPORT_ITEMS)}
+ onCloseError={() => clearQBDErrorField(policyID, CONST.QUICKBOOKS_DESKTOP_CONFIG.IMPORT_ITEMS)}
+ />
+
+ );
+}
+
+QuickbooksDesktopItemsPage.displayName = 'QuickbooksDesktopItemsPage';
+
+export default withPolicyConnections(QuickbooksDesktopItemsPage);
diff --git a/src/pages/workspace/accounting/utils.tsx b/src/pages/workspace/accounting/utils.tsx
index 153dc52b688a..aadb8806f67f 100644
--- a/src/pages/workspace/accounting/utils.tsx
+++ b/src/pages/workspace/accounting/utils.tsx
@@ -18,7 +18,7 @@ import {getTrackingCategories} from '@userActions/connections/Xero';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {Policy} from '@src/types/onyx';
-import type {Account, ConnectionName, Connections, PolicyConnectionName, QBDReimbursableExportAccountType} from '@src/types/onyx/Policy';
+import type {Account, ConnectionName, Connections, PolicyConnectionName, QBDNonReimbursableExportAccountType, QBDReimbursableExportAccountType} from '@src/types/onyx/Policy';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import {
getImportCustomFieldsSettings,
@@ -47,11 +47,22 @@ function getAccountingIntegrationData(
integrationToDisconnect?: ConnectionName,
shouldDisconnectIntegrationBeforeConnecting?: boolean,
canUseNetSuiteUSATax?: boolean,
+ isSmallScreenWidth?: boolean,
): AccountingIntegration | undefined {
const qboConfig = policy?.connections?.quickbooksOnline?.config;
const netsuiteConfig = policy?.connections?.netsuite?.options?.config;
const netsuiteSelectedSubsidiary = (policy?.connections?.netsuite?.options?.data?.subsidiaryList ?? []).find((subsidiary) => subsidiary.internalID === netsuiteConfig?.subsidiaryID);
+ const getBackToAfterWorkspaceUpgradeRouteForQBD = () => {
+ if (integrationToDisconnect) {
+ return ROUTES.POLICY_ACCOUNTING.getRoute(policyID, connectionName, integrationToDisconnect, shouldDisconnectIntegrationBeforeConnecting);
+ }
+ if (isSmallScreenWidth) {
+ return ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_SETUP_REQUIRED_DEVICE_MODAL.getRoute(policyID);
+ }
+ return ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_SETUP_MODAL.getRoute(policyID);
+ };
+
switch (connectionName) {
case CONST.POLICY.CONNECTIONS.NAME.QBO:
return {
@@ -265,6 +276,7 @@ function getAccountingIntegrationData(
CONST.QUICKBOOKS_DESKTOP_CONFIG.ENABLE_NEW_CATEGORIES,
CONST.QUICKBOOKS_DESKTOP_CONFIG.MAPPINGS.CLASSES,
CONST.QUICKBOOKS_DESKTOP_CONFIG.MAPPINGS.CUSTOMERS,
+ CONST.QUICKBOOKS_DESKTOP_CONFIG.IMPORT_ITEMS,
],
subscribedExportSettings: [
CONST.QUICKBOOKS_DESKTOP_CONFIG.EXPORT_DATE,
@@ -272,8 +284,16 @@ function getAccountingIntegrationData(
CONST.QUICKBOOKS_DESKTOP_CONFIG.REIMBURSABLE,
CONST.QUICKBOOKS_DESKTOP_CONFIG.REIMBURSABLE_ACCOUNT,
CONST.QUICKBOOKS_DESKTOP_CONFIG.MARK_CHECKS_TO_BE_PRINTED,
+ CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE,
+ CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE_ACCOUNT,
+ CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE_BILL_DEFAULT_VENDOR,
+ CONST.QUICKBOOKS_DESKTOP_CONFIG.SHOULD_AUTO_CREATE_VENDOR,
],
subscribedAdvancedSettings: [CONST.QUICKBOOKS_DESKTOP_CONFIG.SHOULD_AUTO_CREATE_VENDOR, CONST.QUICKBOOKS_DESKTOP_CONFIG.AUTO_SYNC],
+ workspaceUpgradeNavigationDetails: {
+ integrationAlias: CONST.UPGRADE_FEATURE_INTRO_MAPPING.quickbooksDesktop.alias,
+ backToAfterWorkspaceUpgradeRoute: getBackToAfterWorkspaceUpgradeRouteForQBD(),
+ },
};
default:
return undefined;
@@ -313,8 +333,11 @@ function getSynchronizationErrorMessage(
return `${syncError} ("${connection?.lastSync?.errorMessage}")`;
}
-function getQBDReimbursableAccounts(quickbooksDesktop?: Connections[typeof CONST.POLICY.CONNECTIONS.NAME.QBD], reimbursable?: QBDReimbursableExportAccountType | undefined) {
- const {bankAccounts, journalEntryAccounts, payableAccounts} = quickbooksDesktop?.data ?? {};
+function getQBDReimbursableAccounts(
+ quickbooksDesktop?: Connections[typeof CONST.POLICY.CONNECTIONS.NAME.QBD],
+ reimbursable?: QBDReimbursableExportAccountType | QBDNonReimbursableExportAccountType,
+) {
+ const {bankAccounts, journalEntryAccounts, payableAccounts, creditCardAccounts} = quickbooksDesktop?.data ?? {};
let accounts: Account[];
switch (reimbursable ?? quickbooksDesktop?.config?.export.reimbursable) {
@@ -328,6 +351,9 @@ function getQBDReimbursableAccounts(quickbooksDesktop?: Connections[typeof CONST
case CONST.QUICKBOOKS_DESKTOP_REIMBURSABLE_ACCOUNT_TYPE.VENDOR_BILL:
accounts = payableAccounts ?? [];
break;
+ case CONST.QUICKBOOKS_DESKTOP_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.CREDIT_CARD:
+ accounts = creditCardAccounts ?? [];
+ break;
default:
accounts = [];
}
diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
index 1286d504d3ec..366f30d2fd73 100644
--- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
+++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
@@ -32,6 +32,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
+import {isConnectionInProgress} from '@libs/actions/connections';
import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import localeCompare from '@libs/LocaleCompare';
@@ -73,6 +74,9 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) {
const policy = usePolicy(policyId);
const {selectionMode} = useMobileSelectionMode();
const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyId}`);
+ const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policy?.id}`);
+ const isSyncInProgress = isConnectionInProgress(connectionSyncProgress, policy);
+ const hasSyncError = PolicyUtils.hasSyncError(policy, isSyncInProgress);
const isConnectedToAccounting = Object.keys(policy?.connections ?? {}).length > 0;
const currentConnectionName = PolicyUtils.getCurrentConnectionName(policy);
@@ -284,7 +288,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) {
const getHeaderText = () => (
- {isConnectedToAccounting ? (
+ {!hasSyncError && isConnectedToAccounting ? (
{`${translate('workspace.categories.importedFromAccountingSoftware')} `}
Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARD_NAME.getRoute(policyID, cardID, bank))}
@@ -143,26 +144,26 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag
/>
}
description={translate('workspace.moreFeatures.companyCards.lastUpdated')}
- title={card?.isLoadingLastUpdated ? translate('workspace.moreFeatures.companyCards.updating') : card?.lastUpdated}
+ title={card?.isLoadingLastUpdated ? translate('workspace.moreFeatures.companyCards.updating') : card?.lastScrape}
interactive={false}
/>
Policy.clearCompanyCardErrorField(workspaceAccountID, cardID, bank, 'lastUpdated', true)}
+ errors={ErrorUtils.getLatestErrorField(card ?? {}, 'lastScrape')}
+ onClose={() => Policy.clearCompanyCardErrorField(workspaceAccountID, cardID, bank, 'lastScrape', true)}
>
diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardEditCardNamePage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardEditCardNamePage.tsx
index c55d575838a8..56e8d88d8d54 100644
--- a/src/pages/workspace/companyCards/WorkspaceCompanyCardEditCardNamePage.tsx
+++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardEditCardNamePage.tsx
@@ -27,16 +27,15 @@ type WorkspaceCompanyCardEditCardNamePageProps = StackScreenProps) => {
- Policy.updateCompanyCardName(workspaceAccountID, cardID, values[INPUT_IDS.NAME], bank);
+ Policy.updateCompanyCardName(workspaceAccountID, cardID, values[INPUT_IDS.NAME], bank, defaultValue);
Navigation.goBack();
};
@@ -72,7 +71,7 @@ function WorkspaceCompanyCardEditCardNamePage({route}: WorkspaceCompanyCardEditC
hint={translate('workspace.moreFeatures.companyCards.giveItNameInstruction')}
aria-label={translate('workspace.moreFeatures.companyCards.cardName')}
role={CONST.ROLE.PRESENTATION}
- defaultValue={card?.nameValuePairs?.cardTitle}
+ defaultValue={defaultValue}
maxLength={CONST.EXPENSIFY_CARD.CARD_TITLE_INPUT_LIMIT}
ref={inputCallbackRef}
/>
diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx
index 767f08068f95..2589f05e2769 100644
--- a/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx
+++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx
@@ -13,64 +13,45 @@ import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as CardUtils from '@libs/CardUtils';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
+import * as PolicyUtils from '@libs/PolicyUtils';
import Navigation from '@navigation/Navigation';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import variables from '@styles/variables';
import * as Card from '@userActions/Card';
+import * as CompanyCards from '@userActions/CompanyCards';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
-import type {CardFeeds} from '@src/types/onyx';
+import type {CompanyCardFeed} from '@src/types/onyx';
type CardFeedListItem = ListItem & {
/** Card feed value */
- value: string;
-};
-
-const mockedData: CardFeeds = {
- companyCards: {
- cdfbmo: {
- pending: false,
- asrEnabled: true,
- forceReimbursable: 'force_no',
- liabilityType: 'corporate',
- errors: {error: CONST.COMPANY_CARDS.CONNECTION_ERROR},
- preferredPolicy: '',
- reportTitleFormat: '{report:card}{report:bank}{report:submit:from}{report:total}{report:enddate:MMMM}',
- statementPeriodEndDay: 'LAST_DAY_OF_MONTH',
- },
- },
- companyCardNicknames: {
- cdfbmo: 'BMO MasterCard',
- },
+ value: CompanyCardFeed;
};
type WorkspaceCompanyCardFeedSelectorPageProps = StackScreenProps;
function WorkspaceCompanyCardFeedSelectorPage({route}: WorkspaceCompanyCardFeedSelectorPageProps) {
const {policyID} = route.params;
+ const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policyID);
const {translate} = useLocalize();
const styles = useThemeStyles();
- // TODO: use data form onyx instead of mocked one when API is implemented
- // const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`);
+ const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`);
const [lastSelectedFeed] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`);
+ const selectedFeed = CardUtils.getSelectedFeed(lastSelectedFeed, cardFeeds);
- const cardFeeds = mockedData;
- const defaultFeed = Object.keys(cardFeeds?.companyCards ?? {}).at(0);
- const selectedFeed = lastSelectedFeed ?? defaultFeed ?? '';
-
- const feeds: CardFeedListItem[] = Object.entries(cardFeeds?.companyCardNicknames ?? {}).map(([key, value]) => ({
- value: key,
- text: value,
- keyForList: key,
- isSelected: key === selectedFeed,
- brickRoadIndicator: CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR,
- canShowSeveralIndicators: !!cardFeeds?.companyCards?.[selectedFeed]?.errors,
+ const feeds: CardFeedListItem[] = Object.keys(cardFeeds?.settings?.companyCards ?? {}).map((feed) => ({
+ value: feed as CompanyCardFeed,
+ text: cardFeeds?.settings?.companyCardNicknames?.[feed] ?? translate(`workspace.companyCards.addNewCard.cardProviders.${feed as CompanyCardFeed}`),
+ keyForList: feed,
+ isSelected: feed === selectedFeed,
+ brickRoadIndicator: cardFeeds?.settings?.companyCards?.[feed]?.errors ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
+ canShowSeveralIndicators: !!cardFeeds?.settings?.companyCards?.[feed]?.errors,
leftElement: (
{
- // TODO: navigate to Add Feed flow when it's implemented
+ CompanyCards.clearAddNewCardFlow();
+ Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW.getRoute(policyID));
}}
/>
}
diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx
index c9c01592304a..fd2c73ddb402 100644
--- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx
+++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx
@@ -1,12 +1,13 @@
import React, {useCallback, useMemo} from 'react';
import type {ListRenderItemInfo} from 'react-native';
-import {FlatList, View} from 'react-native';
+import {ActivityIndicator, FlatList, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import {PressableWithFeedback} from '@components/Pressable';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
+import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import * as CardUtils from '@libs/CardUtils';
import Navigation from '@navigation/Navigation';
@@ -27,8 +28,10 @@ type WorkspaceCompanyCardsListProps = {
function WorkspaceCompanyCardsList({cardsList, policyID}: WorkspaceCompanyCardsListProps) {
const styles = useThemeStyles();
+ const theme = useTheme();
const {translate} = useLocalize();
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
+ const [customCardNames] = useOnyx(ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES);
const sortedCards = useMemo(() => CardUtils.sortCardsByCardholderName(cardsList, personalDetails), [cardsList, personalDetails]);
@@ -55,14 +58,14 @@ function WorkspaceCompanyCardsList({cardsList, policyID}: WorkspaceCompanyCardsL
>
);
},
- [cardsList, personalDetails, policyID, styles],
+ [cardsList, customCardNames, personalDetails, policyID, styles],
);
const renderListHeader = useCallback(
@@ -85,6 +88,16 @@ function WorkspaceCompanyCardsList({cardsList, policyID}: WorkspaceCompanyCardsL
[styles, translate],
);
+ if (!cardsList) {
+ return (
+
+ );
+ }
+
if (sortedCards.length === 0) {
return ;
}
diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx
index 320eb71247cb..74e6593d6986 100644
--- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx
+++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx
@@ -18,13 +18,14 @@ import Navigation from '@navigation/Navigation';
import variables from '@styles/variables';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type {CompanyCardFeed} from '@src/types/onyx';
type WorkspaceCompanyCardsListHeaderButtonsProps = {
/** Current policy id */
policyID: string;
/** Currently selected feed */
- selectedFeed: string;
+ selectedFeed: CompanyCardFeed;
};
function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed}: WorkspaceCompanyCardsListHeaderButtonsProps) {
@@ -35,10 +36,11 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed}: Worksp
const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policyID);
const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`);
const shouldChangeLayout = isMediumScreenWidth || shouldUseNarrowLayout;
+ const feedName = cardFeeds?.settings?.companyCardNicknames?.[selectedFeed] ?? translate(`workspace.companyCards.addNewCard.cardProviders.${selectedFeed}`);
return (
@@ -46,7 +48,7 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed}: Worksp
Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_SELECT_FEED.getRoute(policyID))}
style={[styles.flexRow, styles.alignItemsCenter, styles.gap3, shouldChangeLayout && styles.mb3]}
- accessibilityLabel={cardFeeds?.companyCardNicknames?.[selectedFeed] ?? ''}
+ accessibilityLabel={feedName}
>
- {cardFeeds?.companyCardNicknames?.[selectedFeed]}
+ {feedName}
- {PolicyUtils.hasPolicyFeedsError(cardFeeds?.companyCards ?? {}, selectedFeed) && (
+ {PolicyUtils.hasPolicyFeedsError(cardFeeds?.settings?.companyCards ?? {}, selectedFeed) && (
{}}
+ isDisabled={!!cardFeeds?.settings?.companyCards?.[selectedFeed].pending || !!cardFeeds?.settings?.companyCards?.[selectedFeed].errors}
+ onPress={() => Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD.getRoute(policyID, selectedFeed))}
icon={Expensicons.Plus}
text={translate('workspace.companyCards.assignCard')}
style={shouldChangeLayout && styles.flex1}
diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx
index 25f1487381ab..14c7322e9c19 100644
--- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx
+++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx
@@ -1,12 +1,13 @@
import {useFocusEffect} from '@react-navigation/native';
import type {StackScreenProps} from '@react-navigation/stack';
-import React, {useCallback} from 'react';
+import React, {useCallback, useEffect} from 'react';
import {ActivityIndicator} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import * as Illustrations from '@components/Icon/Illustrations';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
+import * as CardUtils from '@libs/CardUtils';
import type {FullScreenNavigatorParamList} from '@libs/Navigation/types';
import * as PolicyUtils from '@libs/PolicyUtils';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
@@ -15,103 +16,11 @@ import * as Policy from '@userActions/Policy/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
-import type {WorkspaceCardsList} from '@src/types/onyx';
import WorkspaceCompanyCardPageEmptyState from './WorkspaceCompanyCardPageEmptyState';
-import WorkspaceCompanyCardsFeedAddedEmptyPage from './WorkspaceCompanyCardsFeedAddedEmptyPage';
import WorkspaceCompanyCardsFeedPendingPage from './WorkspaceCompanyCardsFeedPendingPage';
import WorkspaceCompanyCardsList from './WorkspaceCompanyCardsList';
import WorkspaceCompanyCardsListHeaderButtons from './WorkspaceCompanyCardsListHeaderButtons';
-const mockedCards = {
- id1: {
- cardID: 885646,
- accountID: 11309072,
- bank: 'cdfbmo',
- nameValuePairs: {
- cardTitle: 'Test 1',
- },
- cardNumber: '1234 56XX XXXX 1222',
- },
- id2: {
- accountID: 885646,
- bank: 'cdfbmo',
- cardID: 885642,
- nameValuePairs: {
- cardTitle: 'Test 2',
- },
- cardNumber: '1234 56XX XXXX 1222',
- },
- id18: {
- accountID: 885646,
- cardID: 885643,
- nameValuePairs: {
- cardTitle: 'Test 1',
- },
- cardNumber: '1234 56XX XXXX 1222',
- },
- id27: {
- cardID: 885644,
- accountID: 885646,
- bank: 'cdfbmo',
- nameValuePairs: {
- cardTitle: 'Test 2',
- },
- cardNumber: '1234 56XX XXXX 1222',
- },
- id16: {
- cardID: 885645,
- accountID: 885646,
- bank: 'cdfbmo',
- nameValuePairs: {
- cardTitle: 'Test 1',
- },
- cardNumber: '1234 56XX XXXX 1222',
- },
- id25: {
- cardID: 885646,
- accountID: 885646,
- nameValuePairs: {
- cardTitle: 'Test 2',
- },
- cardNumber: '1234 56XX XXXX 1222',
- },
- id14: {
- cardID: 885647,
- accountID: 885646,
- bank: 'cdfbmo',
- nameValuePairs: {
- cardTitle: 'Test 1',
- },
- cardNumber: '1234 56XX XXXX 1222',
- },
- id23: {
- cardID: 885648,
- accountID: 885646,
- bank: 'cdfbmo',
- nameValuePairs: {
- cardTitle: 'Test 2',
- },
- cardNumber: '1234 56XX XXXX 1222',
- },
- id12: {
- cardID: 885649,
- accountID: 885646,
- nameValuePairs: {
- cardTitle: 'Test 1',
- },
- cardNumber: '1234 56XX XXXX 1222',
- },
- id21: {
- cardID: 885640,
- accountID: 885646,
- bank: 'cdfbmo',
- nameValuePairs: {
- cardTitle: 'Test 2',
- },
- cardNumber: '1234 56XX XXXX 1222',
- },
-} as unknown as WorkspaceCardsList;
-
type WorkspaceCompanyCardPageProps = StackScreenProps;
function WorkspaceCompanyCardPage({route}: WorkspaceCompanyCardPageProps) {
@@ -120,26 +29,31 @@ function WorkspaceCompanyCardPage({route}: WorkspaceCompanyCardPageProps) {
const theme = useTheme();
const policyID = route.params.policyID;
const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policyID);
- const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`);
const [lastSelectedFeed] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`);
- const defaultFeed = Object.keys(cardFeeds?.companyCards ?? {}).at(0);
- const selectedFeed = lastSelectedFeed ?? defaultFeed;
+ const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`);
+ const selectedFeed = CardUtils.getSelectedFeed(lastSelectedFeed, cardFeeds);
+ const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${selectedFeed}`);
+
+ const isLoading = !cardFeeds || !!(cardFeeds.isLoading && !cardFeeds.settings);
+ const companyCards = cardFeeds?.settings?.companyCards ?? {};
+ const selectedCompanyCard = companyCards[selectedFeed ?? ''] ?? null;
+ const isNoFeed = !selectedCompanyCard;
+ const isPending = !!selectedCompanyCard?.pending;
+ const isFeedAdded = !isPending && !isNoFeed;
+
const fetchCompanyCards = useCallback(() => {
Policy.openPolicyCompanyCardsPage(policyID, workspaceAccountID);
}, [policyID, workspaceAccountID]);
useFocusEffect(fetchCompanyCards);
- const companyCards = cardFeeds?.companyCards ?? {};
- const selectedCompanyCard = companyCards[selectedFeed ?? ''] ?? null;
- const isNoFeed = !selectedCompanyCard;
- const isPending = selectedCompanyCard?.pending;
- const isFeedAdded = !isPending && !isNoFeed;
- const isLoading = !cardFeeds || cardFeeds.isLoading;
+ useEffect(() => {
+ if (isLoading || !selectedFeed || isPending) {
+ return;
+ }
- // TODO: use data form onyx instead of mocked one when API is implemented
- // const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${selectedFeed}`);
- const cardsList = mockedCards ?? {};
+ Policy.openPolicyCompanyCardsFeed(policyID, selectedFeed);
+ }, [selectedFeed, isLoading, policyID, isPending]);
return (
)}
{isNoFeed && }
- {isFeedAdded && !isPending && }
{isPending && }
{isFeedAdded && !isPending && (
) => {
@@ -57,7 +58,7 @@ function WorkspaceCompanyCardsSettingsFeedNamePage({
);
const submit = ({name}: WorkspaceCompanyCardFeedName) => {
- Policy.setWorkspaceCompanyCardFeedName(policyID, workspaceAccountID, lastSelectedFeed, name);
+ Policy.setWorkspaceCompanyCardFeedName(policyID, workspaceAccountID, selectedFeed, name);
Navigation.goBack(ROUTES.WORKSPACE_COMPANY_CARDS_SETTINGS.getRoute(policyID));
};
@@ -65,7 +66,7 @@ function WorkspaceCompanyCardsSettingsFeedNamePage({
{
@@ -49,7 +49,7 @@ function WorkspaceCompanyCardsSettingsPage({
};
const deleteCompanyCardFeed = () => {
- Policy.deleteWorkspaceCompanyCardFeed(policyID, workspaceAccountID, lastSelectedFeed);
+ Policy.deleteWorkspaceCompanyCardFeed(policyID, workspaceAccountID, selectedFeed);
setDeleteCompanyCardConfirmModalVisible(false);
Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack);
};
@@ -57,7 +57,8 @@ function WorkspaceCompanyCardsSettingsPage({
const onToggleLiability = (isOn: boolean) => {
Policy.setWorkspaceCompanyCardTransactionLiability(
workspaceAccountID,
- lastSelectedFeed,
+ policyID,
+ selectedFeed,
isOn ? CONST.COMPANY_CARDS.DELETE_TRANSACTIONS.ALLOW : CONST.COMPANY_CARDS.DELETE_TRANSACTIONS.RESTRICT,
);
};
diff --git a/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx b/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx
index 44b444314c66..480bd5d538fe 100644
--- a/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx
+++ b/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx
@@ -33,7 +33,7 @@ function AddNewCardPage({policy}: WithPolicyAndFullscreenLoadingProps) {
case CONST.COMPANY_CARDS.STEP.CARD_NAME:
return ;
case CONST.COMPANY_CARDS.STEP.CARD_DETAILS:
- return ;
+ return ;
case CONST.COMPANY_CARDS.STEP.AMEX_CUSTOM_FEED:
return ;
default:
@@ -48,7 +48,7 @@ function AddNewCardPage({policy}: WithPolicyAndFullscreenLoadingProps) {
case CONST.COMPANY_CARDS.STEP.CARD_NAME:
return ;
case CONST.COMPANY_CARDS.STEP.CARD_DETAILS:
- return ;
+ return ;
default:
return ;
}
diff --git a/src/pages/workspace/companyCards/addNew/AmexCustomFeed.tsx b/src/pages/workspace/companyCards/addNew/AmexCustomFeed.tsx
index f168c72924ea..765b42997573 100644
--- a/src/pages/workspace/companyCards/addNew/AmexCustomFeed.tsx
+++ b/src/pages/workspace/companyCards/addNew/AmexCustomFeed.tsx
@@ -30,7 +30,7 @@ function AmexCustomFeed() {
CompanyCards.setAddNewCompanyCardStepAndData({
step: typeSelected === CONST.COMPANY_CARDS.AMEX_CUSTOM_FEED.CORPORATE ? CONST.COMPANY_CARDS.STEP.CARD_INSTRUCTIONS : CONST.COMPANY_CARDS.STEP.BANK_CONNECTION,
data: {
- cardType: CONST.COMPANY_CARDS.CARD_TYPE.AMEX,
+ feedType: CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX,
selectedAmexCustomFeed: typeSelected,
},
});
diff --git a/src/pages/workspace/companyCards/addNew/CardInstructionsStep.tsx b/src/pages/workspace/companyCards/addNew/CardInstructionsStep.tsx
index 604d5ed36b08..1b49074a116a 100644
--- a/src/pages/workspace/companyCards/addNew/CardInstructionsStep.tsx
+++ b/src/pages/workspace/companyCards/addNew/CardInstructionsStep.tsx
@@ -1,4 +1,3 @@
-import {Str} from 'expensify-common';
import React from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
@@ -12,6 +11,7 @@ import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import usePermissions from '@hooks/usePermissions';
import useThemeStyles from '@hooks/useThemeStyles';
+import * as CardUtils from '@libs/CardUtils';
import Parser from '@libs/Parser';
import Navigation from '@navigation/Navigation';
import * as Card from '@userActions/Card';
@@ -31,10 +31,10 @@ function CardInstructionsStep({policyID}: CardInstructionsStepProps) {
const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD);
const data = addNewCard?.data;
- const feedProvider = data?.cardType;
+ const feedProvider = data?.feedType ?? CONST.COMPANY_CARD.FEED_BANK_NAME.VISA;
const bank = data?.selectedBank;
- const isStripeFeedProvider = feedProvider === CONST.COMPANY_CARDS.CARD_TYPE.STRIPE;
- const isAmexFeedProvider = feedProvider === CONST.COMPANY_CARDS.CARD_TYPE.AMEX;
+ const isStripeFeedProvider = feedProvider === CONST.COMPANY_CARD.FEED_BANK_NAME.STRIPE;
+ const isAmexFeedProvider = feedProvider === CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX;
const isOtherBankSelected = bank === CONST.COMPANY_CARDS.BANKS.OTHER;
const buttonTranslation = isStripeFeedProvider ? translate('common.submit') : translate('common.next');
@@ -86,7 +86,7 @@ function CardInstructionsStep({policyID}: CardInstructionsStepProps) {
contentContainerStyle={styles.flexGrow1}
>
- {translate('workspace.companyCards.addNewCard.enableFeed.title', {provider: Str.recapitalize(feedProvider ?? '')})}
+ {translate('workspace.companyCards.addNewCard.enableFeed.title', {provider: CardUtils.getCardFeedName(feedProvider)})}
{translate('workspace.companyCards.addNewCard.enableFeed.heading')}
diff --git a/src/pages/workspace/companyCards/addNew/CardNameStep.tsx b/src/pages/workspace/companyCards/addNew/CardNameStep.tsx
index d8d6fe10acf4..2b910515ba64 100644
--- a/src/pages/workspace/companyCards/addNew/CardNameStep.tsx
+++ b/src/pages/workspace/companyCards/addNew/CardNameStep.tsx
@@ -30,7 +30,7 @@ function CardNameStep() {
CompanyCards.setAddNewCompanyCardStepAndData({
step: CONST.COMPANY_CARDS.STEP.CARD_DETAILS,
data: {
- cardTitle: values.cardTitle,
+ bankName: values.cardTitle,
},
isEditing: false,
});
@@ -65,7 +65,7 @@ function CardNameStep() {
inputID={INPUT_IDS.CARD_TITLE}
label={translate('workspace.companyCards.addNewCard.enterNameOfBank')}
role={CONST.ROLE.PRESENTATION}
- defaultValue={addNewCard?.data?.cardTitle}
+ defaultValue={addNewCard?.data?.bankName}
containerStyles={[styles.mb6]}
ref={inputCallbackRef}
/>
diff --git a/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx b/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx
index 9e0cd64673ad..b2f5f3f0c321 100644
--- a/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx
+++ b/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx
@@ -2,7 +2,6 @@ import React, {useEffect, useState} from 'react';
import {View} from 'react-native';
import type {StyleProp, ViewStyle} from 'react-native';
import {useOnyx} from 'react-native-onyx';
-import type {ValueOf} from 'type-fest';
import FormHelpMessage from '@components/FormHelpMessage';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Icon from '@components/Icon';
@@ -20,21 +19,22 @@ import variables from '@styles/variables';
import * as CompanyCards from '@userActions/CompanyCards';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import type {CompanyCardFeed} from '@src/types/onyx';
type AvailableCompanyCardTypes = {
isAmexAvailable?: boolean;
translate: LocaleContextProps['translate'];
- typeSelected?: ValueOf;
+ typeSelected?: CompanyCardFeed;
styles: StyleProp;
};
function getAvailableCompanyCardTypes({isAmexAvailable, translate, typeSelected, styles}: AvailableCompanyCardTypes) {
const defaultTypes = [
{
- value: CONST.COMPANY_CARDS.CARD_TYPE.MASTERCARD,
- text: translate('workspace.companyCards.addNewCard.cardProviders.mastercard'),
- keyForList: CONST.COMPANY_CARDS.CARD_TYPE.MASTERCARD,
- isSelected: typeSelected === CONST.COMPANY_CARDS.CARD_TYPE.MASTERCARD,
+ value: CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD,
+ text: translate('workspace.companyCards.addNewCard.cardProviders.cdf'),
+ keyForList: CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD,
+ isSelected: typeSelected === CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD,
leftElement: (
>();
+ const [typeSelected, setTypeSelected] = useState();
const {canUseDirectFeeds} = usePermissions();
const [isError, setIsError] = useState(false);
const data = getAvailableCompanyCardTypes({isAmexAvailable: !canUseDirectFeeds, translate, typeSelected, styles: styles.mr3});
@@ -99,7 +99,7 @@ function CardTypeStep() {
CompanyCards.setAddNewCompanyCardStepAndData({
step: CONST.COMPANY_CARDS.STEP.CARD_INSTRUCTIONS,
data: {
- cardType: typeSelected,
+ feedType: typeSelected,
},
isEditing: false,
});
@@ -107,8 +107,8 @@ function CardTypeStep() {
};
useEffect(() => {
- setTypeSelected(addNewCard?.data.cardType);
- }, [addNewCard?.data.cardType]);
+ setTypeSelected(addNewCard?.data.feedType);
+ }, [addNewCard?.data.feedType]);
const handleBackButtonPress = () => {
if (canUseDirectFeeds) {
@@ -139,7 +139,7 @@ function CardTypeStep() {
}}
sections={[{data}]}
shouldSingleExecuteRowSelect
- initiallyFocusedOptionKey={addNewCard?.data.cardType}
+ initiallyFocusedOptionKey={addNewCard?.data.feedType}
shouldUpdateFocusedIndex
showConfirmButton
confirmButtonText={translate('common.next')}
diff --git a/src/pages/workspace/companyCards/addNew/DetailsStep.tsx b/src/pages/workspace/companyCards/addNew/DetailsStep.tsx
index ba8a89c44438..87c819b074b6 100644
--- a/src/pages/workspace/companyCards/addNew/DetailsStep.tsx
+++ b/src/pages/workspace/companyCards/addNew/DetailsStep.tsx
@@ -12,25 +12,46 @@ import useAutoFocusInput from '@hooks/useAutoFocusInput';
import useLocalize from '@hooks/useLocalize';
import usePermissions from '@hooks/usePermissions';
import useThemeStyles from '@hooks/useThemeStyles';
+import * as Policy from '@libs/actions/Policy/Policy';
import * as ValidationUtils from '@libs/ValidationUtils';
+import Navigation from '@navigation/Navigation';
import * as CompanyCards from '@userActions/CompanyCards';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
import INPUT_IDS from '@src/types/form/AddNewCardFeedForm';
-function DetailsStep() {
+type DetailsStepProps = {
+ /** ID of the current policy */
+ policyID: string;
+};
+
+function DetailsStep({policyID}: DetailsStepProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const {inputCallbackRef} = useAutoFocusInput();
const {canUseDirectFeeds} = usePermissions();
const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD);
- const feedProvider = addNewCard?.data?.cardType;
- const isStripeFeedProvider = feedProvider === CONST.COMPANY_CARDS.CARD_TYPE.STRIPE;
+ const feedProvider = addNewCard?.data?.feedType;
+ const isStripeFeedProvider = feedProvider === CONST.COMPANY_CARD.FEED_BANK_NAME.STRIPE;
const bank = addNewCard?.data?.selectedBank;
const isOtherBankSelected = bank === CONST.COMPANY_CARDS.BANKS.OTHER;
- // TODO: add submit function
- const submit = () => {};
+ const submit = (values: FormOnyxValues) => {
+ if (!addNewCard?.data) {
+ return;
+ }
+
+ const feedDetails = Object.entries({
+ ...values,
+ bankName: addNewCard.data.bankName ?? 'Amex',
+ })
+ .map(([key, value]) => `${key}: ${value}`)
+ .join(', ');
+
+ Policy.addNewCompanyCardsFeed(policyID, addNewCard.data.feedType, feedDetails);
+ Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID));
+ };
const handleBackButtonPress = () => {
if (!canUseDirectFeeds || isOtherBankSelected) {
@@ -45,7 +66,7 @@ function DetailsStep() {
const errors = ValidationUtils.getFieldRequiredErrors(values, [INPUT_IDS.BANK_ID]);
switch (feedProvider) {
- case CONST.COMPANY_CARDS.CARD_TYPE.VISA:
+ case CONST.COMPANY_CARD.FEED_BANK_NAME.VISA:
if (!values[INPUT_IDS.BANK_ID]) {
errors[INPUT_IDS.BANK_ID] = translate('common.error.fieldRequired');
}
@@ -56,12 +77,12 @@ function DetailsStep() {
errors[INPUT_IDS.COMPANY_ID] = translate('common.error.fieldRequired');
}
break;
- case CONST.COMPANY_CARDS.CARD_TYPE.MASTERCARD:
+ case CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD:
if (!values[INPUT_IDS.DISTRIBUTION_ID]) {
errors[INPUT_IDS.DISTRIBUTION_ID] = translate('common.error.fieldRequired');
}
break;
- case CONST.COMPANY_CARDS.CARD_TYPE.AMEX:
+ case CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX:
if (!values[INPUT_IDS.DELIVERY_FILE_NAME]) {
errors[INPUT_IDS.DELIVERY_FILE_NAME] = translate('common.error.fieldRequired');
}
@@ -76,13 +97,13 @@ function DetailsStep() {
const renderInputs = () => {
switch (feedProvider) {
- case CONST.COMPANY_CARDS.CARD_TYPE.VISA:
+ case CONST.COMPANY_CARD.FEED_BANK_NAME.VISA:
return (
<>
>
);
- case CONST.COMPANY_CARDS.CARD_TYPE.MASTERCARD:
+ case CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD:
return (
);
- case CONST.COMPANY_CARDS.CARD_TYPE.AMEX:
+ case CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX:
return (
& WithPolicyAndFullscreenLoadingProps;
function AssignCardFeedPage({route, policy}: AssignCardFeedPageProps) {
const [assignCard] = useOnyx(ONYXKEYS.ASSIGN_CARD);
const currentStep = assignCard?.currentStep;
const feed = route.params?.feed;
+ const backTo = route.params?.backTo;
+ const policyID = policy?.id ?? '-1';
useEffect(() => {
- CompanyCards.setAssignCardStepAndData({data: {feed}});
+ CompanyCards.setAssignCardStepAndData({data: {bankName: feed}});
}, [feed]);
switch (currentStep) {
case CONST.COMPANY_CARD.STEP.ASSIGNEE:
return ;
case CONST.COMPANY_CARD.STEP.CARD:
- return ;
+ return (
+
+ );
case CONST.COMPANY_CARD.STEP.TRANSACTION_START_DATE:
return ;
case CONST.COMPANY_CARD.STEP.CONFIRMATION:
- return ;
+ return (
+
+ );
default:
return ;
}
diff --git a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx
index b31780d54fa2..ef5c51927a9b 100644
--- a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx
+++ b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx
@@ -1,7 +1,6 @@
import React, {useState} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
-import type {ValueOf} from 'type-fest';
import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton';
import Icon from '@components/Icon';
import * as Illustrations from '@components/Icon/Illustrations';
@@ -15,54 +14,40 @@ import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as CardUtils from '@libs/CardUtils';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
+import * as PolicyUtils from '@libs/PolicyUtils';
import variables from '@styles/variables';
import * as CompanyCards from '@userActions/CompanyCards';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type {CompanyCardFeed} from '@src/types/onyx';
-type MockedCard = {
- key: string;
- cardNumber: string;
-};
-
-const mockedCardList = [
- {
- key: '1',
- cardNumber: '123412XXXXXX1234',
- },
- {
- key: '2',
- cardNumber: '123412XXXXXX1235',
- },
- {
- key: '3',
- cardNumber: '123412XXXXXX1236',
- },
-];
-
-const mockedCardListEmpty: MockedCard[] = [];
+type CardSelectionStepProps = {
+ /** Selected feed */
+ feed: CompanyCardFeed;
-const feedNamesMapping = {
- [CONST.COMPANY_CARD.FEED_BANK_NAME.VISA]: 'Visa',
- [CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD]: 'MasterCard',
- [CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX]: 'American Express',
+ /** Current policy id */
+ policyID: string;
};
-type CardSelectionStepProps = {
- feed: string;
-};
+function CardSelectionStep({feed, policyID}: CardSelectionStepProps) {
+ const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policyID);
-function CardSelectionStep({feed}: CardSelectionStepProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const {environmentURL} = useEnvironment();
const [assignCard] = useOnyx(ONYXKEYS.ASSIGN_CARD);
+ const [list] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${feed}`);
const isEditing = assignCard?.isEditing;
const assignee = assignCard?.data?.email ?? '';
+ const {cardList, ...cards} = list ?? {};
+ // We need to filter out cards which already has been assigned
+ const filteredCardList = Object.fromEntries(
+ Object.entries(cardList ?? {}).filter(([cardNumber]) => !Object.values(cards).find((card) => card.lastFourPAN && cardNumber.endsWith(card.lastFourPAN))),
+ );
- const [cardSelected, setCardSelected] = useState(assignCard?.data?.cardName ?? '');
+ const [cardSelected, setCardSelected] = useState(assignCard?.data?.encryptedCardNumber ?? '');
const [shouldShowError, setShouldShowError] = useState(false);
const handleBackButtonPress = () => {
@@ -86,21 +71,24 @@ function CardSelectionStep({feed}: CardSelectionStepProps) {
setShouldShowError(true);
return;
}
+
+ const cardName =
+ Object.entries(filteredCardList)
+ .find(([, encryptedCardNumber]) => encryptedCardNumber === cardSelected)
+ ?.at(0) ?? '';
+
CompanyCards.setAssignCardStepAndData({
currentStep: isEditing ? CONST.COMPANY_CARD.STEP.CONFIRMATION : CONST.COMPANY_CARD.STEP.TRANSACTION_START_DATE,
- data: {cardName: cardSelected},
+ data: {encryptedCardNumber: cardSelected, cardName},
isEditing: false,
});
};
- // TODO: for now mocking cards
- const mockedCards = !Object.values(CONST.COMPANY_CARD.FEED_BANK_NAME).some((value) => value === feed) ? mockedCardListEmpty : mockedCardList;
-
- const cardListOptions = mockedCards.map((item) => ({
- keyForList: item?.cardNumber,
- value: item?.cardNumber,
- text: item?.cardNumber,
- isSelected: cardSelected === item?.cardNumber,
+ const cardListOptions = Object.entries(filteredCardList).map(([cardNumber, encryptedCardNumber]) => ({
+ keyForList: encryptedCardNumber,
+ value: encryptedCardNumber,
+ text: cardNumber,
+ isSelected: cardSelected === encryptedCardNumber,
leftElement: (
{translate('workspace.companyCards.chooseCardFor', {
assignee: PersonalDetailsUtils.getPersonalDetailByEmail(assignee ?? '')?.displayName ?? '',
- feed: feedNamesMapping[feed as ValueOf] ?? 'visa',
+ feed: CardUtils.getCardFeedName(feed),
})}
{
- Navigation.goBack();
+ Policy.assignWorkspaceCompanyCard(policyID, data);
+ Navigation.navigate(backTo ?? ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID));
CompanyCards.clearAssignCardStepAndData();
};
diff --git a/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx b/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx
index e0ae8954720c..5dff1e9b5109 100644
--- a/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx
+++ b/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx
@@ -108,6 +108,7 @@ function LimitTypeStep({policy}: LimitTypeStepProps) {
shouldSingleExecuteRowSelect
initiallyFocusedOptionKey={typeSelected}
shouldUpdateFocusedIndex
+ isAlternateTextMultilineSupported
/>
!feed.pending).length > 0;
- // TODO: for now enabled for testing purposes. Change this to check for the actual multiple feeds when API is ready
- const hasMultipleFeeds = policy?.areCompanyCardsEnabled;
+ useEffect(() => {
+ Policy.openPolicyCompanyCardsPage(policyID, workspaceAccountID);
+ }, [policyID, workspaceAccountID]);
const memberCards = useMemo(() => {
- if (!expensifyCardsList) {
+ if (!allCardsList) {
return [];
}
- return Object.values(expensifyCardsList).filter((expensifyCard) => expensifyCard.accountID === accountID);
- }, [expensifyCardsList, accountID]);
+ return Object.values(allCardsList ?? {}).filter((card) => card.accountID === accountID && workspaceAccountID.toString() === card.fundID);
+ }, [allCardsList, accountID, workspaceAccountID]);
const confirmModalPrompt = useMemo(() => {
const isApprover = Member.isApprover(policy, accountID);
@@ -150,15 +151,12 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM
}, [accountID]);
const navigateToDetails = useCallback(
- (cardID: string) => {
- Navigation.navigate(ROUTES.WORKSPACE_EXPENSIFY_CARD_DETAILS.getRoute(policyID, cardID, Navigation.getActiveRoute()));
- },
- [policyID],
- );
-
- const navigateToCompanyCardDetails = useCallback(
- (cardID: string, bank: string) => {
- Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARD_DETAILS.getRoute(policyID, cardID, bank, Navigation.getActiveRoute()));
+ (card: MemberCard) => {
+ if (card.bank === CONST.EXPENSIFY_CARD.BANK) {
+ Navigation.navigate(ROUTES.WORKSPACE_EXPENSIFY_CARD_DETAILS.getRoute(policyID, card.cardID.toString(), Navigation.getActiveRoute()));
+ return;
+ }
+ Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARD_DETAILS.getRoute(policyID, card.cardID.toString(), card.bank, Navigation.getActiveRoute()));
},
[policyID],
);
@@ -293,7 +291,7 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM
onRoleChange={changeRole}
onClose={() => setIsRoleSelectionModalVisible(false)}
/>
- {(policy?.areExpensifyCardsEnabled ?? policy?.areCompanyCardsEnabled) && (
+ {(!!policy?.areExpensifyCardsEnabled || (!!policy?.areCompanyCardsEnabled && hasMultipleFeeds)) && (
<>
@@ -302,35 +300,23 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM
{memberCards.map((memberCard) => (