diff --git a/android/app/build.gradle b/android/app/build.gradle
index 4f1a95ed2786..1b603d34b9b7 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -107,8 +107,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1009000505
- versionName "9.0.5-5"
+ versionCode 1009000509
+ versionName "9.0.5-9"
// Supported language variants must be declared here to avoid from being removed during the compilation.
// This also helps us to not include unnecessary language variants in the APK.
resConfigs "en", "es"
diff --git a/assets/images/simple-illustrations/simple-illustration__virtualcard.svg b/assets/images/simple-illustrations/simple-illustration__virtualcard.svg
new file mode 100644
index 000000000000..2c1f538102a2
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__virtualcard.svg
@@ -0,0 +1,48 @@
+
+
+
diff --git a/docs/articles/expensify-classic/expensify-card/Cardholder-Settings-and-Features.md b/docs/articles/expensify-classic/expensify-card/Cardholder-Settings-and-Features.md
index ebad41aa2267..38686462a1c2 100644
--- a/docs/articles/expensify-classic/expensify-card/Cardholder-Settings-and-Features.md
+++ b/docs/articles/expensify-classic/expensify-card/Cardholder-Settings-and-Features.md
@@ -5,7 +5,6 @@ description: Expensify Card Settings for Employees
# Using Your Expensify Visa® Commercial Card
-## Getting Started
### Activate Your Card
You can start using your card immediately upon receipt by logging into your Expensify account, heading to your Home tab, and following the prompts on the _**Activate your Expensify Card**_ task.
diff --git a/docs/articles/expensify-classic/expensify-card/Request-the-Card.md b/docs/articles/expensify-classic/expensify-card/Request-the-Card.md
index 724745f458ef..b65c66c986ad 100644
--- a/docs/articles/expensify-classic/expensify-card/Request-the-Card.md
+++ b/docs/articles/expensify-classic/expensify-card/Request-the-Card.md
@@ -2,45 +2,35 @@
title: Request the Card
description: Details on requesting the Expensify Card as an employee
---
-# Overview
-
-Once your organization is approved for the Expensify Visa® Commercial Card, you can request a card!
-
-This article covers how to request, activate, and replace your physical and virtual Expensify Cards.
-
-# How to get your first Expensify Card
-
-An admin in your organization must first enable the Expensify Cards before you can receive a card. After that, an admin may assign you a card by setting a limit. You can think of setting a card limit as “unlocking” access to the card.
-
-If you haven’t been assigned a limit yet, look for the task on your account's homepage that says, “Ask your admin for the card!” This task allows you to message your admin team to make that request.
-
-Once you’re assigned a card limit, we’ll notify you via email to let you know you can request a card. A link within the notification email will take you to your account’s homepage, where you can provide your shipping address for the physical card. Enter your address, and we’ll ship the card to arrive within 3-5 business days.
-
-Once your physical card arrives in the mail, activate it in Expensify by entering the last four digits of the card in the activation task on your account’s homepage.
-
-# Virtual Card
-
-Once assigned a limit, a virtual card is available immediately. You can view the virtual card details via **Settings > Account > Credit Card Import > Show Details**. Feel free to begin transacting with the virtual card while your physical card is in transit – your virtual card and physical card share a limit.
-
-Please note that you must enable two-factor authentication on your account if you want to have the option to dispute transactions made on your virtual card.
-
-# Notifications
-
-To stay up-to-date on your card’s limit and spending activity, download the Expensify mobile app and enable push notifications. Your card is connected to your Expensify account, so each transaction on your card will trigger a push notification. We’ll also send you a push notification if we detect potentially fraudulent activity and allow you to confirm your purchase.
-
-# How to request a replacement Expensify Card
-
-You can request a new card anytime if your Expensify Card is lost, stolen, or damaged. From your Expensify account on the web, head to **Settings > Account > Credit Card Import** and click **Request a New Card**. Confirm the shipping information, complete the prompts, and your new card will arrive in 2 - 3 business days.
-
-Selecting the “lost” or “stolen” options will deactivate your current card to prevent potentially fraudulent activity. However, choosing the “damaged” option will leave your current card active so you can use it while the new one is shipped to you.
-
-If you need to cancel your Expensify Card and cannot access the website or mobile app, call our interactive voice recognition phone service (available 24/7). Call 1-877-751-5848 (US) or +44 808 196 0632 (Internationally).
-
-It's not possible to order a replacement card over the phone, so, if applicable, you would need to handle this step from your Expensify account.
-
-# Card Expiration Date
-
-If you notice that your card expiration date is soon, it's time for a new Expensify card. Expensify will automatically input a notification in your account's Home (Inbox) tab. This notice will ask you to input your address, but this is more if you have changed your address since your card was issued to you. You can ignore it and do nothing; the new Expensify card will ship to your address on file. The new Expensify card will have a new, unique card number and will not be associated with the old one.
+To start using the Expensify Card, do the following:
+1. **Enable Expensify Cards:** An admin must first enable the cards. Then, an admin can assign you a card by setting a limit, which allows access to the card.
+2. **Request the Card:**
+ - If you haven’t been assigned a limit, look for the task on your account’s homepage that says, “Ask your admin for the card!” Use this task to message your admin team.
+ - Once you’re assigned a card limit, you’ll receive an email notification. Click the link in the email to provide your shipping address on your account’s homepage.
+ - Enter your address, and the physical card will be shipped within 3-5 business days.
+3. **Activate the Card:** When your physical card arrives, activate it in Expensify by entering the last four digits of the card in the activation task on your homepage.
+
+### Virtual Cards
+Once you've been assigned a limit, a virtual card is available immediately. You can view its details via _**Settings > Account > Credit Card Import > Show Details**_.
+
+To protect your account and card spend, enable two-factor authentication under _**Settings > Account > Account Details**_.
+
+### Notifications
+- Download the Expensify mobile app and enable push notifications to stay updated on your card’s limit and spending.
+- Each transaction triggers a push notification.
+- You’ll also get notifications for potentially fraudulent activity, allowing you to confirm or dispute charges.
+
+## Request a Replacement Expensify Card
+### If the card is lost, stolen, or damaged Card:
+ - Go to _**Settings > Account > Credit Card Import** and click **Request a New Card**_.
+ - Confirm your shipping information and complete the prompts. The new card will arrive in 2-3 business days.
+ - Selecting “lost” or “stolen” deactivates your current card to prevent fraud. Choosing “damaged” keeps the current card active until the new one arrives.
+ - If you can’t access the website or app, call 1-877-751-5848 (US) or +44 808 196 0632 (Internationally) to cancel your card.
+
+### If the card is expiring
+- If your card is about to expire, Expensify will notify you via your account’s Home (Inbox) tab.
+- Enter your address if it has changed; otherwise, do nothing, and the new card will ship to your address on file.
+- The new card will have a unique number and will not be linked to the old one.
{% include faq-begin.md %}
diff --git a/docs/articles/expensify-classic/expensify-card/Statements.md b/docs/articles/expensify-classic/expensify-card/Statements.md
index 894dfa3d8b9a..eb797f0cee4b 100644
--- a/docs/articles/expensify-classic/expensify-card/Statements.md
+++ b/docs/articles/expensify-classic/expensify-card/Statements.md
@@ -1,73 +1,62 @@
---
title: — Expensify Card Statements and Settlements
-description: Learn how the Expensify Card statement and settlements work!
+description: Understand how to access your Expensify Card Statement
---
-# Overview
-Expensify offers several settlement types and a statement that provides a detailed view of transactions and settlements. We discuss specifics on both below.
+## Expensify Card Statements
+Expensify offers several settlement types and a detailed statement of transactions and settlements.
-# How to use Expensify Visa® Commercial Card Statement and Settlements
-## Using the statement
-If your domain uses the Expensify Card and you have a validated Business Bank Account, access the Expensify Card statement at Settings > Domains > Company Cards > Reconciliation Tab > Settlements.
+### Accessing the Statement
+- If your domain uses the Expensify Card and you have a validated Business Bank Account, access the statement at _**Settings > Domains > Company Cards > Reconciliation Tab > Settlements**_.
+- The statement shows individual transactions (debits) and their corresponding settlements (credits).
-The Expensify Card statement displays individual transactions (debits) and their corresponding settlements (credits). Each Expensify Cardholder has a Digital Card and a Physical Card, which are treated the same in settlement, reconciliation, and exporting to your accounting system.
-
-Here's a breakdown of crucial information in the statement:
-- **Date:** For card payments, it shows the debit date; for card transactions, it displays the purchase date.
-- **Entry ID:** This unique ID groups card payments and transactions together.
-- **Withdrawn Amount:** This applies to card payments, matching the debited amount from the Business Bank Account.
-- **Transaction Amount:** This applies to card transactions, matching the expense purchase amount.
-- **User email:** Applies to card transactions, indicating the cardholder's Expensify email address.
-- **Transaction ID:** A unique ID for locating transactions and assisting Expensify Support in case of issues. Transaction IDs are handy for reconciling pre-authorizations. To find the original purchase, locate the Transaction ID in the Settlements tab of the reconciliation dashboard, download the settlements as a CSV, and search for the Transaction ID within it.
+### Key Information in the Statement
+- **Date:** Debit date for card payments; purchase date for transactions.
+- **Entry ID:** Unique ID grouping card payments and transactions.
+- **Withdrawn Amount:** Amount debited from the Business Bank Account for card payments.
+- **Transaction Amount:** Expense purchase amount for card transactions.
+- **User Email:** Cardholder’s Expensify email address.
+- **Transaction ID:** Unique ID for locating transactions and assisting support.
![Expanded card settlement that shows the various items that make up each card settlement.](https://help.expensify.com/assets/images/ExpensifyHelp_SettlementExpanded.png){:width="100%"}
-The Expensify Card statement only shows payments from existing Business Bank Accounts under Settings > Account > Payments > Business Accounts. If a Business Account is deleted, the statement won't contain data for payments from that account.
-
-## Exporting your statement
-When using the Expensify Card, you can export your statement to a CSV with these steps:
+**Note:** The statement only includes payments from existing Business Bank Accounts under **Settings > Account > Payments > Business Accounts**. Deleted accounts' payments won't appear.
- 1. Login to your account on the web app and click on Settings > Domains > Company Cards.
- 2. Click the Reconciliation tab at the top right, then select Settlements.
- 3. Enter your desired statement dates using the Start and End fields.
- 4. Click Search to access the statement for that period.
- 5. You can view the table or select Download to export it as a CSV.
+## Exporting Statements
+1. Log in to the web app and go to **Settings > Domains > Company Cards**.
+2. Click the **Reconciliation** tab and select **Settlements**.
+3. Enter the start and end dates for your statement.
+4. Click **Search** to view the statement.
+5. Click **Download** to export it as a CSV.
![Click the Download CSV button in the middle of the page to export your card settlements.](https://help.expensify.com/assets/images/ExpensifyHelp_SettlementExport.png){:width="100%"}
## Expensify Card Settlement Frequency
-Paying your Expensify Card balance is simple with automatic settlement. There are two settlement frequency options:
- - **Daily Settlement:** Your Expensify Card balance is paid in full every business day, meaning you’ll see an itemized debit each business day.
- - **Monthly Settlement:** Expensify Cards are settled monthly, with your settlement date determined during the card activation process. With monthly, you’ll see only one itemized debit per month. (Available for Plaid-connected bank accounts with no recent negative balance.)
+- **Daily Settlement:** Balance paid in full every business day with an itemized debit each day.
+- **Monthly Settlement:** Balance settled monthly on a predetermined date with one itemized debit per month (available for Plaid-connected accounts with no recent negative balance).
-## How settlement works
-Each business day (Monday through Friday, excluding US bank holidays) or on your monthly settlement date, we calculate the total of posted Expensify Card transactions since the last settlement. The settlement amount represents what you must pay to bring your Expensify Card balance back to $0.
+## How Settlement Works
+- Each business day or on your monthly settlement date, the total of posted transactions is calculated.
+- The settlement amount is withdrawn from the Verified Business Bank Account linked to the primary domain admin, resetting your card balance to $0.
+- To change your settlement frequency or bank account, go to _**Settings > Domains > [Domain Name] > Company Cards**_, click the **Settings** tab, and select the new options from the dropdown menu. Click **Save** to confirm.
-We'll automatically withdraw this settlement amount from the Verified Business Bank Account linked to the primary domain admin. You can set up this bank account in the web app under Settings > Account > Payments > Bank Accounts.
+![Change your card settlement account or settlement frequency via the dropdown menus in the middle of the screen.](https://help.expensify.com/assets/images/ExpensifyHelp_CardSettings.png){:width="100%"}
-Once the payment is made, your Expensify Card balance will be $0, and the transactions are considered "settled."
-To change your settlement frequency or bank account, go to Settings > Domains > [Domain Name] > Company Cards. On the Company Cards page, click the Settings tab, choose a new settlement frequency or account from the dropdown menu, and click Save to confirm the change.
+# FAQ
-![Change your card settlement account or settlement frequency via the dropdown menus in the middle of the screen.](https://help.expensify.com/assets/images/ExpensifyHelp_CardSettings.png){:width="100%"}
+## Can you pay your balance early if you’ve reached your Domain Limit?
+- For Monthly Settlement, use the “Settle Now” button to manually initiate settlement.
+- For Daily Settlement, balances settle automatically with no additional action required.
-# Expensify Card Statement and Settlements FAQs
-## Can you pay your balance early if you've reached your Domain Limit?
-If you've chosen Monthly Settlement, you can manually initiate settlement using the "Settle Now" button. We'll settle the outstanding balance and then perform settlement again on your selected predetermined monthly settlement date.
-
-If you opt for Daily Settlement, the Expensify Card statement will automatically settle daily through an automatic withdrawal from your business bank account. No additional action is needed on your part.
-
## Will our domain limit change if our Verified Bank Account has a higher balance?
-Your domain limit may fluctuate based on your cash balance, spending patterns, and history with Expensify. Suppose you've recently transferred funds to the business bank account linked to Expensify card settlements. In that case, you should expect a change in your domain limit within 24 hours of the transfer (assuming your business bank account is connected through Plaid).
-
+Domain limits may change based on cash balance, spending patterns, and history with Expensify. If your bank account is connected through Plaid, expect changes within 24 hours of transferring funds.
+
## How is the “Amount Owed” figure on the card list calculated?
-The amount owed consists of all Expensify Card transactions, both pending and posted, since the last settlement date. The settlement amount withdrawn from your designated Verified Business Bank Account only includes posted transactions.
-
-Your amount owed decreases when the settlement clears. Any pending transactions that don't post timely will automatically expire, reducing your amount owed.
-
-## **How do I view all unsettled expenses?**
-To view unsettled expenses since the last settlement, use the Reconciliation Dashboard's Expenses tab. Follow these steps:
- 1. Note the dates of expenses in your last settlement.
- 2. Switch to the Expenses tab on the Reconciliation Dashboard.
- 3. Set the start date just after the last settled expenses and the end date to today.
- 4. The Imported Total will show the outstanding amount, and you can click through to view individual expenses.
+It includes all pending and posted transactions since the last settlement date. The settlement amount withdrawn only includes posted transactions.
+
+## How do I view all unsettled expenses?
+1. Note the dates of expenses in your last settlement.
+2. Go to the **Expenses** tab on the Reconciliation Dashboard.
+3. Set the start date after the last settled expenses and the end date to today.
+4. The **Imported Total** shows the outstanding amount, and you can click to view individual expenses.
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 881a91fcee5d..74865e8954b3 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -40,7 +40,7 @@
CFBundleVersion
- 9.0.5.5
+ 9.0.5.9FullStoryOrgId
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index dd3dbc8264da..a70b0e1e044e 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature????CFBundleVersion
- 9.0.5.5
+ 9.0.5.9
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index c75fa81b19e1..347b9da625fc 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -13,7 +13,7 @@
CFBundleShortVersionString9.0.5CFBundleVersion
- 9.0.5.5
+ 9.0.5.9NSExtensionNSExtensionPointIdentifier
diff --git a/package-lock.json b/package-lock.json
index 2871cf3c1bb0..92b28950d7ca 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "9.0.5-5",
+ "version": "9.0.5-9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "9.0.5-5",
+ "version": "9.0.5-9",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 3ff6021ced4f..4d350ac8b2a9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "9.0.5-5",
+ "version": "9.0.5-9",
"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 423b8898ff63..cb6caa7c66e2 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -2158,6 +2158,10 @@ const CONST = {
CARD_NAME: 'CardName',
CONFIRMATION: 'Confirmation',
},
+ CARD_TYPE: {
+ PHYSICAL: 'physical',
+ VIRTUAL: 'virtual',
+ },
},
AVATAR_ROW_SIZE: {
DEFAULT: 4,
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 5d6b5492d15c..2740b9e336b1 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -556,8 +556,8 @@ const ONYXKEYS = {
NEW_CHAT_NAME_FORM_DRAFT: 'newChatNameFormDraft',
SUBSCRIPTION_SIZE_FORM: 'subscriptionSizeForm',
SUBSCRIPTION_SIZE_FORM_DRAFT: 'subscriptionSizeFormDraft',
- ISSUE_NEW_EXPENSIFY_CARD_FORM: 'issueNewExpensifyCardForm',
- ISSUE_NEW_EXPENSIFY_CARD_FORM_DRAFT: 'issueNewExpensifyCardFormDraft',
+ ISSUE_NEW_EXPENSIFY_CARD_FORM: 'issueNewExpensifyCard',
+ ISSUE_NEW_EXPENSIFY_CARD_FORM_DRAFT: 'issueNewExpensifyCardDraft',
SAGE_INTACCT_CREDENTIALS_FORM: 'sageIntacctCredentialsForm',
SAGE_INTACCT_CREDENTIALS_FORM_DRAFT: 'sageIntacctCredentialsFormDraft',
NETSUITE_TOKEN_INPUT_FORM: 'netsuiteTokenInputForm',
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index c4773d55480d..cf2b006d0e94 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -815,13 +815,10 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/expensify-card',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card` as const,
},
- // TODO: uncomment after development is done
- // WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW: {
- // route: 'settings/workspaces/:policyID/expensify-card/issues-new',
- // getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card/issue-new` as const,
- // },
- // TODO: remove after development is done - this one is for testing purposes
- WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW: 'settings/workspaces/expensify-card/issue-new',
+ WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW: {
+ route: 'settings/workspaces/:policyID/expensify-card/issue-new',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card/issue-new` as const,
+ },
WORKSPACE_DISTANCE_RATES: {
route: 'settings/workspaces/:policyID/distance-rates',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/distance-rates` as const,
diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts
index bd0824372799..5212f5b0edb7 100644
--- a/src/components/Icon/Illustrations.ts
+++ b/src/components/Icon/Illustrations.ts
@@ -92,6 +92,7 @@ import ThumbsUpStars from '@assets/images/simple-illustrations/simple-illustrati
import TrackShoe from '@assets/images/simple-illustrations/simple-illustration__track-shoe.svg';
import TrashCan from '@assets/images/simple-illustrations/simple-illustration__trashcan.svg';
import TreasureChest from '@assets/images/simple-illustrations/simple-illustration__treasurechest.svg';
+import VirtualCard from '@assets/images/simple-illustrations/simple-illustration__virtualcard.svg';
import WalletAlt from '@assets/images/simple-illustrations/simple-illustration__wallet-alt.svg';
import Workflows from '@assets/images/simple-illustrations/simple-illustration__workflows.svg';
import ExpensifyApprovedLogoLight from '@assets/images/subscription-details__approvedlogo--light.svg';
@@ -196,4 +197,5 @@ export {
CheckmarkCircle,
CreditCardEyes,
LockClosedOrange,
+ VirtualCard,
};
diff --git a/src/components/SelectionScreen.tsx b/src/components/SelectionScreen.tsx
index af1cdfd171ea..8e1b0a88c875 100644
--- a/src/components/SelectionScreen.tsx
+++ b/src/components/SelectionScreen.tsx
@@ -1,12 +1,18 @@
import {isEmpty} from 'lodash';
import React from 'react';
+import type {StyleProp, ViewStyle} from 'react-native';
import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
import * as PolicyUtils from '@libs/PolicyUtils';
import type {AccessVariant} from '@pages/workspace/AccessOrNotFoundWrapper';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import type {TranslationPaths} from '@src/languages/types';
+import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
import type {ConnectionName, PolicyFeatureName} from '@src/types/onyx/Policy';
+import type {ReceiptErrors} from '@src/types/onyx/Transaction';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
import HeaderWithBackButton from './HeaderWithBackButton';
+import OfflineWithFeedback from './OfflineWithFeedback';
import ScreenWrapper from './ScreenWrapper';
import SelectionList from './SelectionList';
import type RadioListItem from './SelectionList/RadioListItem';
@@ -63,6 +69,18 @@ type SelectionScreenProps = {
/** Name of the current connection */
connectionName: ConnectionName;
+
+ /** The type of action that's pending */
+ pendingAction?: OnyxCommon.PendingAction | null;
+
+ /** The errors to display */
+ errors?: OnyxCommon.Errors | ReceiptErrors | null;
+
+ /** Additional style object for the error row */
+ errorRowStyles?: StyleProp;
+
+ /** A function to run when the X button next to the error is clicked */
+ onClose?: () => void;
};
function SelectionScreen({
@@ -81,8 +99,13 @@ function SelectionScreen({
featureName,
shouldBeBlocked,
connectionName,
+ pendingAction,
+ errors,
+ errorRowStyles,
+ onClose,
}: SelectionScreenProps) {
const {translate} = useLocalize();
+ const styles = useThemeStyles();
const policy = PolicyUtils.getPolicy(policyID);
const isConnectionEmpty = isEmpty(policy?.connections?.[connectionName]);
@@ -95,24 +118,33 @@ function SelectionScreen({
shouldBeBlocked={isConnectionEmpty || shouldBeBlocked}
>
-
+ {headerContent}
+
+
+
);
diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts
index be7ce9aca8b5..862b0ae5e928 100644
--- a/src/libs/CurrencyUtils.ts
+++ b/src/libs/CurrencyUtils.ts
@@ -97,11 +97,11 @@ function convertToFrontendAmountAsInteger(amountAsInt: number, currency: string
*
* @note we do not support any currencies with more than two decimal places.
*/
-function convertToFrontendAmountAsString(amountAsInt: number | null | undefined, currency: string = CONST.CURRENCY.USD): string {
+function convertToFrontendAmountAsString(amountAsInt: number | null | undefined, currency: string = CONST.CURRENCY.USD, withDecimals = true): string {
if (amountAsInt === null || amountAsInt === undefined) {
return '';
}
- const decimals = getCurrencyDecimals(currency);
+ const decimals = withDecimals ? getCurrencyDecimals(currency) : 0;
return convertToFrontendAmountAsInteger(amountAsInt, currency).toFixed(decimals);
}
diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
index 1f0a45366637..a119d6c2bc64 100755
--- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
+++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
@@ -136,7 +136,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = {
SCREENS.WORKSPACE.REPORT_FIELDS_VALUE_SETTINGS,
SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_VALUE,
],
- [SCREENS.WORKSPACE.EXPENSIFY_CARD]: [],
+ [SCREENS.WORKSPACE.EXPENSIFY_CARD]: [SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW],
};
export default FULL_SCREEN_TO_RHP_MAPPING;
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index ea0ae37fcede..9399470989c2 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -447,7 +447,7 @@ const config: LinkingOptions['config'] = {
path: ROUTES.WORKSPACE_PROFILE_SHARE.route,
},
[SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW]: {
- path: ROUTES.WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW,
+ path: ROUTES.WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW.route,
},
[SCREENS.WORKSPACE.RATE_AND_UNIT]: {
path: ROUTES.WORKSPACE_RATE_AND_UNIT.route,
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 055239b776b3..84f8edf174d6 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -564,6 +564,9 @@ type SettingsNavigatorParamList = {
policyID: string;
taxID: string;
};
+ [SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW]: {
+ policyID: string;
+ };
} & ReimbursementAccountNavigatorParamList;
type NewChatNavigatorParamList = {
@@ -1015,7 +1018,6 @@ type FullScreenNavigatorParamList = {
[SCREENS.WORKSPACE.DISTANCE_RATES]: {
policyID: string;
};
-
[SCREENS.WORKSPACE.ACCOUNTING.ROOT]: {
policyID: string;
};
@@ -1028,6 +1030,9 @@ type FullScreenNavigatorParamList = {
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_INVOICE_ACCOUNT_SELECTOR]: {
policyID: string;
};
+ [SCREENS.WORKSPACE.EXPENSIFY_CARD]: {
+ policyID: string;
+ };
};
type OnboardingModalNavigatorParamList = {
diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts
index f20d27ffdf22..330d9d6ef61d 100644
--- a/src/libs/OptionsListUtils.ts
+++ b/src/libs/OptionsListUtils.ts
@@ -223,6 +223,10 @@ type FilterOptionsConfig = Pick<
'sortByReportTypeInSearch' | 'canInviteUser' | 'betas' | 'selectedOptions' | 'excludeUnknownUsers' | 'excludeLogins' | 'maxRecentReportsToShow'
> & {preferChatroomsOverThreads?: boolean};
+type HasText = {
+ text?: string;
+};
+
/**
* OptionsListUtils is used to build a list options passed to the OptionsList component. Several different UI views can
* be configured to display different results based on the options passed to the private getOptions() method. Public
@@ -2559,6 +2563,10 @@ function filterOptions(options: Options, searchInputValue: string, config?: Filt
};
}
+function sortItemsAlphabetically(membersList: T[]): T[] {
+ return membersList.sort((a, b) => (a.text ?? '').toLowerCase().localeCompare((b.text ?? '').toLowerCase()));
+}
+
export {
getAvatarsForAccountIDs,
isCurrentUser,
@@ -2584,6 +2592,7 @@ export {
getEnabledCategoriesCount,
hasEnabledOptions,
sortCategories,
+ sortItemsAlphabetically,
sortTags,
getCategoryOptionTree,
hasEnabledTags,
diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index a70c254396e6..fdc121bfdb6c 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -241,6 +241,7 @@ function getTagListName(policyTagList: OnyxEntry, orderWeight: nu
return Object.values(policyTagList).find((tag) => tag.orderWeight === orderWeight)?.name ?? '';
}
+
/**
* Gets all tag lists of a policy
*/
@@ -687,6 +688,13 @@ function getCurrentConnectionName(policy: Policy | undefined): string | undefine
return connectionKey ? CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionKey] : undefined;
}
+/**
+ * Check if the policy member is deleted from the workspace
+ */
+function isDeletedPolicyEmployee(policyEmployee: PolicyEmployee, isOffline: boolean) {
+ return !isOffline && policyEmployee.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && isEmptyObject(policyEmployee.errors);
+}
+
export {
canEditTaxRate,
extractPolicyIDFromPath,
@@ -720,6 +728,7 @@ export {
hasPolicyErrorFields,
hasTaxRateError,
isExpensifyTeam,
+ isDeletedPolicyEmployee,
isFreeGroupPolicy,
isInstantSubmitEnabled,
isPaidGroupPolicy,
diff --git a/src/libs/actions/Card.ts b/src/libs/actions/Card.ts
index aea952618071..b23493c08e8e 100644
--- a/src/libs/actions/Card.ts
+++ b/src/libs/actions/Card.ts
@@ -5,10 +5,21 @@ import type {ActivatePhysicalExpensifyCardParams, ReportVirtualExpensifyCardFrau
import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {ExpensifyCardDetails, IssueNewCardStep} from '@src/types/onyx/Card';
+import type {ExpensifyCardDetails, IssueNewCardData, IssueNewCardStep} from '@src/types/onyx/Card';
type ReplacementReason = 'damaged' | 'stolen';
+type IssueNewCardFlowData = {
+ /** Step to be set in Onyx */
+ step?: IssueNewCardStep;
+
+ /** Whether the user is editing step */
+ isEditing?: boolean;
+
+ /** Data required to be sent to issue a new card */
+ data?: Partial;
+};
+
function reportVirtualExpensifyCardFraud(cardID: number) {
const optimisticData: OnyxUpdate[] = [
{
@@ -185,9 +196,24 @@ function revealVirtualCardDetails(cardID: number): Promise
});
}
-function setIssueNewCardStep(step: IssueNewCardStep | null) {
- Onyx.merge(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD, {currentStep: step});
+function setIssueNewCardStepAndData({data, isEditing, step}: IssueNewCardFlowData) {
+ Onyx.merge(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD, {data, isEditing, currentStep: step});
+}
+
+function clearIssueNewCardFlow() {
+ Onyx.set(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD, {
+ currentStep: null,
+ data: {},
+ });
}
-export {requestReplacementExpensifyCard, activatePhysicalExpensifyCard, clearCardListErrors, reportVirtualExpensifyCardFraud, revealVirtualCardDetails, setIssueNewCardStep};
+export {
+ requestReplacementExpensifyCard,
+ activatePhysicalExpensifyCard,
+ clearCardListErrors,
+ reportVirtualExpensifyCardFraud,
+ revealVirtualCardDetails,
+ setIssueNewCardStepAndData,
+ clearIssueNewCardFlow,
+};
export type {ReplacementReason};
diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts
index 33d906652af6..2fe026aed03c 100644
--- a/src/libs/actions/Policy/Policy.ts
+++ b/src/libs/actions/Policy/Policy.ts
@@ -526,6 +526,10 @@ function clearXeroErrorField(policyID: string, fieldName: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {connections: {xero: {config: {errorFields: {[fieldName]: null}}}}});
}
+function clearSageIntacctErrorField(policyID: string, fieldName: string) {
+ Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {connections: {intacct: {config: {errorFields: {[fieldName]: null}}}}});
+}
+
function clearNetSuiteErrorField(policyID: string, fieldName: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {connections: {netsuite: {options: {config: {errorFields: {[fieldName]: null}}}}}});
}
@@ -3123,6 +3127,7 @@ export {
generateCustomUnitID,
clearQBOErrorField,
clearXeroErrorField,
+ clearSageIntacctErrorField,
clearNetSuiteErrorField,
clearNetSuiteAutoSyncErrorField,
clearWorkspaceReimbursementErrors,
diff --git a/src/libs/actions/connections/SageIntacct.ts b/src/libs/actions/connections/SageIntacct.ts
index 3dcb9726d0b9..861ff4fa362e 100644
--- a/src/libs/actions/connections/SageIntacct.ts
+++ b/src/libs/actions/connections/SageIntacct.ts
@@ -7,7 +7,7 @@ import {WRITE_COMMANDS} from '@libs/API/types';
import * as ErrorUtils from '@libs/ErrorUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {Connections} from '@src/types/onyx/Policy';
+import type {SageIntacctExportConfig} from '@src/types/onyx/Policy';
type SageIntacctCredentials = {companyID: string; userID: string; password: string};
@@ -21,7 +21,7 @@ function connectToSageIntacct(policyID: string, credentials: SageIntacctCredenti
API.write(WRITE_COMMANDS.CONNECT_POLICY_TO_SAGE_INTACCT, parameters, {});
}
-function prepareOnyxDataForExportUpdate(policyID: string, settingName: keyof Connections['intacct']['config']['export'], settingValue: string | null) {
+function prepareOnyxDataForExportUpdate(policyID: string, settingName: keyof SageIntacctExportConfig, settingValue: string | null) {
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
@@ -32,12 +32,12 @@ function prepareOnyxDataForExportUpdate(policyID: string, settingName: keyof Con
config: {
export: {
[settingName]: settingValue,
- pendingFields: {
- [settingName]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
- },
- errorFields: {
- [settingName]: null,
- },
+ },
+ pendingFields: {
+ [settingName]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ errorFields: {
+ [settingName]: null,
},
},
},
@@ -54,14 +54,11 @@ function prepareOnyxDataForExportUpdate(policyID: string, settingName: keyof Con
connections: {
intacct: {
config: {
- export: {
- [settingName]: settingValue,
- pendingFields: {
- [settingName]: null,
- },
- errorFields: {
- [settingName]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'),
- },
+ pendingFields: {
+ [settingName]: null,
+ },
+ errorFields: {
+ [settingName]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'),
},
},
},
@@ -78,14 +75,11 @@ function prepareOnyxDataForExportUpdate(policyID: string, settingName: keyof Con
connections: {
intacct: {
config: {
- export: {
- [settingName]: settingValue,
- pendingFields: {
- [settingName]: null,
- },
- errorFields: {
- [settingName]: null,
- },
+ pendingFields: {
+ [settingName]: null,
+ },
+ errorFields: {
+ [settingName]: null,
},
},
},
@@ -177,7 +171,7 @@ function updateSageIntacctNonreimbursableExpensesExportVendor(policyID: string,
API.write(WRITE_COMMANDS.UPDATE_SAGE_INTACCT_NON_REIMBURSABLE_EXPENSES_EXPORT_VENDOR, parameters, {optimisticData, failureData, successData});
}
-function updateSageIntacctDefaultVendor(policyID: string, settingName: keyof Connections['intacct']['config']['export'], vendor: string) {
+function updateSageIntacctDefaultVendor(policyID: string, settingName: keyof SageIntacctExportConfig, vendor: string) {
if (settingName === CONST.SAGE_INTACCT_CONFIG.REIMBURSABLE_VENDOR) {
updateSageIntacctReimbursableExpensesReportExportDefaultVendor(policyID, vendor);
} else if (settingName === CONST.SAGE_INTACCT_CONFIG.NON_REIMBURSABLE_CREDIT_CARD_VENDOR) {
diff --git a/src/pages/EditReportFieldPage.tsx b/src/pages/EditReportFieldPage.tsx
index 69f959d6545f..d489e58493d3 100644
--- a/src/pages/EditReportFieldPage.tsx
+++ b/src/pages/EditReportFieldPage.tsx
@@ -77,11 +77,11 @@ function EditReportFieldPage({route, policy, report}: EditReportFieldPageProps)
const value = form[fieldKey];
if (isReportFieldTitle) {
ReportActions.updateReportName(report.reportID, value, report.reportName ?? '');
+ Navigation.goBack();
} else {
ReportActions.updateReportField(report.reportID, {...reportField, value: value === '' ? null : value}, reportField);
+ Navigation.dismissModal(report?.reportID);
}
-
- Navigation.dismissModal(report?.reportID);
};
const handleReportFieldDelete = () => {
diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx
index 530df5fa0532..c9440ee548af 100644
--- a/src/pages/ReportDetailsPage.tsx
+++ b/src/pages/ReportDetailsPage.tsx
@@ -200,9 +200,9 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
const shouldShowDeleteButton = shouldShowTaskDeleteButton || canDeleteRequest;
const canUnapproveRequest =
- ReportUtils.isMoneyRequestReport(moneyRequestReport) &&
- (ReportUtils.isReportManager(moneyRequestReport) || isPolicyAdmin) &&
- (ReportUtils.isReportApproved(moneyRequestReport) || ReportUtils.isReportManuallyReimbursed(moneyRequestReport));
+ ReportUtils.isExpenseReport(report) &&
+ (ReportUtils.isReportManager(report) || isPolicyAdmin) &&
+ (ReportUtils.isReportApproved(report) || ReportUtils.isReportManuallyReimbursed(report));
useEffect(() => {
if (canDeleteRequest) {
diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx
index 671cc6d06706..5fef11fa4b15 100644
--- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx
+++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx
@@ -551,7 +551,9 @@ function IOURequestStepConfirmation({
!isOffline && policyEmployee.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && isEmptyObject(policyEmployee.errors),
- [isOffline],
- );
-
const policyOwner = policy?.owner;
const currentUserLogin = currentUserPersonalDetails.login;
const invitedPrimaryToSecondaryLogins = invertObject(policy?.primaryLoginsInvited ?? {});
@@ -320,7 +312,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft,
Object.entries(policy?.employeeList ?? {}).forEach(([email, policyEmployee]) => {
const accountID = Number(policyMemberEmailsToAccountIDs[email] ?? '');
- if (isDeletedPolicyEmployee(policyEmployee)) {
+ if (PolicyUtils.isDeletedPolicyEmployee(policyEmployee, isOffline)) {
return;
}
@@ -375,13 +367,13 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft,
invitedSecondaryLogin: details?.login ? invitedPrimaryToSecondaryLogins[details.login] ?? '' : '',
});
});
- result = result.sort((a, b) => (a.text ?? '').toLowerCase().localeCompare((b.text ?? '').toLowerCase()));
+ result = OptionsListUtils.sortItemsAlphabetically(result);
return result;
}, [
+ isOffline,
currentUserLogin,
formatPhoneNumber,
invitedPrimaryToSecondaryLogins,
- isDeletedPolicyEmployee,
isPolicyAdmin,
personalDetails,
policy?.owner,
diff --git a/src/pages/workspace/accounting/intacct/export/SageIntacctDatePage.tsx b/src/pages/workspace/accounting/intacct/export/SageIntacctDatePage.tsx
index 51d08423e530..d337d17f7e1a 100644
--- a/src/pages/workspace/accounting/intacct/export/SageIntacctDatePage.tsx
+++ b/src/pages/workspace/accounting/intacct/export/SageIntacctDatePage.tsx
@@ -8,10 +8,12 @@ import type {SelectorType} from '@components/SelectionScreen';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
+import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@navigation/Navigation';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import {updateSageIntacctExportDate} from '@userActions/connections/SageIntacct';
+import * as Policy from '@userActions/Policy/Policy';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
@@ -23,6 +25,7 @@ function SageIntacctDatePage({policy}: WithPolicyProps) {
const {translate} = useLocalize();
const policyID = policy?.id ?? '-1';
const styles = useThemeStyles();
+ const {config} = policy?.connections?.intacct ?? {};
const {export: exportConfig} = policy?.connections?.intacct?.config ?? {};
const data: MenuListItem[] = Object.values(CONST.SAGE_INTACCT_EXPORT_DATE).map((dateType) => ({
value: dateType,
@@ -65,6 +68,10 @@ function SageIntacctDatePage({policy}: WithPolicyProps) {
featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED}
onBackButtonPress={() => Navigation.goBack(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_EXPORT.getRoute(policyID))}
connectionName={CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT}
+ pendingAction={config?.pendingFields?.exportDate}
+ errors={ErrorUtils.getLatestErrorField(config ?? {}, CONST.SAGE_INTACCT_CONFIG.EXPORT_DATE)}
+ errorRowStyles={[styles.ph5, styles.mv2]}
+ onClose={() => Policy.clearSageIntacctErrorField(policyID, CONST.SAGE_INTACCT_CONFIG.EXPORT_DATE)}
/>
);
}
diff --git a/src/pages/workspace/accounting/intacct/export/SageIntacctDefaultVendorPage.tsx b/src/pages/workspace/accounting/intacct/export/SageIntacctDefaultVendorPage.tsx
index 4782cfba9e97..dd9b9a3167e1 100644
--- a/src/pages/workspace/accounting/intacct/export/SageIntacctDefaultVendorPage.tsx
+++ b/src/pages/workspace/accounting/intacct/export/SageIntacctDefaultVendorPage.tsx
@@ -10,11 +10,13 @@ import SelectionScreen from '@components/SelectionScreen';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
+import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import {getSageIntacctNonReimbursableActiveDefaultVendor, getSageIntacctVendors} from '@libs/PolicyUtils';
import type {SettingsNavigatorParamList} from '@navigation/types';
import variables from '@styles/variables';
import {updateSageIntacctDefaultVendor} from '@userActions/connections/SageIntacct';
+import * as Policy from '@userActions/Policy/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -29,20 +31,22 @@ function SageIntacctDefaultVendorPage({route}: SageIntacctDefaultVendorPageProps
const policyID = route.params.policyID ?? '-1';
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);
+ const {config} = policy?.connections?.intacct ?? {};
+ const {export: exportConfig} = policy?.connections?.intacct?.config ?? {};
const isReimbursable = route.params.reimbursable === CONST.SAGE_INTACCT_CONFIG.REIMBURSABLE;
let defaultVendor;
let settingName: keyof Connections['intacct']['config']['export'];
if (!isReimbursable) {
- const {nonReimbursable} = policy?.connections?.intacct?.config.export ?? {};
+ const {nonReimbursable} = exportConfig ?? {};
defaultVendor = getSageIntacctNonReimbursableActiveDefaultVendor(policy);
settingName =
nonReimbursable === CONST.SAGE_INTACCT_NON_REIMBURSABLE_EXPENSE_TYPE.CREDIT_CARD_CHARGE
? CONST.SAGE_INTACCT_CONFIG.NON_REIMBURSABLE_CREDIT_CARD_VENDOR
: CONST.SAGE_INTACCT_CONFIG.NON_REIMBURSABLE_VENDOR;
} else {
- const {reimbursableExpenseReportDefaultVendor} = policy?.connections?.intacct?.config.export ?? {};
+ const {reimbursableExpenseReportDefaultVendor} = exportConfig ?? {};
defaultVendor = reimbursableExpenseReportDefaultVendor;
settingName = CONST.SAGE_INTACCT_CONFIG.REIMBURSABLE_VENDOR;
}
@@ -103,6 +107,10 @@ function SageIntacctDefaultVendorPage({route}: SageIntacctDefaultVendorPageProps
listEmptyContent={listEmptyContent}
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.PAID]}
connectionName={CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT}
+ pendingAction={config?.pendingFields?.[settingName]}
+ errors={ErrorUtils.getLatestErrorField(config, settingName)}
+ errorRowStyles={[styles.ph5, styles.mv2]}
+ onClose={() => Policy.clearSageIntacctErrorField(policyID, settingName)}
/>
);
}
diff --git a/src/pages/workspace/accounting/intacct/export/SageIntacctExportPage.tsx b/src/pages/workspace/accounting/intacct/export/SageIntacctExportPage.tsx
index b6aceb56b6ce..5f024f5d68e1 100644
--- a/src/pages/workspace/accounting/intacct/export/SageIntacctExportPage.tsx
+++ b/src/pages/workspace/accounting/intacct/export/SageIntacctExportPage.tsx
@@ -15,6 +15,7 @@ function SageIntacctExportPage({policy}: WithPolicyProps) {
const styles = useThemeStyles();
const policyID = policy?.id ?? '-1';
+ const {config} = policy?.connections?.intacct ?? {};
const {export: exportConfig, credentials} = policy?.connections?.intacct?.config ?? {};
const sections = useMemo(
@@ -23,15 +24,15 @@ function SageIntacctExportPage({policy}: WithPolicyProps) {
description: translate('workspace.sageIntacct.preferredExporter'),
action: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_PREFERRED_EXPORTER.getRoute(policyID)),
title: exportConfig?.exporter ?? translate('workspace.sageIntacct.notConfigured'),
- hasError: !!exportConfig?.errorFields?.exporter,
- pendingAction: exportConfig?.pendingFields?.exporter,
+ hasError: !!config?.errorFields?.exporter,
+ pendingAction: config?.pendingFields?.exporter,
},
{
description: translate('workspace.sageIntacct.exportDate.label'),
action: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_EXPORT_DATE.getRoute(policyID)),
title: exportConfig?.exportDate ? translate(`workspace.sageIntacct.exportDate.values.${exportConfig.exportDate}.label`) : translate(`workspace.sageIntacct.notConfigured`),
- hasError: !!exportConfig?.errorFields?.exportDate,
- pendingAction: exportConfig?.pendingFields?.exportDate,
+ hasError: !!config?.errorFields?.exportDate,
+ pendingAction: config?.pendingFields?.exportDate,
},
{
description: translate('workspace.sageIntacct.reimbursableExpenses.label'),
@@ -39,8 +40,8 @@ function SageIntacctExportPage({policy}: WithPolicyProps) {
title: exportConfig?.reimbursable
? translate(`workspace.sageIntacct.reimbursableExpenses.values.${exportConfig.reimbursable}`)
: translate('workspace.sageIntacct.notConfigured'),
- hasError: !!exportConfig?.errorFields?.reimbursable,
- pendingAction: exportConfig?.pendingFields?.reimbursable,
+ hasError: !!config?.errorFields?.reimbursable || !!config?.errorFields?.reimbursableExpenseReportDefaultVendor,
+ pendingAction: config?.pendingFields?.reimbursable ?? config?.pendingFields?.reimbursableExpenseReportDefaultVendor,
},
{
description: translate('workspace.sageIntacct.nonReimbursableExpenses.label'),
@@ -48,11 +49,21 @@ function SageIntacctExportPage({policy}: WithPolicyProps) {
title: exportConfig?.nonReimbursable
? translate(`workspace.sageIntacct.nonReimbursableExpenses.values.${exportConfig.nonReimbursable}`)
: translate('workspace.sageIntacct.notConfigured'),
- hasError: !!exportConfig?.errorFields?.nonReimbursable,
- pendingAction: exportConfig?.pendingFields?.nonReimbursable,
+ hasError:
+ !!config?.errorFields?.nonReimbursable ??
+ !!config?.errorFields?.nonReimbursableAccount ??
+ config?.export.nonReimbursable === CONST.SAGE_INTACCT_NON_REIMBURSABLE_EXPENSE_TYPE.VENDOR_BILL
+ ? !!config?.errorFields?.nonReimbursableVendor
+ : !!config?.errorFields?.nonReimbursableCreditCardChargeDefaultVendor,
+ pendingAction:
+ config?.pendingFields?.nonReimbursable ??
+ config?.errorFields?.nonReimbursableAccount ??
+ config?.export.nonReimbursable === CONST.SAGE_INTACCT_NON_REIMBURSABLE_EXPENSE_TYPE.VENDOR_BILL
+ ? config?.pendingFields?.nonReimbursableVendor
+ : config?.pendingFields?.nonReimbursableCreditCardChargeDefaultVendor,
},
],
- [exportConfig, policyID, translate],
+ [config, exportConfig, policyID, translate],
);
return (
diff --git a/src/pages/workspace/accounting/intacct/export/SageIntacctNonReimbursableCreditCardAccountPage.tsx b/src/pages/workspace/accounting/intacct/export/SageIntacctNonReimbursableCreditCardAccountPage.tsx
index f22b653b26f9..8628910aa3ce 100644
--- a/src/pages/workspace/accounting/intacct/export/SageIntacctNonReimbursableCreditCardAccountPage.tsx
+++ b/src/pages/workspace/accounting/intacct/export/SageIntacctNonReimbursableCreditCardAccountPage.tsx
@@ -6,12 +6,14 @@ import type {SelectorType} from '@components/SelectionScreen';
import SelectionScreen from '@components/SelectionScreen';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
+import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import {getSageIntacctCreditCards} from '@libs/PolicyUtils';
import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import variables from '@styles/variables';
import {updateSageIntacctNonreimbursableExpensesExportAccount} from '@userActions/connections/SageIntacct';
+import * as Policy from '@userActions/Policy/Policy';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
@@ -20,7 +22,7 @@ function SageIntacctNonReimbursableCreditCardAccountPage({policy}: WithPolicyCon
const {translate} = useLocalize();
const policyID = policy?.id ?? '-1';
-
+ const {config} = policy?.connections?.intacct ?? {};
const {export: exportConfig} = policy?.connections?.intacct?.config ?? {};
const creditCardSelectorOptions = useMemo(() => getSageIntacctCreditCards(policy, exportConfig?.nonReimbursableAccount), [exportConfig?.nonReimbursableAccount, policy]);
@@ -63,6 +65,10 @@ function SageIntacctNonReimbursableCreditCardAccountPage({policy}: WithPolicyCon
listEmptyContent={listEmptyContent}
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.PAID]}
connectionName={CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT}
+ pendingAction={config?.pendingFields?.nonReimbursableAccount}
+ errors={ErrorUtils.getLatestErrorField(config ?? {}, CONST.SAGE_INTACCT_CONFIG.NON_REIMBURSABLE_ACCOUNT)}
+ errorRowStyles={[styles.ph5, styles.mv2]}
+ onClose={() => Policy.clearSageIntacctErrorField(policyID, CONST.SAGE_INTACCT_CONFIG.NON_REIMBURSABLE_ACCOUNT)}
/>
);
}
diff --git a/src/pages/workspace/accounting/intacct/export/SageIntacctNonReimbursableExpensesPage.tsx b/src/pages/workspace/accounting/intacct/export/SageIntacctNonReimbursableExpensesPage.tsx
index ba977cbba238..086c68621b2c 100644
--- a/src/pages/workspace/accounting/intacct/export/SageIntacctNonReimbursableExpensesPage.tsx
+++ b/src/pages/workspace/accounting/intacct/export/SageIntacctNonReimbursableExpensesPage.tsx
@@ -10,12 +10,14 @@ import type {ListItem} from '@components/SelectionList/types';
import type {SelectorType} from '@components/SelectionScreen';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
+import * as ErrorUtils from '@libs/ErrorUtils';
import {getSageIntacctNonReimbursableActiveDefaultVendor} from '@libs/PolicyUtils';
import Navigation from '@navigation/Navigation';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow';
import {updateSageIntacctDefaultVendor, updateSageIntacctNonreimbursableExpensesExportDestination} from '@userActions/connections/SageIntacct';
+import * as Policy from '@userActions/Policy/Policy';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {SageIntacctDataElementWithValue} from '@src/types/onyx/Policy';
@@ -61,12 +63,12 @@ function SageIntacctNonReimbursableExpensesPage({policy}: WithPolicyProps) {
title: defaultVendorName && defaultVendorName !== '' ? defaultVendorName : translate('workspace.sageIntacct.notConfigured'),
hasError:
config?.export.nonReimbursable === CONST.SAGE_INTACCT_NON_REIMBURSABLE_EXPENSE_TYPE.VENDOR_BILL
- ? !!config?.export?.errorFields?.nonReimbursableVendor
- : !!config?.export?.errorFields?.nonReimbursableCreditCardChargeDefaultVendor,
+ ? !!config?.errorFields?.nonReimbursableVendor
+ : !!config?.errorFields?.nonReimbursableCreditCardChargeDefaultVendor,
pendingAction:
config?.export.nonReimbursable === CONST.SAGE_INTACCT_NON_REIMBURSABLE_EXPENSE_TYPE.VENDOR_BILL
- ? config?.export?.pendingFields?.nonReimbursableVendor
- : config?.export?.pendingFields?.nonReimbursableCreditCardChargeDefaultVendor,
+ ? config?.pendingFields?.nonReimbursableVendor
+ : config?.pendingFields?.nonReimbursableCreditCardChargeDefaultVendor,
};
return (
@@ -84,11 +86,11 @@ function SageIntacctNonReimbursableExpensesPage({policy}: WithPolicyProps) {
);
}, [
- config?.export?.errorFields?.nonReimbursableCreditCardChargeDefaultVendor,
- config?.export?.errorFields?.nonReimbursableVendor,
+ config?.errorFields?.nonReimbursableCreditCardChargeDefaultVendor,
+ config?.errorFields?.nonReimbursableVendor,
config?.export.nonReimbursable,
- config?.export?.pendingFields?.nonReimbursableCreditCardChargeDefaultVendor,
- config?.export?.pendingFields?.nonReimbursableVendor,
+ config?.pendingFields?.nonReimbursableCreditCardChargeDefaultVendor,
+ config?.pendingFields?.nonReimbursableVendor,
intacctData?.vendors,
policy,
policyID,
@@ -100,8 +102,8 @@ function SageIntacctNonReimbursableExpensesPage({policy}: WithPolicyProps) {
description: translate('workspace.sageIntacct.creditCardAccount'),
action: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_NON_REIMBURSABLE_CREDIT_CARD_ACCOUNT.getRoute(policyID)),
title: config?.export.nonReimbursableAccount ? config.export.nonReimbursableAccount : translate('workspace.sageIntacct.notConfigured'),
- hasError: !!config?.export?.errorFields?.nonReimbursableAccount,
- pendingAction: config?.export?.pendingFields?.nonReimbursableAccount,
+ hasError: !!config?.errorFields?.nonReimbursableAccount,
+ pendingAction: config?.pendingFields?.nonReimbursableAccount,
};
return (
@@ -118,7 +120,7 @@ function SageIntacctNonReimbursableExpensesPage({policy}: WithPolicyProps) {
/>
);
- }, [config?.export?.errorFields?.nonReimbursableAccount, config?.export.nonReimbursableAccount, config?.export?.pendingFields?.nonReimbursableAccount, policyID, translate]);
+ }, [config?.errorFields?.nonReimbursableAccount, config?.export.nonReimbursableAccount, config?.pendingFields?.nonReimbursableAccount, policyID, translate]);
return (
- selectNonReimbursableExpense(selection as MenuListItem)}
- sections={[{data}]}
- ListItem={RadioListItem}
- showScrollIndicator
- shouldShowTooltips={false}
- listFooterContent={
+ Policy.clearSageIntacctErrorField(policyID, CONST.SAGE_INTACCT_CONFIG.NON_REIMBURSABLE)}
+ style={[styles.flexGrow1, styles.flexShrink1]}
+ contentContainerStyle={[styles.flexGrow1, styles.flexShrink1]}
+ >
+ selectNonReimbursableExpense(selection as MenuListItem)}
+ sections={[{data}]}
+ ListItem={RadioListItem}
+ showScrollIndicator
+ shouldShowTooltips={false}
+ containerStyle={[styles.flexReset, styles.flexGrow1, styles.flexShrink1]}
+ />
+
+
+ {config?.export.nonReimbursable === CONST.SAGE_INTACCT_NON_REIMBURSABLE_EXPENSE_TYPE.VENDOR_BILL && defaultVendor}
+ {config?.export.nonReimbursable === CONST.SAGE_INTACCT_NON_REIMBURSABLE_EXPENSE_TYPE.CREDIT_CARD_CHARGE && (
- {config?.export.nonReimbursable === CONST.SAGE_INTACCT_NON_REIMBURSABLE_EXPENSE_TYPE.VENDOR_BILL && defaultVendor}
- {config?.export.nonReimbursable === CONST.SAGE_INTACCT_NON_REIMBURSABLE_EXPENSE_TYPE.CREDIT_CARD_CHARGE && (
-
- {creditCardAccount}
- {
- const vendor = enabled ? policy?.connections?.intacct?.data?.vendors?.[0].id ?? '' : '';
- updateSageIntacctDefaultVendor(policyID, CONST.SAGE_INTACCT_CONFIG.NON_REIMBURSABLE_CREDIT_CARD_VENDOR, vendor);
- }}
- wrapperStyle={[styles.ph5, styles.pv3]}
- pendingAction={config?.export?.pendingFields?.nonReimbursableCreditCardChargeDefaultVendor}
- />
- {!!config?.export.nonReimbursableCreditCardChargeDefaultVendor && defaultVendor}
-
- )}
+ {creditCardAccount}
+ {
+ const vendor = enabled ? policy?.connections?.intacct?.data?.vendors?.[0].id ?? '' : '';
+ updateSageIntacctDefaultVendor(policyID, CONST.SAGE_INTACCT_CONFIG.NON_REIMBURSABLE_CREDIT_CARD_VENDOR, vendor);
+ }}
+ wrapperStyle={[styles.ph5, styles.pv3]}
+ pendingAction={config?.pendingFields?.nonReimbursableCreditCardChargeDefaultVendor}
+ errors={ErrorUtils.getLatestErrorField(config, CONST.SAGE_INTACCT_CONFIG.NON_REIMBURSABLE_CREDIT_CARD_VENDOR)}
+ onCloseError={() => Policy.clearSageIntacctErrorField(policyID, CONST.SAGE_INTACCT_CONFIG.NON_REIMBURSABLE_CREDIT_CARD_VENDOR)}
+ />
+ {!!config?.export.nonReimbursableCreditCardChargeDefaultVendor && defaultVendor}
- }
- />
+ )}
+
);
}
diff --git a/src/pages/workspace/accounting/intacct/export/SageIntacctPreferredExporterPage.tsx b/src/pages/workspace/accounting/intacct/export/SageIntacctPreferredExporterPage.tsx
index abbb87982a5e..c7a954cfc722 100644
--- a/src/pages/workspace/accounting/intacct/export/SageIntacctPreferredExporterPage.tsx
+++ b/src/pages/workspace/accounting/intacct/export/SageIntacctPreferredExporterPage.tsx
@@ -8,11 +8,13 @@ import Text from '@components/Text';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
+import * as ErrorUtils from '@libs/ErrorUtils';
import {getAdminEmployees, isExpensifyTeam} from '@libs/PolicyUtils';
import Navigation from '@navigation/Navigation';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import {updateSageIntacctExporter} from '@userActions/connections/SageIntacct';
+import * as Policy from '@userActions/Policy/Policy';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
@@ -24,6 +26,7 @@ function SageIntacctPreferredExporterPage({policy}: WithPolicyProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const policyOwner = policy?.owner ?? '';
+ const {config} = policy?.connections?.intacct ?? {};
const {export: exportConfiguration} = policy?.connections?.intacct?.config ?? {};
const exporters = getAdminEmployees(policy);
const {login: currentUserLogin} = useCurrentUserPersonalDetails();
@@ -95,6 +98,10 @@ function SageIntacctPreferredExporterPage({policy}: WithPolicyProps) {
onBackButtonPress={() => Navigation.goBack(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_EXPORT.getRoute(policyID))}
title="workspace.sageIntacct.preferredExporter"
connectionName={CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT}
+ pendingAction={config?.pendingFields?.exporter}
+ errors={ErrorUtils.getLatestErrorField(config ?? {}, CONST.SAGE_INTACCT_CONFIG.EXPORTER)}
+ errorRowStyles={[styles.ph5, styles.mv2]}
+ onClose={() => Policy.clearSageIntacctErrorField(policyID, CONST.SAGE_INTACCT_CONFIG.EXPORTER)}
/>
);
}
diff --git a/src/pages/workspace/accounting/intacct/export/SageIntacctReimbursableExpensesPage.tsx b/src/pages/workspace/accounting/intacct/export/SageIntacctReimbursableExpensesPage.tsx
index b3a4a04a5582..f9fca3f7944c 100644
--- a/src/pages/workspace/accounting/intacct/export/SageIntacctReimbursableExpensesPage.tsx
+++ b/src/pages/workspace/accounting/intacct/export/SageIntacctReimbursableExpensesPage.tsx
@@ -10,11 +10,13 @@ import type {ListItem} from '@components/SelectionList/types';
import type {SelectorType} from '@components/SelectionScreen';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
+import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@navigation/Navigation';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow';
import {updateSageIntacctDefaultVendor, updateSageIntacctReimbursableExpensesExportDestination} from '@userActions/connections/SageIntacct';
+import * as Policy from '@userActions/Policy/Policy';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {SageIntacctDataElementWithValue} from '@src/types/onyx/Policy';
@@ -59,8 +61,8 @@ function SageIntacctReimbursableExpensesPage({policy}: WithPolicyProps) {
description: translate('workspace.sageIntacct.defaultVendor'),
action: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_DEFAULT_VENDOR.getRoute(policyID, CONST.SAGE_INTACCT_CONFIG.REIMBURSABLE)),
title: defaultVendorName && defaultVendorName !== '' ? defaultVendorName : translate('workspace.sageIntacct.notConfigured'),
- hasError: !!config?.export?.errorFields?.reimbursableExpenseReportDefaultVendor,
- pendingAction: config?.export?.pendingFields?.reimbursableExpenseReportDefaultVendor,
+ hasError: !!config?.errorFields?.reimbursableExpenseReportDefaultVendor,
+ pendingAction: config?.pendingFields?.reimbursableExpenseReportDefaultVendor,
};
return (
@@ -78,8 +80,8 @@ function SageIntacctReimbursableExpensesPage({policy}: WithPolicyProps) {
);
}, [
- config?.export?.errorFields?.reimbursableExpenseReportDefaultVendor,
- config?.export?.pendingFields?.reimbursableExpenseReportDefaultVendor,
+ config?.errorFields?.reimbursableExpenseReportDefaultVendor,
+ config?.pendingFields?.reimbursableExpenseReportDefaultVendor,
intacctData?.vendors,
policyID,
reimbursableExpenseReportDefaultVendor,
@@ -97,36 +99,46 @@ function SageIntacctReimbursableExpensesPage({policy}: WithPolicyProps) {
displayName={SageIntacctReimbursableExpensesPage.displayName}
policyID={policyID}
connectionName={CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT}
- contentContainerStyle={[styles.flex1]}
shouldUseScrollView={false}
+ shouldIncludeSafeAreaPaddingBottom
>
- selectReimbursableDestination(selection as MenuListItem)}
- sections={[{data}]}
- ListItem={RadioListItem}
- showScrollIndicator
- shouldShowTooltips={false}
- listFooterContent={
- reimbursable === CONST.SAGE_INTACCT_REIMBURSABLE_EXPENSE_TYPE.EXPENSE_REPORT ? (
-
- {
- const vendor = enabled ? policy?.connections?.intacct?.data?.vendors?.[0].id ?? '' : '';
- updateSageIntacctDefaultVendor(policyID, CONST.SAGE_INTACCT_CONFIG.REIMBURSABLE_VENDOR, vendor);
- }}
- wrapperStyle={[styles.ph5, styles.pv3]}
- pendingAction={config?.export?.pendingFields?.reimbursableExpenseReportDefaultVendor}
- />
- {!!reimbursableExpenseReportDefaultVendor && defaultVendor}
-
- ) : undefined
- }
- />
+ Policy.clearSageIntacctErrorField(policyID, CONST.SAGE_INTACCT_CONFIG.REIMBURSABLE)}
+ style={[styles.flexGrow1, styles.flexShrink1]}
+ contentContainerStyle={[styles.flexGrow1, styles.flexShrink1]}
+ >
+ selectReimbursableDestination(selection as MenuListItem)}
+ sections={[{data}]}
+ ListItem={RadioListItem}
+ showScrollIndicator
+ shouldShowTooltips={false}
+ containerStyle={[styles.flexReset, styles.flexGrow1, styles.flexShrink1]}
+ />
+
+ {reimbursable === CONST.SAGE_INTACCT_REIMBURSABLE_EXPENSE_TYPE.EXPENSE_REPORT && (
+
+ {
+ const vendor = enabled ? policy?.connections?.intacct?.data?.vendors?.[0].id ?? '' : '';
+ updateSageIntacctDefaultVendor(policyID, CONST.SAGE_INTACCT_CONFIG.REIMBURSABLE_VENDOR, vendor);
+ }}
+ pendingAction={config?.pendingFields?.reimbursableExpenseReportDefaultVendor}
+ errors={ErrorUtils.getLatestErrorField(config, CONST.SAGE_INTACCT_CONFIG.REIMBURSABLE_VENDOR)}
+ wrapperStyle={[styles.ph5, styles.pv3]}
+ onCloseError={() => Policy.clearSageIntacctErrorField(policyID, CONST.SAGE_INTACCT_CONFIG.REIMBURSABLE_VENDOR)}
+ />
+ {!!reimbursableExpenseReportDefaultVendor && defaultVendor}
+
+ )}
);
}
diff --git a/src/pages/workspace/card/issueNew/AssigneeStep.tsx b/src/pages/workspace/card/issueNew/AssigneeStep.tsx
index 5012ba294518..23acb3d4a24a 100644
--- a/src/pages/workspace/card/issueNew/AssigneeStep.tsx
+++ b/src/pages/workspace/card/issueNew/AssigneeStep.tsx
@@ -1,30 +1,124 @@
-import React from 'react';
+import React, {useMemo} from 'react';
import {View} from 'react-native';
-import FormProvider from '@components/Form/FormProvider';
+import type {OnyxEntry} from 'react-native-onyx';
+import {useOnyx} from 'react-native-onyx';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import * as Expensicons from '@components/Icon/Expensicons';
import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader';
import ScreenWrapper from '@components/ScreenWrapper';
+import SelectionList from '@components/SelectionList';
+import type {ListItem} from '@components/SelectionList/types';
+import UserListItem from '@components/SelectionList/UserListItem';
import Text from '@components/Text';
+import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
+import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
+import {formatPhoneNumber} from '@libs/LocalePhoneNumber';
+import * as OptionsListUtils from '@libs/OptionsListUtils';
+import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
+import * as PolicyUtils from '@libs/PolicyUtils';
import Navigation from '@navigation/Navigation';
import * as Card from '@userActions/Card';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import type * as OnyxTypes from '@src/types/onyx';
-function AssigneeStep() {
+const MINIMUM_MEMBER_TO_SHOW_SEARCH = 8;
+
+type AssigneeStepProps = {
+ // The policy that the card will be issued under
+ policy: OnyxEntry;
+};
+
+function AssigneeStep({policy}: AssigneeStepProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
+ const {isOffline} = useNetwork();
+ const [issueNewCard] = useOnyx(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD);
+
+ const isEditing = issueNewCard?.isEditing;
+
+ const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState('');
- const submit = () => {
- // TODO: the logic will be created in https://github.com/Expensify/App/issues/44309
- Card.setIssueNewCardStep(CONST.EXPENSIFY_CARD.STEP.CARD_TYPE);
+ const submit = (assignee: ListItem) => {
+ Card.setIssueNewCardStepAndData({
+ step: isEditing ? CONST.EXPENSIFY_CARD.STEP.CONFIRMATION : CONST.EXPENSIFY_CARD.STEP.CARD_TYPE,
+ data: {
+ assigneeEmail: assignee?.login ?? '',
+ },
+ isEditing: false,
+ });
};
const handleBackButtonPress = () => {
+ if (isEditing) {
+ Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false});
+ return;
+ }
Navigation.goBack();
+ Card.clearIssueNewCardFlow();
};
+ const shouldShowSearchInput = policy?.employeeList && Object.keys(policy.employeeList).length >= MINIMUM_MEMBER_TO_SHOW_SEARCH;
+ const textInputLabel = shouldShowSearchInput ? translate('workspace.card.issueNewCard.findMember') : undefined;
+
+ const membersDetails = useMemo(() => {
+ let membersList: ListItem[] = [];
+ if (!policy?.employeeList) {
+ return membersList;
+ }
+
+ Object.entries(policy.employeeList ?? {}).forEach(([email, policyEmployee]) => {
+ if (PolicyUtils.isDeletedPolicyEmployee(policyEmployee, isOffline)) {
+ return;
+ }
+
+ const personalDetail = PersonalDetailsUtils.getPersonalDetailByEmail(email);
+ membersList.push({
+ keyForList: email,
+ text: personalDetail?.displayName,
+ alternateText: email,
+ login: email,
+ accountID: personalDetail?.accountID,
+ icons: [
+ {
+ source: personalDetail?.avatar ?? Expensicons.FallbackAvatar,
+ name: formatPhoneNumber(email),
+ type: CONST.ICON_TYPE_AVATAR,
+ id: personalDetail?.accountID,
+ },
+ ],
+ });
+ });
+
+ membersList = OptionsListUtils.sortItemsAlphabetically(membersList);
+
+ return membersList;
+ }, [isOffline, policy?.employeeList]);
+
+ const sections = useMemo(() => {
+ if (!debouncedSearchTerm) {
+ return [
+ {
+ data: membersDetails,
+ shouldShow: true,
+ },
+ ];
+ }
+
+ const searchValue = OptionsListUtils.getSearchValueForPhoneOrEmail(debouncedSearchTerm).toLowerCase();
+ const filteredOptions = membersDetails.filter((option) => !!option.text?.toLowerCase().includes(searchValue) || !!option.alternateText?.toLowerCase().includes(searchValue));
+
+ return [
+ {
+ title: undefined,
+ data: filteredOptions,
+ shouldShow: true,
+ },
+ ];
+ }, [membersDetails, debouncedSearchTerm]);
+
return (
{translate('workspace.card.issueNewCard.whoNeedsCard')}
-
- {/* TODO: the content will be created in https://github.com/Expensify/App/issues/44309 */}
-
-
+
);
}
diff --git a/src/pages/workspace/card/issueNew/CardNameStep.tsx b/src/pages/workspace/card/issueNew/CardNameStep.tsx
index 9b48d6417732..58b0748e438a 100644
--- a/src/pages/workspace/card/issueNew/CardNameStep.tsx
+++ b/src/pages/workspace/card/issueNew/CardNameStep.tsx
@@ -1,28 +1,59 @@
-import React from 'react';
+import React, {useCallback} from 'react';
import {View} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
import FormProvider from '@components/Form/FormProvider';
+import InputWrapper from '@components/Form/InputWrapper';
+import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader';
import ScreenWrapper from '@components/ScreenWrapper';
import Text from '@components/Text';
+import TextInput from '@components/TextInput';
+import useAutoFocusInput from '@hooks/useAutoFocusInput';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
+import * as ValidationUtils from '@libs/ValidationUtils';
import * as Card from '@userActions/Card';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import INPUT_IDS from '@src/types/form/IssueNewExpensifyCardForm';
function CardNameStep() {
const {translate} = useLocalize();
const styles = useThemeStyles();
+ const {inputCallbackRef} = useAutoFocusInput();
+ const [issueNewCard] = useOnyx(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD);
- const submit = () => {
- // TODO: the logic will be created in https://github.com/Expensify/App/issues/44309
- Card.setIssueNewCardStep(CONST.EXPENSIFY_CARD.STEP.CONFIRMATION);
- };
+ const isEditing = issueNewCard?.isEditing;
- const handleBackButtonPress = () => {
- Card.setIssueNewCardStep(CONST.EXPENSIFY_CARD.STEP.LIMIT);
- };
+ const validate = useCallback(
+ (values: FormOnyxValues): FormInputErrors => {
+ const errors = ValidationUtils.getFieldRequiredErrors(values, [INPUT_IDS.CARD_TITLE]);
+ if (!values.cardTitle) {
+ errors.cardTitle = translate('common.error.fieldRequired');
+ }
+ return errors;
+ },
+ [translate],
+ );
+
+ const submit = useCallback((values: FormOnyxValues) => {
+ Card.setIssueNewCardStepAndData({
+ step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION,
+ data: {
+ cardTitle: values.cardTitle,
+ },
+ isEditing: false,
+ });
+ }, []);
+
+ const handleBackButtonPress = useCallback(() => {
+ if (isEditing) {
+ Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false});
+ return;
+ }
+ Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.LIMIT});
+ }, [isEditing]);
return (
{translate('workspace.card.issueNewCard.giveItName')}
- {/* TODO: the content will be created in https://github.com/Expensify/App/issues/44309 */}
-
+
);
diff --git a/src/pages/workspace/card/issueNew/CardTypeStep.tsx b/src/pages/workspace/card/issueNew/CardTypeStep.tsx
index 93b99f51d239..31b5585b91ad 100644
--- a/src/pages/workspace/card/issueNew/CardTypeStep.tsx
+++ b/src/pages/workspace/card/issueNew/CardTypeStep.tsx
@@ -1,12 +1,16 @@
import React from 'react';
import {View} from 'react-native';
-import FormProvider from '@components/Form/FormProvider';
+import {useOnyx} from 'react-native-onyx';
+import type {ValueOf} from 'type-fest';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import * as Illustrations from '@components/Icon/Illustrations';
import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader';
+import MenuItem from '@components/MenuItem';
import ScreenWrapper from '@components/ScreenWrapper';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
+import variables from '@styles/variables';
import * as Card from '@userActions/Card';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -14,14 +18,26 @@ import ONYXKEYS from '@src/ONYXKEYS';
function CardTypeStep() {
const {translate} = useLocalize();
const styles = useThemeStyles();
+ const [issueNewCard] = useOnyx(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD);
- const submit = () => {
- // TODO: the logic will be created in https://github.com/Expensify/App/issues/44309
- Card.setIssueNewCardStep(CONST.EXPENSIFY_CARD.STEP.LIMIT_TYPE);
+ const isEditing = issueNewCard?.isEditing;
+
+ const submit = (value: ValueOf) => {
+ Card.setIssueNewCardStepAndData({
+ step: isEditing ? CONST.EXPENSIFY_CARD.STEP.CONFIRMATION : CONST.EXPENSIFY_CARD.STEP.LIMIT_TYPE,
+ data: {
+ cardType: value,
+ },
+ isEditing: false,
+ });
};
const handleBackButtonPress = () => {
- Card.setIssueNewCardStep(CONST.EXPENSIFY_CARD.STEP.ASSIGNEE);
+ if (isEditing) {
+ Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false});
+ return;
+ }
+ Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.ASSIGNEE});
};
return (
@@ -42,15 +58,32 @@ function CardTypeStep() {
/>
{translate('workspace.card.issueNewCard.chooseCardType')}
-
- {/* TODO: the content will be created in https://github.com/Expensify/App/issues/44309 */}
-
-
+
+
);
}
diff --git a/src/pages/workspace/card/issueNew/ConfirmationStep.tsx b/src/pages/workspace/card/issueNew/ConfirmationStep.tsx
index a64d6f463531..35f9fab6598f 100644
--- a/src/pages/workspace/card/issueNew/ConfirmationStep.tsx
+++ b/src/pages/workspace/card/issueNew/ConfirmationStep.tsx
@@ -1,32 +1,62 @@
import React from 'react';
import {View} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
import Button from '@components/Button';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader';
+import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import ScreenWrapper from '@components/ScreenWrapper';
+import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
+import * as CurrencyUtils from '@libs/CurrencyUtils';
+import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import Navigation from '@navigation/Navigation';
import * as Card from '@userActions/Card';
import CONST from '@src/CONST';
-import ROUTES from '@src/ROUTES';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {IssueNewCardStep} from '@src/types/onyx/Card';
+
+function getTranslationKeyForLimitType(limitType: string | undefined) {
+ switch (limitType) {
+ case CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART:
+ return 'workspace.card.issueNewCard.smartLimit';
+ case CONST.EXPENSIFY_CARD.LIMIT_TYPES.FIXED:
+ return 'workspace.card.issueNewCard.fixedAmount';
+ case CONST.EXPENSIFY_CARD.LIMIT_TYPES.MONTHLY:
+ return 'workspace.card.issueNewCard.monthly';
+ default:
+ return '';
+ }
+}
function ConfirmationStep() {
const {translate} = useLocalize();
const styles = useThemeStyles();
const {isOffline} = useNetwork();
+ const [issueNewCard] = useOnyx(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD);
+
+ const data = issueNewCard?.data;
+
const submit = () => {
- // TODO: the logic will be created in https://github.com/Expensify/App/issues/44309
- Navigation.navigate(ROUTES.SETTINGS);
+ // TODO: the logic will be created when CreateExpensifyCard is ready
+ Navigation.goBack();
+ Card.clearIssueNewCardFlow();
+ };
+
+ const editStep = (step: IssueNewCardStep) => {
+ Card.setIssueNewCardStepAndData({step, isEditing: true});
};
const handleBackButtonPress = () => {
- Card.setIssueNewCardStep(CONST.EXPENSIFY_CARD.STEP.CARD_NAME);
+ Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CARD_NAME});
};
+ const translationForLimitType = getTranslationKeyForLimitType(data?.limitType);
+
return (
- {translate('workspace.card.issueNewCard.letsDoubleCheck')}
-
- {/* TODO: the content will be created in https://github.com/Expensify/App/issues/44309 */}
-
+ editStep(CONST.EXPENSIFY_CARD.STEP.CARD_TYPE)}
+ />
+ editStep(CONST.EXPENSIFY_CARD.STEP.LIMIT)}
+ />
+ editStep(CONST.EXPENSIFY_CARD.STEP.LIMIT_TYPE)}
+ />
+ editStep(CONST.EXPENSIFY_CARD.STEP.CARD_NAME)}
+ />
+
+
+
+
);
}
diff --git a/src/pages/workspace/card/issueNew/IssueNewCardPage.tsx b/src/pages/workspace/card/issueNew/IssueNewCardPage.tsx
index d63bbd56b4d0..e12835a4a1e0 100644
--- a/src/pages/workspace/card/issueNew/IssueNewCardPage.tsx
+++ b/src/pages/workspace/card/issueNew/IssueNewCardPage.tsx
@@ -1,5 +1,7 @@
import React from 'react';
import {useOnyx} from 'react-native-onyx';
+import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading';
+import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import AssigneeStep from './AssigneeStep';
@@ -9,18 +11,21 @@ import ConfirmationStep from './ConfirmationStep';
import LimitStep from './LimitStep';
import LimitTypeStep from './LimitTypeStep';
-function IssueNewCardPage() {
+function IssueNewCardPage({policy}: WithPolicyAndFullscreenLoadingProps) {
const [issueNewCard] = useOnyx(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD);
const {currentStep} = issueNewCard ?? {};
+ // TODO: add logic to skip Assignee step when the flow is started from the member's profile page
+ // TODO: StartIssueNewCardFlow call to API
+
switch (currentStep) {
case CONST.EXPENSIFY_CARD.STEP.ASSIGNEE:
- return ;
+ return ;
case CONST.EXPENSIFY_CARD.STEP.CARD_TYPE:
return ;
case CONST.EXPENSIFY_CARD.STEP.LIMIT_TYPE:
- return ;
+ return ;
case CONST.EXPENSIFY_CARD.STEP.LIMIT:
return ;
case CONST.EXPENSIFY_CARD.STEP.CARD_NAME:
@@ -28,9 +33,9 @@ function IssueNewCardPage() {
case CONST.EXPENSIFY_CARD.STEP.CONFIRMATION:
return ;
default:
- return ;
+ return ;
}
}
IssueNewCardPage.displayName = 'IssueNewCardPage';
-export default IssueNewCardPage;
+export default withPolicyAndFullscreenLoading(IssueNewCardPage);
diff --git a/src/pages/workspace/card/issueNew/LimitStep.tsx b/src/pages/workspace/card/issueNew/LimitStep.tsx
index dd2e80a6612a..cc65a987bd62 100644
--- a/src/pages/workspace/card/issueNew/LimitStep.tsx
+++ b/src/pages/workspace/card/issueNew/LimitStep.tsx
@@ -1,28 +1,63 @@
-import React from 'react';
+import React, {useCallback} from 'react';
import {View} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
+import AmountForm from '@components/AmountForm';
import FormProvider from '@components/Form/FormProvider';
+import InputWrapper from '@components/Form/InputWrapper';
+import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader';
import ScreenWrapper from '@components/ScreenWrapper';
import Text from '@components/Text';
+import useAutoFocusInput from '@hooks/useAutoFocusInput';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
+import * as CurrencyUtils from '@libs/CurrencyUtils';
+import * as ValidationUtils from '@libs/ValidationUtils';
import * as Card from '@userActions/Card';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import INPUT_IDS from '@src/types/form/IssueNewExpensifyCardForm';
function LimitStep() {
const {translate} = useLocalize();
+ const {inputCallbackRef} = useAutoFocusInput();
const styles = useThemeStyles();
+ const [issueNewCard] = useOnyx(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD);
+ const isEditing = issueNewCard?.isEditing;
- const submit = () => {
- // TODO: the logic will be created in https://github.com/Expensify/App/issues/44309
- Card.setIssueNewCardStep(CONST.EXPENSIFY_CARD.STEP.CARD_NAME);
- };
+ const submit = useCallback(
+ (values: FormOnyxValues) => {
+ const limit = CurrencyUtils.convertToBackendAmount(Number(values?.limit));
+ Card.setIssueNewCardStepAndData({
+ step: isEditing ? CONST.EXPENSIFY_CARD.STEP.CONFIRMATION : CONST.EXPENSIFY_CARD.STEP.CARD_NAME,
+ data: {limit},
+ isEditing: false,
+ });
+ },
+ [isEditing],
+ );
+
+ const handleBackButtonPress = useCallback(() => {
+ if (isEditing) {
+ Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false});
+ return;
+ }
+ Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.LIMIT_TYPE});
+ }, [isEditing]);
- const handleBackButtonPress = () => {
- Card.setIssueNewCardStep(CONST.EXPENSIFY_CARD.STEP.LIMIT_TYPE);
- };
+ const validate = useCallback(
+ (values: FormOnyxValues): FormInputErrors => {
+ const errors = ValidationUtils.getFieldRequiredErrors(values, [INPUT_IDS.LIMIT]);
+
+ // We only want integers to be sent as the limit
+ if (!Number(values.limit) || !Number.isInteger(Number(values.limit))) {
+ errors.limit = translate('iou.error.invalidAmount');
+ }
+ return errors;
+ },
+ [translate],
+ );
return (
{translate('workspace.card.issueNewCard.setLimit')}
- {/* TODO: the content will be created in https://github.com/Expensify/App/issues/44309 */}
-
+
);
diff --git a/src/pages/workspace/card/issueNew/LimitTypeStep.tsx b/src/pages/workspace/card/issueNew/LimitTypeStep.tsx
index b1249e33e3c4..79b9c40ef4ae 100644
--- a/src/pages/workspace/card/issueNew/LimitTypeStep.tsx
+++ b/src/pages/workspace/card/issueNew/LimitTypeStep.tsx
@@ -1,28 +1,87 @@
-import React from 'react';
+import React, {useCallback, useMemo, useState} from 'react';
import {View} from 'react-native';
-import FormProvider from '@components/Form/FormProvider';
+import type {OnyxEntry} from 'react-native-onyx';
+import {useOnyx} from 'react-native-onyx';
+import Button from '@components/Button';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader';
import ScreenWrapper from '@components/ScreenWrapper';
+import SelectionList from '@components/SelectionList';
+import RadioListItem from '@components/SelectionList/RadioListItem';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Card from '@userActions/Card';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import type * as OnyxTypes from '@src/types/onyx';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
-function LimitTypeStep() {
+type LimitTypeStepProps = {
+ // The policy that the card will be issued under
+ policy: OnyxEntry;
+};
+
+function LimitTypeStep({policy}: LimitTypeStepProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
+ const [issueNewCard] = useOnyx(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD);
+
+ const areApprovalsConfigured = !isEmptyObject(policy?.approver) && policy?.approvalMode !== CONST.POLICY.APPROVAL_MODE.OPTIONAL;
+ const defaultType = areApprovalsConfigured ? CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART : CONST.EXPENSIFY_CARD.LIMIT_TYPES.MONTHLY;
+
+ const [typeSelected, setTypeSelected] = useState(issueNewCard?.data?.limitType ?? defaultType);
+
+ const isEditing = issueNewCard?.isEditing;
+
+ const submit = useCallback(() => {
+ Card.setIssueNewCardStepAndData({
+ step: isEditing ? CONST.EXPENSIFY_CARD.STEP.CONFIRMATION : CONST.EXPENSIFY_CARD.STEP.LIMIT,
+ data: {limitType: typeSelected},
+ isEditing: false,
+ });
+ }, [isEditing, typeSelected]);
- const submit = () => {
- // TODO: the logic will be created in https://github.com/Expensify/App/issues/44309
- Card.setIssueNewCardStep(CONST.EXPENSIFY_CARD.STEP.LIMIT);
- };
+ const handleBackButtonPress = useCallback(() => {
+ if (isEditing) {
+ Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false});
+ return;
+ }
+ Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CARD_TYPE});
+ }, [isEditing]);
- const handleBackButtonPress = () => {
- Card.setIssueNewCardStep(CONST.EXPENSIFY_CARD.STEP.CARD_TYPE);
- };
+ const data = useMemo(() => {
+ const options = [];
+
+ if (areApprovalsConfigured) {
+ options.push({
+ value: CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART,
+ text: translate('workspace.card.issueNewCard.smartLimit'),
+ alternateText: translate('workspace.card.issueNewCard.smartLimitDescription'),
+ keyForList: CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART,
+ isSelected: typeSelected === CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART,
+ });
+ }
+
+ options.push(
+ {
+ value: CONST.EXPENSIFY_CARD.LIMIT_TYPES.MONTHLY,
+ text: translate('workspace.card.issueNewCard.monthly'),
+ alternateText: translate('workspace.card.issueNewCard.monthlyDescription'),
+ keyForList: CONST.EXPENSIFY_CARD.LIMIT_TYPES.MONTHLY,
+ isSelected: typeSelected === CONST.EXPENSIFY_CARD.LIMIT_TYPES.MONTHLY,
+ },
+ {
+ value: CONST.EXPENSIFY_CARD.LIMIT_TYPES.FIXED,
+ text: translate('workspace.card.issueNewCard.fixedAmount'),
+ alternateText: translate('workspace.card.issueNewCard.fixedAmountDescription'),
+ keyForList: CONST.EXPENSIFY_CARD.LIMIT_TYPES.FIXED,
+ isSelected: typeSelected === CONST.EXPENSIFY_CARD.LIMIT_TYPES.FIXED,
+ },
+ );
+
+ return options;
+ }, [areApprovalsConfigured, translate, typeSelected]);
return (
{translate('workspace.card.issueNewCard.chooseLimitType')}
-
- {/* TODO: the content will be created in https://github.com/Expensify/App/issues/44309 */}
-
-
+ setTypeSelected(value)}
+ sections={[{data}]}
+ shouldDebounceRowSelect
+ />
+
);
}
diff --git a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx
index 46b073b2bd48..91a5f3b8f3f1 100644
--- a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx
+++ b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx
@@ -21,6 +21,7 @@ import Navigation from '@navigation/Navigation';
import type {FullScreenNavigatorParamList} from '@navigation/types';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type {Card, WorkspaceCardsList} from '@src/types/onyx';
import WorkspaceCardListHeader from './WorkspaceCardListHeader';
@@ -100,7 +101,7 @@ function WorkspaceExpensifyCardListPage({route}: WorkspaceExpensifyCardListPageP