diff --git a/android/app/build.gradle b/android/app/build.gradle
index a53c6dfb62af..7318b5b6d3ff 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -98,8 +98,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001043600
- versionName "1.4.36-0"
+ versionCode 1001043701
+ versionName "1.4.37-1"
}
flavorDimensions "default"
diff --git a/docs/articles/expensify-classic/getting-started/Individual-Users.md b/docs/articles/expensify-classic/getting-started/Individual-Users.md
deleted file mode 100644
index 12029f80388b..000000000000
--- a/docs/articles/expensify-classic/getting-started/Individual-Users.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-title: Individual Users
-description: Learn how Expensify can help you track and submit your personal or self-employed business expenses.
----
-# Overview
-If you're an individual using Expensify, the Track and Submit plans are designed to assist self-employed users in effectively managing both their personal and business finances.
-
-# How to use the Track plan
-
-The Track plan is tailored for solo Expensify users who don't require expense submission to others. Individuals or sole proprietors can choose the Track plan to customize their Individual Workspace to align with their personal expense tracking requirements.
-
-You can select the Track plan from the Workspace settings. Navigate to **Settings > Workspace > Individual > *[Workspace Name]* > Plan** to select Track.
-You can also do this from the Pricing page at https://www.expensify.com/pricing.
-
-The Track plan includes a predefined set of categories designed to align with IRS Schedule C expense categories. However, you have the flexibility to add extra categories as needed. For a more detailed breakdown, you can also set up tags to create another layer of coding.
-
-The Track plan offers 25 free SmartScans per month. If you require more than 25 SmartScans, you can upgrade to a Monthly Individual subscription at a cost of $4.99 USD per month.
-
-# How to use the Submit plan
-The Submit plan is designed for individuals who need to keep track of their expenses and share them with someone else, such as their boss, accountant, or even a housemate. It's specifically tailored for single users who want to both track and submit their expenses efficiently.
-
-You can select the Track plan from the Workspace settings. Navigate to **Settings > Workspaces > Individual > *[Workspace Name]* > Plan** to select "Submit" or from the Pricing page at https://www.expensify.com/pricing.
-
-You will select who your expenses get sent to under **Settings > Workspace > Individual > *[Workspace Name]* > Reports**. If the recipient already has an Expensify account, they'll be able to see the report directly in the Expensify app. Otherwise, non-Expensify users will receive a PDF copy of the report attached to the email so it can be processed.
-
-The Submit plan includes a predefined set of categories designed to align with IRS Schedule C expense categories. However, you have the flexibility to add extra categories as needed. For a more detailed breakdown, you can also set up tags to create another layer of coding.
-
-The Submit plan offers 25 free SmartScans per month.If you require more than 25 SmartScans, you can upgrade to a Monthly Individual subscription at a cost of $4.99 USD per month.
-
-# FAQ
-
-## Who should use the Track plan?
-An individual who wants to store receipts, look to track spending by category to help with budgeting and a self-employed user who needs to track receipts and mileage for tax purposes.
-
-## Who should use the Submit plan?
-An individual who seeks to utilize the features of the track plan to monitor their expenses while also requiring the ability to submit those expenses to someone else.
-
-## How can I keep track of personal and business expenses in the same account?
-You have the capability to create distinct "business" and "personal" tags and assign them to your expenses for proper categorization. By doing so, you can effectively code your expenses based on their nature. Additionally, you can utilize filters to ensure that you only view the expenses that are relevant to your specific needs, whether they are business-related or personal.
-
-## How can I export expenses for tax purposes?
-From the expense page, you have the option to select all of your expenses and export them to a CSV (Comma-Separated Values) file. This CSV file can be conveniently imported directly into your tax software for easier tax preparation.
-
diff --git a/docs/articles/expensify-classic/getting-started/Join-your-company's-workspace.md b/docs/articles/expensify-classic/getting-started/Join-your-company's-workspace.md
index 099f381e6010..a31b972e683c 100644
--- a/docs/articles/expensify-classic/getting-started/Join-your-company's-workspace.md
+++ b/docs/articles/expensify-classic/getting-started/Join-your-company's-workspace.md
@@ -212,7 +212,6 @@ Once you’ve created your expenses, they may be automatically added to an expen
Attach PDF: Select this checkbox to attach a copy of your report to the email.
Tap Submit .
-
{% include end-option.html %}
@@ -255,4 +254,4 @@ Add an extra layer of security to help keep your financial data safe and secure
When you log in to Expensify in the future, you’ll open your authenticator app to get the code and enter it into Expensify. A new code regenerates every few seconds, so the code is always different. If the code time runs out, you can generate a new code as needed.
-
\ No newline at end of file
+
diff --git a/docs/redirects.csv b/docs/redirects.csv
index df615431533f..988b07d729f0 100644
--- a/docs/redirects.csv
+++ b/docs/redirects.csv
@@ -49,3 +49,4 @@ https://community.expensify.com/discussion/5793/how-to-connect-your-personal-car
https://community.expensify.com/discussion/4826/how-to-set-your-annual-subscription-size,https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription
https://community.expensify.com/discussion/5667/deep-dive-how-does-the-annual-subscription-billing-work,https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription
https://help.expensify.com/expensify-classic/hubs/getting-started/plan-types,https://www.expensify.com/pricing
+https://help.expensify.com/articles/expensify-classic/getting-started/Employees,https://help.expensify.com/articles/expensify-classic/getting-started/Join-your-company's-workspace
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 6a6a54e58c49..f8bf6953b9bf 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.4.36
+ 1.4.37
CFBundleSignature
????
CFBundleURLTypes
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.4.36.0
+ 1.4.37.1
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 763896fd45f1..cef9024b8744 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 1.4.36
+ 1.4.37
CFBundleSignature
????
CFBundleVersion
- 1.4.36.0
+ 1.4.37.1
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index a4bef0847c95..c1f74b80085d 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -11,9 +11,9 @@
CFBundleName
$(PRODUCT_NAME)
CFBundleShortVersionString
- 1.4.36
+ 1.4.37
CFBundleVersion
- 1.4.36.0
+ 1.4.37.1
NSExtension
NSExtensionPointIdentifier
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 584dd5636773..ad1826d1c8e6 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1922,7 +1922,7 @@ SPEC CHECKSUMS:
react-native-image-picker: c33d4e79f0a14a2b66e5065e14946ae63749660b
react-native-key-command: 5af6ee30ff4932f78da6a2109017549042932aa5
react-native-launch-arguments: 5f41e0abf88a15e3c5309b8875d6fd5ac43df49d
- react-native-live-markdown: 1ca4275d4dba8eebb736a005148f24a8224f54d9
+ react-native-live-markdown: 81ac491b913cb8541a06586adcdc159ab1b86fb5
react-native-netinfo: 8a7fd3f7130ef4ad2fb4276d5c9f8d3f28d2df3d
react-native-pager-view: 02a5c4962530f7efc10dd51ee9cdabeff5e6c631
react-native-pdf: b4ca3d37a9a86d9165287741c8b2ef4d8940c00e
@@ -1981,7 +1981,7 @@ SPEC CHECKSUMS:
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2
VisionCamera: 7d13aae043ffb38b224a0f725d1e23ca9c190fe7
- Yoga: 13c8ef87792450193e117976337b8527b49e8c03
+ Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047
PODFILE CHECKSUM: 0ccbb4f2406893c6e9f266dc1e7470dcd72885d2
diff --git a/package-lock.json b/package-lock.json
index 674325508da5..ac335fd12eb2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,17 +1,17 @@
{
"name": "new.expensify",
- "version": "1.4.36-0",
+ "version": "1.4.37-1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.4.36-0",
+ "version": "1.4.37-1",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@dotlottie/react-player": "^1.6.3",
- "@expensify/react-native-live-markdown": "git+ssh://git@github.com/Expensify/react-native-live-markdown.git#c316611781f19815caebfed5540e0faf2a274785",
+ "@expensify/react-native-live-markdown": "git+ssh://git@github.com/Expensify/react-native-live-markdown.git#2ed4240336e50abb4a7fa9ff6a3c180f8bc9ce5b",
"@expo/metro-runtime": "~3.1.1",
"@formatjs/intl-datetimeformat": "^6.10.0",
"@formatjs/intl-getcanonicallocales": "^2.2.0",
@@ -21,7 +21,7 @@
"@formatjs/intl-pluralrules": "^5.2.2",
"@gorhom/portal": "^1.0.14",
"@invertase/react-native-apple-authentication": "^2.2.2",
- "@kie/act-js": "^2.0.1",
+ "@kie/act-js": "^2.6.0",
"@kie/mock-github": "^1.0.0",
"@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52",
"@onfido/react-native-sdk": "8.3.0",
@@ -3352,8 +3352,8 @@
},
"node_modules/@expensify/react-native-live-markdown": {
"version": "0.1.0",
- "resolved": "git+ssh://git@github.com/Expensify/react-native-live-markdown.git#c316611781f19815caebfed5540e0faf2a274785",
- "integrity": "sha512-yF3oaBhqWQonl12LPELYLsgfmqCsGg2bu15g/h8XzVX3f/nzfPtrWE/ax2lWEIpIjk4/+aEu/VGNKLnlehjTxQ==",
+ "resolved": "git+ssh://git@github.com/Expensify/react-native-live-markdown.git#2ed4240336e50abb4a7fa9ff6a3c180f8bc9ce5b",
+ "integrity": "sha512-i+HIsCFL9cdma+saH/KN2llTGqEb2DQttEJKozdm4fvcie9Ce2/q7XNDZo6nIYTbIVXPDLKPDmWLXqXTgLBKDQ==",
"license": "MIT",
"workspaces": [
"example"
@@ -7186,9 +7186,9 @@
"dev": true
},
"node_modules/@kie/act-js": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@kie/act-js/-/act-js-2.3.0.tgz",
- "integrity": "sha512-Q9k0b05uA46jXKWmVfoGDW+0xsCcE7QPiHi8IH7h41P36DujHKBj4k28RCeIEx3IwUCxYHKwubN8DH4Vzc9XcA==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@kie/act-js/-/act-js-2.6.0.tgz",
+ "integrity": "sha512-AkBTAsvRWLMvJ0cBiZ0+XUC9QVNwJzcZFxZJV2i+oaXPirafCyzzmT7+cJ4t0YNFMkgM+EEN7XMWr2MgoSyOvg==",
"hasInstallScript": true,
"dependencies": {
"@kie/mock-github": "^2.0.0",
@@ -32351,9 +32351,9 @@
}
},
"node_modules/follow-redirects": {
- "version": "1.15.3",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
- "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
+ "version": "1.15.5",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
+ "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
"funding": [
{
"type": "individual",
diff --git a/package.json b/package.json
index 0c1105f978c6..9cf4ec00727a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.4.36-0",
+ "version": "1.4.37-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.",
@@ -59,7 +59,7 @@
},
"dependencies": {
"@dotlottie/react-player": "^1.6.3",
- "@expensify/react-native-live-markdown": "git+ssh://git@github.com/Expensify/react-native-live-markdown.git#c316611781f19815caebfed5540e0faf2a274785",
+ "@expensify/react-native-live-markdown": "git+ssh://git@github.com/Expensify/react-native-live-markdown.git#2ed4240336e50abb4a7fa9ff6a3c180f8bc9ce5b",
"@expo/metro-runtime": "~3.1.1",
"@formatjs/intl-datetimeformat": "^6.10.0",
"@formatjs/intl-getcanonicallocales": "^2.2.0",
@@ -69,7 +69,7 @@
"@formatjs/intl-pluralrules": "^5.2.2",
"@gorhom/portal": "^1.0.14",
"@invertase/react-native-apple-authentication": "^2.2.2",
- "@kie/act-js": "^2.0.1",
+ "@kie/act-js": "^2.6.0",
"@kie/mock-github": "^1.0.0",
"@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52",
"@onfido/react-native-sdk": "8.3.0",
diff --git a/patches/react-native-pdf+6.7.3.patch b/patches/react-native-pdf+6.7.3.patch
new file mode 100644
index 000000000000..63024e7d4c1f
--- /dev/null
+++ b/patches/react-native-pdf+6.7.3.patch
@@ -0,0 +1,23 @@
+diff --git a/node_modules/react-native-pdf/index.js b/node_modules/react-native-pdf/index.js
+index c05de52..bea7af8 100644
+--- a/node_modules/react-native-pdf/index.js
++++ b/node_modules/react-native-pdf/index.js
+@@ -367,11 +367,17 @@ export default class Pdf extends Component {
+ message[4] = message.splice(4).join('|');
+ }
+ if (message[0] === 'loadComplete') {
++ let tableContents;
++ try {
++ tableContents = message[4]&&JSON.parse(message[4]);
++ } catch(e) {
++ tableContents = message[4];
++ }
+ this.props.onLoadComplete && this.props.onLoadComplete(Number(message[1]), this.state.path, {
+ width: Number(message[2]),
+ height: Number(message[3]),
+ },
+- message[4]&&JSON.parse(message[4]));
++ tableContents);
+ } else if (message[0] === 'pageChanged') {
+ this.props.onPageChanged && this.props.onPageChanged(Number(message[1]), Number(message[2]));
+ } else if (message[0] === 'error') {
diff --git a/src/CONST.ts b/src/CONST.ts
index b27923465a1f..6c726cde12f7 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -46,6 +46,8 @@ const CONST = {
IN: 'in',
OUT: 'out',
},
+ // Multiplier for gyroscope animation in order to make it a bit more subtle
+ ANIMATION_GYROSCOPE_VALUE: 0.4,
BACKGROUND_IMAGE_TRANSITION_DURATION: 1000,
ARROW_HIDE_DELAY: 3000,
@@ -100,6 +102,10 @@ const CONST = {
MAX_LENGTH: 40,
},
+ REPORT_DESCRIPTION: {
+ MAX_LENGTH: 1024,
+ },
+
PULL_REQUEST_NUMBER,
MERCHANT_NAME_MAX_LENGTH: 255,
@@ -146,7 +152,7 @@ const CONST = {
CONTAINER_MINHEIGHT: 500,
VIEW_HEIGHT: 275,
},
- MONEY_REPORT: {
+ MONEY_OR_TASK_REPORT: {
SMALL_SCREEN: {
IMAGE_HEIGHT: 300,
CONTAINER_MINHEIGHT: 280,
@@ -1431,6 +1437,8 @@ const CONST = {
EMOJI: /[\p{Extended_Pictographic}\u200d\u{1f1e6}-\u{1f1ff}\u{1f3fb}-\u{1f3ff}\u{e0020}-\u{e007f}\u20E3\uFE0F]|[#*0-9]\uFE0F?\u20E3/gu,
// eslint-disable-next-line max-len, no-misleading-character-class
EMOJIS: /[\p{Extended_Pictographic}](\u200D[\p{Extended_Pictographic}]|[\u{1F3FB}-\u{1F3FF}]|[\u{E0020}-\u{E007F}]|\uFE0F|\u20E3)*|[\u{1F1E6}-\u{1F1FF}]{2}|[#*0-9]\uFE0F?\u20E3/gu,
+ // eslint-disable-next-line max-len, no-misleading-character-class
+ EMOJI_SKIN_TONES: /[\u{1f3fb}-\u{1f3ff}]/gu,
TAX_ID: /^\d{9}$/,
NON_NUMERIC: /\D/g,
@@ -1588,7 +1596,6 @@ const CONST = {
INVITE: 'invite',
SETTINGS: 'settings',
LEAVE_ROOM: 'leaveRoom',
- WELCOME_MESSAGE: 'welcomeMessage',
PRIVATE_NOTES: 'privateNotes',
},
EDIT_REQUEST_FIELD: {
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 7328fb2543ad..9fed8b69db79 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -313,8 +313,8 @@ const ONYXKEYS = {
DISPLAY_NAME_FORM_DRAFT: 'displayNameFormDraft',
ROOM_NAME_FORM: 'roomNameForm',
ROOM_NAME_FORM_DRAFT: 'roomNameFormDraft',
- WELCOME_MESSAGE_FORM: 'welcomeMessageForm',
- WELCOME_MESSAGE_FORM_DRAFT: 'welcomeMessageFormDraft',
+ REPORT_DESCRIPTION_FORM: 'reportDescriptionForm',
+ REPORT_DESCRIPTION_FORM_DRAFT: 'reportDescriptionFormDraft',
LEGAL_NAME_FORM: 'legalNameForm',
LEGAL_NAME_FORM_DRAFT: 'legalNameFormDraft',
WORKSPACE_INVITE_MESSAGE_FORM: 'workspaceInviteMessageForm',
@@ -408,7 +408,7 @@ type OnyxValues = {
[ONYXKEYS.NVP_PRIVATE_PUSH_NOTIFICATION_ID]: string;
[ONYXKEYS.NVP_TRY_FOCUS_MODE]: boolean;
[ONYXKEYS.FOCUS_MODE_NOTIFICATION]: boolean;
- [ONYXKEYS.NVP_LAST_PAYMENT_METHOD]: Record;
+ [ONYXKEYS.NVP_LAST_PAYMENT_METHOD]: OnyxTypes.LastPaymentMethod;
[ONYXKEYS.NVP_RECENT_WAYPOINTS]: OnyxTypes.RecentWaypoint[];
[ONYXKEYS.NVP_HAS_DISMISSED_IDLE_PANEL]: boolean;
[ONYXKEYS.NVP_INTRO_SELECTED]: OnyxTypes.IntroSelected;
@@ -480,9 +480,9 @@ type OnyxValues = {
[ONYXKEYS.COLLECTION.TRANSACTION]: OnyxTypes.Transaction;
[ONYXKEYS.COLLECTION.TRANSACTION_DRAFT]: OnyxTypes.Transaction;
[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: OnyxTypes.TransactionViolations;
+ [ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT]: OnyxTypes.Transaction;
[ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS]: OnyxTypes.RecentlyUsedTags;
[ONYXKEYS.COLLECTION.SELECTED_TAB]: string;
- [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: OnyxTypes.TransactionViolation[];
[ONYXKEYS.COLLECTION.PRIVATE_NOTES_DRAFT]: string;
[ONYXKEYS.COLLECTION.NEXT_STEP]: OnyxTypes.ReportNextStep;
@@ -499,10 +499,10 @@ type OnyxValues = {
[ONYXKEYS.FORMS.PROFILE_SETTINGS_FORM_DRAFT]: OnyxTypes.Form;
[ONYXKEYS.FORMS.DISPLAY_NAME_FORM]: OnyxTypes.DisplayNameForm;
[ONYXKEYS.FORMS.DISPLAY_NAME_FORM_DRAFT]: OnyxTypes.DisplayNameForm;
- [ONYXKEYS.FORMS.ROOM_NAME_FORM]: OnyxTypes.Form;
- [ONYXKEYS.FORMS.ROOM_NAME_FORM_DRAFT]: OnyxTypes.Form;
- [ONYXKEYS.FORMS.WELCOME_MESSAGE_FORM]: OnyxTypes.Form;
- [ONYXKEYS.FORMS.WELCOME_MESSAGE_FORM_DRAFT]: OnyxTypes.Form;
+ [ONYXKEYS.FORMS.ROOM_NAME_FORM]: OnyxTypes.RoomNameForm;
+ [ONYXKEYS.FORMS.ROOM_NAME_FORM_DRAFT]: OnyxTypes.RoomNameForm;
+ [ONYXKEYS.FORMS.REPORT_DESCRIPTION_FORM]: OnyxTypes.Form;
+ [ONYXKEYS.FORMS.REPORT_DESCRIPTION_FORM_DRAFT]: OnyxTypes.Form;
[ONYXKEYS.FORMS.LEGAL_NAME_FORM]: OnyxTypes.Form;
[ONYXKEYS.FORMS.LEGAL_NAME_FORM_DRAFT]: OnyxTypes.Form;
[ONYXKEYS.FORMS.WORKSPACE_INVITE_MESSAGE_FORM]: OnyxTypes.Form;
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index a84dc9c8f9ae..016e4267803b 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -214,10 +214,6 @@ const ROUTES = {
route: 'r/:reportID/settings/who-can-post',
getRoute: (reportID: string) => `r/${reportID}/settings/who-can-post` as const,
},
- REPORT_WELCOME_MESSAGE: {
- route: 'r/:reportID/welcomeMessage',
- getRoute: (reportID: string) => `r/${reportID}/welcomeMessage` as const,
- },
SPLIT_BILL_DETAILS: {
route: 'r/:reportID/split/:reportActionID',
getRoute: (reportID: string, reportActionID: string) => `r/${reportID}/split/${reportActionID}` as const,
@@ -235,7 +231,7 @@ const ROUTES = {
route: 'r/:reportID/title',
getRoute: (reportID: string) => `r/${reportID}/title` as const,
},
- TASK_DESCRIPTION: {
+ REPORT_DESCRIPTION: {
route: 'r/:reportID/description',
getRoute: (reportID: string) => `r/${reportID}/description` as const,
},
@@ -293,10 +289,6 @@ const ROUTES = {
route: ':iouType/new/category/:reportID?',
getRoute: (iouType: string, reportID = '') => `${iouType}/new/category/${reportID}` as const,
},
- MONEY_REQUEST_TAG: {
- route: ':iouType/new/tag/:reportID?',
- getRoute: (iouType: string, reportID = '') => `${iouType}/new/tag/${reportID}` as const,
- },
MONEY_REQUEST_MERCHANT: {
route: ':iouType/new/merchant/:reportID?',
getRoute: (iouType: string, reportID = '') => `${iouType}/new/merchant/${reportID}` as const,
@@ -380,9 +372,9 @@ const ROUTES = {
getUrlWithBackToParam(`${action}/${iouType}/scan/${transactionID}/${reportID}`, backTo),
},
MONEY_REQUEST_STEP_TAG: {
- route: 'create/:iouType/tag/:transactionID/:reportID',
- getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') =>
- getUrlWithBackToParam(`create/${iouType}/tag/${transactionID}/${reportID}`, backTo),
+ route: ':action/:iouType/tag/:transactionID/:reportID',
+ getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') =>
+ getUrlWithBackToParam(`${action}/${iouType}/tag/${transactionID}/${reportID}`, backTo),
},
MONEY_REQUEST_STEP_WAYPOINT: {
route: ':action/:iouType/waypoint/:transactionID/:reportID/:pageIndex',
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 96b284dbea2f..e2f0e9745561 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -100,7 +100,7 @@ const SCREENS = {
PROFILE: 'Profile',
REPORT_DETAILS: 'Report_Details',
REPORT_SETTINGS: 'Report_Settings',
- REPORT_WELCOME_MESSAGE: 'Report_WelcomeMessage',
+ REPORT_DESCRIPTION: 'Report_Description',
PARTICIPANTS: 'Participants',
MONEY_REQUEST: 'MoneyRequest',
NEW_TASK: 'NewTask',
@@ -152,7 +152,6 @@ const SCREENS = {
DATE: 'Money_Request_Date',
DESCRIPTION: 'Money_Request_Description',
CATEGORY: 'Money_Request_Category',
- TAG: 'Money_Request_Tag',
MERCHANT: 'Money_Request_Merchant',
WAYPOINT: 'Money_Request_Waypoint',
EDIT_WAYPOINT: 'Money_Request_Edit_Waypoint',
@@ -184,7 +183,6 @@ const SCREENS = {
TASK: {
TITLE: 'Task_Title',
- DESCRIPTION: 'Task_Description',
ASSIGNEE: 'Task_Assignee',
},
@@ -243,7 +241,7 @@ const SCREENS = {
DETAILS_ROOT: 'Details_Root',
PROFILE_ROOT: 'Profile_Root',
PROCESS_MONEY_REQUEST_HOLD_ROOT: 'ProcessMoneyRequestHold_Root',
- REPORT_WELCOME_MESSAGE_ROOT: 'Report_WelcomeMessage_Root',
+ REPORT_DESCRIPTION_ROOT: 'Report_Description_Root',
REPORT_PARTICIPANTS_ROOT: 'ReportParticipants_Root',
ROOM_MEMBERS_ROOT: 'RoomMembers_Root',
ROOM_INVITE_ROOT: 'RoomInvite_Root',
diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx
index 90954c63b751..78ef72ac3536 100755
--- a/src/components/AttachmentModal.tsx
+++ b/src/components/AttachmentModal.tsx
@@ -283,7 +283,7 @@ function AttachmentModal({
* Detach the receipt and close the modal.
*/
const deleteAndCloseModal = useCallback(() => {
- IOU.detachReceipt(transaction?.transactionID);
+ IOU.detachReceipt(transaction?.transactionID ?? '');
setIsDeleteReceiptConfirmModalVisible(false);
Navigation.dismissModal(report?.reportID);
}, [transaction, report]);
diff --git a/src/components/ButtonWithDropdownMenu.tsx b/src/components/ButtonWithDropdownMenu.tsx
index 676912de6b60..9466da601825 100644
--- a/src/components/ButtonWithDropdownMenu.tsx
+++ b/src/components/ButtonWithDropdownMenu.tsx
@@ -9,15 +9,18 @@ import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import type {AnchorPosition} from '@styles/index';
import CONST from '@src/CONST';
+import type AnchorAlignment from '@src/types/utils/AnchorAlignment';
+import type DeepValueOf from '@src/types/utils/DeepValueOf';
import type IconAsset from '@src/types/utils/IconAsset';
import Button from './Button';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
-import type {AnchorAlignment} from './Popover/types';
import PopoverMenu from './PopoverMenu';
+type PaymentType = DeepValueOf;
+
type DropdownOption = {
- value: string;
+ value: PaymentType;
text: string;
icon: IconAsset;
iconWidth?: number;
@@ -30,7 +33,7 @@ type ButtonWithDropdownMenuProps = {
menuHeaderText?: string;
/** Callback to execute when the main button is pressed */
- onPress: (event: GestureResponderEvent | KeyboardEvent | undefined, value: string) => void;
+ onPress: (event: GestureResponderEvent | KeyboardEvent | undefined, value: PaymentType) => void;
/** Callback to execute when a dropdown option is selected */
onOptionSelected?: (option: DropdownOption) => void;
@@ -59,6 +62,9 @@ type ButtonWithDropdownMenuProps = {
/* ref for the button */
buttonRef: RefObject;
+
+ /** The priority to assign the enter key event listener to buttons. 0 is the highest priority. */
+ enterKeyEventListenerPriority?: number;
};
function ButtonWithDropdownMenu({
@@ -76,6 +82,7 @@ function ButtonWithDropdownMenu({
onPress,
options,
onOptionSelected,
+ enterKeyEventListenerPriority = 0,
}: ButtonWithDropdownMenuProps) {
const theme = useTheme();
const styles = useThemeStyles();
@@ -126,6 +133,7 @@ function ButtonWithDropdownMenu({
large={isButtonSizeLarge}
medium={!isButtonSizeLarge}
innerStyles={[innerStyleDropButton]}
+ enterKeyEventListenerPriority={enterKeyEventListenerPriority}
/>
@@ -163,6 +172,7 @@ function ButtonWithDropdownMenu({
large={isButtonSizeLarge}
medium={!isButtonSizeLarge}
innerStyles={[innerStyleDropButton]}
+ enterKeyEventListenerPriority={enterKeyEventListenerPriority}
/>
)}
{options.length > 1 && popoverAnchorPosition && (
diff --git a/src/components/DisplayNames/DisplayNamesWithTooltip.tsx b/src/components/DisplayNames/DisplayNamesWithTooltip.tsx
index ce0ae7ddcf4f..86fa8f475664 100644
--- a/src/components/DisplayNames/DisplayNamesWithTooltip.tsx
+++ b/src/components/DisplayNames/DisplayNamesWithTooltip.tsx
@@ -11,7 +11,7 @@ import type DisplayNamesProps from './types';
type HTMLElementWithText = HTMLElement & RNText;
-function DisplayNamesWithToolTip({shouldUseFullTitle, fullTitle, displayNamesWithTooltips, textStyles = [], numberOfLines = 1}: DisplayNamesProps) {
+function DisplayNamesWithToolTip({shouldUseFullTitle, fullTitle, displayNamesWithTooltips, textStyles = [], numberOfLines = 1, renderAdditionalText}: DisplayNamesProps) {
const styles = useThemeStyles();
const containerRef = useRef(null);
const childRefs = useRef([]);
@@ -56,7 +56,7 @@ function DisplayNamesWithToolTip({shouldUseFullTitle, fullTitle, displayNamesWit
>
{shouldUseFullTitle
? ReportUtils.formatReportLastMessageText(fullTitle)
- : displayNamesWithTooltips.map(({displayName, accountID, avatar, login}, index) => (
+ : displayNamesWithTooltips?.map(({displayName, accountID, avatar, login}, index) => (
// eslint-disable-next-line react/no-array-index-key
, }
))}
+ {renderAdditionalText?.()}
{Boolean(isEllipsisActive) && (
diff --git a/src/components/DisplayNames/DisplayNamesWithoutTooltip.tsx b/src/components/DisplayNames/DisplayNamesWithoutTooltip.tsx
index 177bdb6a9fc4..c66d1698bbd6 100644
--- a/src/components/DisplayNames/DisplayNamesWithoutTooltip.tsx
+++ b/src/components/DisplayNames/DisplayNamesWithoutTooltip.tsx
@@ -12,9 +12,12 @@ type DisplayNamesWithoutTooltipProps = {
/** Number of lines before wrapping */
numberOfLines?: number;
+
+ /** Additional Text component to render after the displayNames */
+ renderAdditionalText?: () => React.ReactNode;
};
-function DisplayNamesWithoutTooltip({textStyles = [], numberOfLines = 1, fullTitle = ''}: DisplayNamesWithoutTooltipProps) {
+function DisplayNamesWithoutTooltip({textStyles = [], numberOfLines = 1, fullTitle = '', renderAdditionalText}: DisplayNamesWithoutTooltipProps) {
const styles = useThemeStyles();
return (
{fullTitle}
+ {renderAdditionalText?.()}
);
}
diff --git a/src/components/DisplayNames/index.native.tsx b/src/components/DisplayNames/index.native.tsx
index b3eceb794bcb..ceee34586e8b 100644
--- a/src/components/DisplayNames/index.native.tsx
+++ b/src/components/DisplayNames/index.native.tsx
@@ -4,7 +4,7 @@ import useLocalize from '@hooks/useLocalize';
import type DisplayNamesProps from './types';
// As we don't have to show tooltips of the Native platform so we simply render the full display names list.
-function DisplayNames({accessibilityLabel, fullTitle, textStyles = [], numberOfLines = 1}: DisplayNamesProps) {
+function DisplayNames({accessibilityLabel, fullTitle, textStyles = [], numberOfLines = 1, renderAdditionalText}: DisplayNamesProps) {
const {translate} = useLocalize();
return (
{fullTitle || translate('common.hidden')}
+ {renderAdditionalText?.()}
);
}
diff --git a/src/components/DisplayNames/index.tsx b/src/components/DisplayNames/index.tsx
index 155193368cc5..28e4d131d6de 100644
--- a/src/components/DisplayNames/index.tsx
+++ b/src/components/DisplayNames/index.tsx
@@ -4,7 +4,7 @@ import DisplayNamesWithoutTooltip from './DisplayNamesWithoutTooltip';
import DisplayNamesWithToolTip from './DisplayNamesWithTooltip';
import type DisplayNamesProps from './types';
-function DisplayNames({fullTitle, tooltipEnabled, textStyles, numberOfLines, shouldUseFullTitle, displayNamesWithTooltips}: DisplayNamesProps) {
+function DisplayNames({fullTitle, tooltipEnabled, textStyles, numberOfLines, shouldUseFullTitle, displayNamesWithTooltips, renderAdditionalText}: DisplayNamesProps) {
const {translate} = useLocalize();
const title = fullTitle || translate('common.hidden');
@@ -14,17 +14,29 @@ function DisplayNames({fullTitle, tooltipEnabled, textStyles, numberOfLines, sho
textStyles={textStyles}
numberOfLines={numberOfLines}
fullTitle={title}
+ renderAdditionalText={renderAdditionalText}
+ />
+ );
+ }
+
+ if (shouldUseFullTitle) {
+ return (
+
);
}
return (
);
}
diff --git a/src/components/DisplayNames/types.ts b/src/components/DisplayNames/types.ts
index 2e6f36d5cc07..8e7d711e04f8 100644
--- a/src/components/DisplayNames/types.ts
+++ b/src/components/DisplayNames/types.ts
@@ -20,7 +20,7 @@ type DisplayNamesProps = {
fullTitle: string;
/** Array of objects that map display names to their corresponding tooltip */
- displayNamesWithTooltips: DisplayNameWithTooltip[];
+ displayNamesWithTooltips?: DisplayNameWithTooltip[];
/** Number of lines before wrapping */
numberOfLines: number;
@@ -39,6 +39,9 @@ type DisplayNamesProps = {
/** If the full title needs to be displayed */
shouldUseFullTitle?: boolean;
+
+ /** Additional Text component to render after the displayNames */
+ renderAdditionalText?: () => React.ReactNode;
};
export default DisplayNamesProps;
diff --git a/src/components/EmojiPicker/EmojiPicker.js b/src/components/EmojiPicker/EmojiPicker.js
index eaf89b7f64ea..bcfdba11b6fc 100644
--- a/src/components/EmojiPicker/EmojiPicker.js
+++ b/src/components/EmojiPicker/EmojiPicker.js
@@ -33,6 +33,7 @@ const EmojiPicker = forwardRef((props, ref) => {
const emojiPopoverAnchorRef = useRef(null);
const onModalHide = useRef(() => {});
const onEmojiSelected = useRef(() => {});
+ const activeEmoji = useRef();
const emojiSearchInput = useRef();
const {isSmallScreenWidth, windowHeight} = useWindowDimensions();
@@ -55,10 +56,12 @@ const EmojiPicker = forwardRef((props, ref) => {
* @param {Object} [anchorOrigin=DEFAULT_ANCHOR_ORIGIN] - Anchor origin for Popover
* @param {Function} [onWillShow=() => {}] - Run a callback when Popover will show
* @param {String} id - Unique id for EmojiPicker
+ * @param {String} activeEmojiValue - Selected emoji to be highlighted
*/
- const showEmojiPicker = (onModalHideValue, onEmojiSelectedValue, emojiPopoverAnchorValue, anchorOrigin, onWillShow = () => {}, id) => {
+ const showEmojiPicker = (onModalHideValue, onEmojiSelectedValue, emojiPopoverAnchorValue, anchorOrigin, onWillShow = () => {}, id, activeEmojiValue) => {
onModalHide.current = onModalHideValue;
onEmojiSelected.current = onEmojiSelectedValue;
+ activeEmoji.current = activeEmojiValue;
emojiPopoverAnchorRef.current = emojiPopoverAnchorValue;
const emojiPopoverAnchor = getEmojiPopoverAnchor();
if (emojiPopoverAnchor.current && emojiPopoverAnchor.current.blur) {
@@ -190,6 +193,7 @@ const EmojiPicker = forwardRef((props, ref) => {
>
(emojiSearchInput.current = el)}
/>
diff --git a/src/components/EmojiPicker/EmojiPickerButtonDropdown.js b/src/components/EmojiPicker/EmojiPickerButtonDropdown.js
index 7f60b0615785..24c99988e4f0 100644
--- a/src/components/EmojiPicker/EmojiPickerButtonDropdown.js
+++ b/src/components/EmojiPicker/EmojiPickerButtonDropdown.js
@@ -29,18 +29,25 @@ function EmojiPickerButtonDropdown(props) {
const StyleUtils = useStyleUtils();
const emojiPopoverAnchor = useRef(null);
useEffect(() => EmojiPickerAction.resetEmojiPopoverAnchor, []);
-
const onPress = () => {
if (EmojiPickerAction.isEmojiPickerVisible()) {
EmojiPickerAction.hideEmojiPicker();
return;
}
- EmojiPickerAction.showEmojiPicker(props.onModalHide, (emoji) => props.onInputChange(emoji), emojiPopoverAnchor, {
- horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
- vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
- shiftVertical: 4,
- });
+ EmojiPickerAction.showEmojiPicker(
+ props.onModalHide,
+ (emoji) => props.onInputChange(emoji),
+ emojiPopoverAnchor,
+ {
+ horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
+ vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
+ shiftVertical: 4,
+ },
+ () => {},
+ undefined,
+ props.value,
+ );
};
return (
diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js
index cbf55b31c74d..f336fe659074 100755
--- a/src/components/EmojiPicker/EmojiPickerMenu/index.js
+++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js
@@ -15,6 +15,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as Browser from '@libs/Browser';
import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus';
+import * as EmojiUtils from '@libs/EmojiUtils';
import isEnterWhileComposition from '@libs/KeyboardShortcut/isEnterWhileComposition';
import * as ReportUtils from '@libs/ReportUtils';
import CONST from '@src/CONST';
@@ -34,7 +35,7 @@ const defaultProps = {
const throttleTime = Browser.isMobile() ? 200 : 50;
-function EmojiPickerMenu({forwardedRef, onEmojiSelected}) {
+function EmojiPickerMenu({forwardedRef, onEmojiSelected, activeEmoji}) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {isSmallScreenWidth, windowWidth} = useWindowDimensions();
@@ -91,11 +92,11 @@ function EmojiPickerMenu({forwardedRef, onEmojiSelected}) {
}
// If the input is not focused and the new index is out of range, focus the input
- if (newIndex < 0 && !searchInputRef.current.isFocused()) {
+ if (newIndex < 0 && !searchInputRef.current.isFocused() && shouldFocusInputOnScreenFocus) {
searchInputRef.current.focus();
}
},
- [filteredEmojis.length, highlightFirstEmoji, isUsingKeyboardMovement],
+ [filteredEmojis.length, highlightFirstEmoji, isUsingKeyboardMovement, shouldFocusInputOnScreenFocus],
);
const disabledIndexes = useMemo(() => (isListFiltered ? [] : [...headerIndices, ...spacersIndexes]), [headerIndices, isListFiltered, spacersIndexes]);
@@ -259,7 +260,8 @@ function EmojiPickerMenu({forwardedRef, onEmojiSelected}) {
const emojiCode = types && types[preferredSkinTone] ? types[preferredSkinTone] : code;
const isEmojiFocused = index === focusedIndex && isUsingKeyboardMovement;
- const shouldEmojiBeHighlighted = index === focusedIndex && highlightEmoji;
+ const shouldEmojiBeHighlighted =
+ (index === focusedIndex && highlightEmoji) || (Boolean(activeEmoji) && EmojiUtils.getRemovedSkinToneEmoji(emojiCode) === EmojiUtils.getRemovedSkinToneEmoji(activeEmoji));
const shouldFirstEmojiBeHighlighted = index === 0 && highlightFirstEmoji;
return (
@@ -293,6 +295,7 @@ function EmojiPickerMenu({forwardedRef, onEmojiSelected}) {
translate,
onEmojiSelected,
setFocusedIndex,
+ activeEmoji,
],
);
diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js
index 27a49d360906..604d867f3840 100644
--- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js
+++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js
@@ -10,6 +10,7 @@ import useSingleExecution from '@hooks/useSingleExecution';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
+import * as EmojiUtils from '@libs/EmojiUtils';
import CONST from '@src/CONST';
import BaseEmojiPickerMenu from './BaseEmojiPickerMenu';
import emojiPickerMenuPropTypes from './emojiPickerMenuPropTypes';
@@ -17,7 +18,7 @@ import useEmojiPickerMenu from './useEmojiPickerMenu';
const propTypes = emojiPickerMenuPropTypes;
-function EmojiPickerMenu({onEmojiSelected}) {
+function EmojiPickerMenu({onEmojiSelected, activeEmoji}) {
const styles = useThemeStyles();
const {windowWidth, isSmallScreenWidth} = useWindowDimensions();
const {translate} = useLocalize();
@@ -94,15 +95,17 @@ function EmojiPickerMenu({onEmojiSelected}) {
}
const emojiCode = types && types[preferredSkinTone] ? types[preferredSkinTone] : code;
+ const shouldEmojiBeHighlighted = Boolean(activeEmoji) && EmojiUtils.getRemovedSkinToneEmoji(emojiCode) === EmojiUtils.getRemovedSkinToneEmoji(activeEmoji);
return (
onEmojiSelected(emoji, item))}
emoji={emojiCode}
+ isHighlighted={shouldEmojiBeHighlighted}
/>
);
},
- [styles, windowWidth, preferredSkinTone, singleExecution, onEmojiSelected, translate],
+ [styles, windowWidth, preferredSkinTone, singleExecution, onEmojiSelected, translate, activeEmoji],
);
return (
diff --git a/src/components/FeatureList.js b/src/components/FeatureList.js
index 53d0d8f7c381..14b807aed85e 100644
--- a/src/components/FeatureList.js
+++ b/src/components/FeatureList.js
@@ -25,7 +25,7 @@ const propTypes = {
ctaAccessibilityLabel: PropTypes.string,
/** Action to call on cta button press */
- onCTAPress: PropTypes.func,
+ onCtaPress: PropTypes.func,
/** A list of menuItems representing the feature list. */
menuItems: PropTypes.arrayOf(PropTypes.shape({...menuItemPropTypes, translationKey: PropTypes.string})).isRequired,
@@ -48,13 +48,13 @@ const defaultProps = {
ctaText: '',
ctaAccessibilityLabel: '',
subtitle: '',
- onCTAPress: () => {},
+ onCtaPress: () => {},
illustration: null,
illustrationBackgroundColor: '',
illustrationStyle: [],
};
-function FeatureList({title, subtitle, ctaText, ctaAccessibilityLabel, onCTAPress, menuItems, illustration, illustrationStyle, illustrationBackgroundColor}) {
+function FeatureList({title, subtitle, ctaText, ctaAccessibilityLabel, onCtaPress, menuItems, illustration, illustrationStyle, illustrationBackgroundColor}) {
const styles = useThemeStyles();
const {translate} = useLocalize();
@@ -91,7 +91,7 @@ function FeatureList({title, subtitle, ctaText, ctaAccessibilityLabel, onCTAPres
(null);
@@ -112,7 +113,7 @@ function FormWrapper({
containerStyles={[styles.mh0, styles.mt5, styles.flex1, submitButtonStyles]}
enabledWhenOffline={enabledWhenOffline}
isSubmitActionDangerous={isSubmitActionDangerous}
- disablePressOnEnter
+ disablePressOnEnter={disablePressOnEnter}
/>
)}
@@ -137,6 +138,7 @@ function FormWrapper({
submitButtonText,
shouldHideFixErrorsAlert,
onFixTheErrorsLinkPressed,
+ disablePressOnEnter,
],
);
diff --git a/src/components/Form/types.ts b/src/components/Form/types.ts
index 447f3205ad68..b2c7aea3f3cf 100644
--- a/src/components/Form/types.ts
+++ b/src/components/Form/types.ts
@@ -84,6 +84,9 @@ type FormProps = {
/** Custom content to display in the footer after submit button */
footerContent?: ReactNode;
+
+ /** Disable press on enter for submit button */
+ disablePressOnEnter?: boolean;
};
type RegisterInput = (inputID: keyof Form, inputProps: TInputProps) => TInputProps;
diff --git a/src/components/FormAlertWithSubmitButton.tsx b/src/components/FormAlertWithSubmitButton.tsx
index ae96aa6c5359..789f1cd2466d 100644
--- a/src/components/FormAlertWithSubmitButton.tsx
+++ b/src/components/FormAlertWithSubmitButton.tsx
@@ -7,7 +7,7 @@ import FormAlertWrapper from './FormAlertWrapper';
type FormAlertWithSubmitButtonProps = {
/** Error message to display above button */
- message?: string;
+ message?: string | null;
/** Whether the button is disabled */
isDisabled?: boolean;
diff --git a/src/components/FormAlertWrapper.tsx b/src/components/FormAlertWrapper.tsx
index 65fa2311620d..bdd5622f7aeb 100644
--- a/src/components/FormAlertWrapper.tsx
+++ b/src/components/FormAlertWrapper.tsx
@@ -28,7 +28,7 @@ type FormAlertWrapperProps = {
isMessageHtml?: boolean;
/** Error message to display above button */
- message?: string;
+ message?: string | null;
/** Props to detect online status */
network: Network;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/NextStepEmailRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/NextStepEmailRenderer.tsx
index b8292cad60a2..775bf75294eb 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/NextStepEmailRenderer.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/NextStepEmailRenderer.tsx
@@ -11,7 +11,14 @@ type NextStepEmailRendererProps = {
function NextStepEmailRenderer({tnode}: NextStepEmailRendererProps) {
const styles = useThemeStyles();
- return {tnode.data} ;
+ return (
+
+ {tnode.data}
+
+ );
}
NextStepEmailRenderer.displayName = 'NextStepEmailRenderer';
diff --git a/src/components/HeaderPageLayout.tsx b/src/components/HeaderPageLayout.tsx
index 47b52fa3fcb2..4a857d940a55 100644
--- a/src/components/HeaderPageLayout.tsx
+++ b/src/components/HeaderPageLayout.tsx
@@ -19,6 +19,9 @@ type HeaderPageLayoutProps = ChildrenProps &
/** The background color to apply in the upper half of the screen. */
backgroundColor?: string;
+ /** TestID to apply to the whole section container */
+ testID: string;
+
/** A fixed footer to display at the bottom of the page. */
footer?: ReactNode;
@@ -50,6 +53,7 @@ function HeaderPageLayout({
style,
headerContent,
shouldShowOfflineIndicatorInWideScreen = false,
+ testID,
...rest
}: HeaderPageLayoutProps) {
const theme = useTheme();
@@ -72,7 +76,7 @@ function HeaderPageLayout({
shouldEnablePickerAvoiding={false}
includeSafeAreaPaddingBottom={false}
offlineIndicatorStyle={[appBGColor]}
- testID={HeaderPageLayout.displayName}
+ testID={testID}
shouldShowOfflineIndicatorInWideScreen={shouldShowOfflineIndicatorInWideScreen}
>
{({safeAreaPaddingBottomStyle}) => (
diff --git a/src/components/HeaderWithBackButton/index.tsx b/src/components/HeaderWithBackButton/index.tsx
index 078cb37c7e0d..64fe512eaff2 100755
--- a/src/components/HeaderWithBackButton/index.tsx
+++ b/src/components/HeaderWithBackButton/index.tsx
@@ -70,7 +70,7 @@ function HeaderWithBackButton({
// Hover on some part of close icons will not work on Electron if dragArea is true
// https://github.com/Expensify/App/issues/29598
dataSet={{dragArea: false}}
- style={[styles.headerBar, shouldShowBorderBottom && styles.borderBottom, shouldShowBackButton ? styles.pl0 : styles.pl5, shouldOverlay && StyleSheet.absoluteFillObject]}
+ style={[styles.headerBar, shouldShowBorderBottom && styles.borderBottom, shouldShowBackButton && styles.pl2, shouldOverlay && StyleSheet.absoluteFillObject]}
>
{shouldShowBackButton && (
@@ -87,7 +87,7 @@ function HeaderWithBackButton({
onBackButtonPress();
}
}}
- style={[styles.LHNToggle]}
+ style={[styles.touchableButtonImage]}
role="button"
accessibilityLabel={translate('common.back')}
nativeID={CONST.BACK_BUTTON_NATIVE_ID}
diff --git a/src/components/IllustratedHeaderPageLayout.tsx b/src/components/IllustratedHeaderPageLayout.tsx
index c40a4e33e67a..4a64aa40484f 100644
--- a/src/components/IllustratedHeaderPageLayout.tsx
+++ b/src/components/IllustratedHeaderPageLayout.tsx
@@ -16,9 +16,12 @@ type IllustratedHeaderPageLayoutProps = HeaderPageLayoutProps & {
/** Overlay content to display on top of animation */
overlayContent?: () => ReactNode;
+
+ /** TestID to apply to the whole section container */
+ testID: string;
};
-function IllustratedHeaderPageLayout({backgroundColor, children, illustration, overlayContent, ...rest}: IllustratedHeaderPageLayoutProps) {
+function IllustratedHeaderPageLayout({backgroundColor, children, illustration, testID, overlayContent, ...rest}: IllustratedHeaderPageLayoutProps) {
const theme = useTheme();
const styles = useThemeStyles();
const shouldLimitHeight = !rest.shouldShowBackButton;
@@ -38,6 +41,7 @@ function IllustratedHeaderPageLayout({backgroundColor, children, illustration, o
{overlayContent?.()}
>
}
+ testID={testID}
headerContainerStyles={[styles.justifyContentCenter, styles.w100, shouldLimitHeight && styles.centralPaneAnimation]}
// eslint-disable-next-line react/jsx-props-no-spreading
{...rest}
diff --git a/src/components/KYCWall/BaseKYCWall.tsx b/src/components/KYCWall/BaseKYCWall.tsx
index 89cceadc0fb0..ab2d217deb0e 100644
--- a/src/components/KYCWall/BaseKYCWall.tsx
+++ b/src/components/KYCWall/BaseKYCWall.tsx
@@ -17,7 +17,9 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {BankAccountList, FundList, ReimbursementAccount, UserWallet, WalletTerms} from '@src/types/onyx';
-import type {AnchorPosition, DomRect, KYCWallProps, PaymentMethod, TransferMethod} from './types';
+import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
+import viewRef from '@src/types/utils/viewRef';
+import type {AnchorPosition, DomRect, KYCWallProps, PaymentMethod} from './types';
// This sets the Horizontal anchor position offset for POPOVER MENU.
const POPOVER_MENU_ANCHOR_POSITION_HORIZONTAL_OFFSET = 20;
@@ -67,7 +69,7 @@ function KYCWall({
walletTerms,
shouldShowPersonalBankAccountOption = false,
}: BaseKYCWallProps) {
- const anchorRef = useRef(null);
+ const anchorRef = useRef(null);
const transferBalanceButtonRef = useRef(null);
const [shouldShowAddPaymentMenu, setShouldShowAddPaymentMenu] = useState(false);
@@ -145,7 +147,7 @@ function KYCWall({
*
*/
const continueAction = useCallback(
- (event?: GestureResponderEvent | KeyboardEvent, iouPaymentType?: TransferMethod) => {
+ (event?: GestureResponderEvent | KeyboardEvent, iouPaymentType?: PaymentMethodType) => {
const currentSource = walletTerms?.source ?? source;
/**
@@ -259,7 +261,7 @@ function KYCWall({
}}
shouldShowPersonalBankAccountOption={shouldShowPersonalBankAccountOption}
/>
- {children(continueAction, anchorRef)}
+ {children(continueAction, viewRef(anchorRef))}
>
);
}
diff --git a/src/components/KYCWall/types.ts b/src/components/KYCWall/types.ts
index 1fe16d4a17c3..1ee8010574c3 100644
--- a/src/components/KYCWall/types.ts
+++ b/src/components/KYCWall/types.ts
@@ -1,24 +1,20 @@
-import type {ForwardedRef} from 'react';
-import type {GestureResponderEvent} from 'react-native';
+import type {RefObject} from 'react';
+import type {GestureResponderEvent, View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import type CONST from '@src/CONST';
import type {Route} from '@src/ROUTES';
import type {Report} from '@src/types/onyx';
+import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
+import type AnchorAlignment from '@src/types/utils/AnchorAlignment';
+import type {EmptyObject} from '@src/types/utils/EmptyObject';
type Source = ValueOf;
-type TransferMethod = ValueOf;
-
type DOMRectProperties = 'top' | 'bottom' | 'left' | 'right' | 'height' | 'x' | 'y';
type DomRect = Pick;
-type AnchorAlignment = {
- horizontal: ValueOf;
- vertical: ValueOf;
-};
-
type AnchorPosition = {
anchorPositionVertical: number;
anchorPositionHorizontal: number;
@@ -49,7 +45,7 @@ type KYCWallProps = {
chatReportID?: string;
/** The IOU/Expense report we are paying */
- iouReport?: OnyxEntry;
+ iouReport?: OnyxEntry | EmptyObject;
/** Where the popover should be positioned relative to the anchor points. */
anchorAlignment?: AnchorAlignment;
@@ -64,10 +60,10 @@ type KYCWallProps = {
shouldShowPersonalBankAccountOption?: boolean;
/** Callback for the end of the onContinue trigger on option selection */
- onSuccessfulKYC: (iouPaymentType?: TransferMethod, currentSource?: Source) => void;
+ onSuccessfulKYC: (iouPaymentType?: PaymentMethodType, currentSource?: Source) => void;
/** Children to build the KYC */
- children: (continueAction: (event: GestureResponderEvent | KeyboardEvent | undefined, method: TransferMethod) => void, anchorRef: ForwardedRef) => void;
+ children: (continueAction: (event: GestureResponderEvent | KeyboardEvent | undefined, method: PaymentMethodType) => void, anchorRef: RefObject) => void;
};
-export type {AnchorPosition, KYCWallProps, PaymentMethod, TransferMethod, DomRect};
+export type {AnchorPosition, KYCWallProps, PaymentMethod, DomRect};
diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx
index cc0de763f515..7e5820f425c5 100644
--- a/src/components/MenuItem.tsx
+++ b/src/components/MenuItem.tsx
@@ -57,7 +57,7 @@ type NoIcon = {
type MenuItemProps = (IconProps | AvatarProps | NoIcon) & {
/** Function to fire when component is pressed */
- onPress?: (event: GestureResponderEvent | KeyboardEvent) => void;
+ onPress?: (event: GestureResponderEvent | KeyboardEvent) => void | Promise;
/** Whether the menu item should be interactive at all */
interactive?: boolean;
diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index 5209ab894828..3f5a3c50f6cc 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -54,6 +54,7 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money
const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(moneyRequestReport);
const isApproved = ReportUtils.isReportApproved(moneyRequestReport);
const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID);
+ const canAllowSettlement = ReportUtils.hasUpdatedTotal(moneyRequestReport);
const policyType = policy?.type;
const isPolicyAdmin = policyType !== CONST.POLICY.TYPE.PERSONAL && policy?.role === CONST.POLICY.ROLE.ADMIN;
const isAutoReimbursable = ReportUtils.canBeAutoReimbursed(moneyRequestReport, policy);
@@ -127,7 +128,6 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money
{shouldShowSettlementButton && !isSmallScreenWidth && (
)}
@@ -159,7 +160,6 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money
{shouldShowSettlementButton && isSmallScreenWidth && (
)}
diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js
index afabb40fd9f4..b6a91cf7a9c8 100755
--- a/src/components/MoneyRequestConfirmationList.js
+++ b/src/components/MoneyRequestConfirmationList.js
@@ -540,6 +540,7 @@ function MoneyRequestConfirmationList(props) {
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
}}
shouldShowPersonalBankAccountOption
+ enterKeyEventListenerPriority={1}
/>
) : (
confirm(value)}
options={splitOrRequestOptions}
buttonSize={CONST.DROPDOWN_BUTTON_SIZE.LARGE}
+ enterKeyEventListenerPriority={1}
/>
);
@@ -769,7 +771,15 @@ function MoneyRequestConfirmationList(props) {
numberOfLinesTitle={2}
onPress={() => {
if (props.isEditingSplitBill) {
- Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(props.reportID, props.reportActionID, CONST.EDIT_REQUEST_FIELD.TAG));
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(
+ CONST.IOU.ACTION.EDIT,
+ CONST.IOU.TYPE.SPLIT,
+ props.transaction.transactionID,
+ props.reportID,
+ Navigation.getActiveRouteWithoutParams(),
+ ),
+ );
return;
}
Navigation.navigate(ROUTES.MONEY_REQUEST_TAG.getRoute(props.iouType, props.reportID));
diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
index 2aff0444a59e..be5cec7a2c0d 100755
--- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
+++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
@@ -589,6 +589,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
}}
+ enterKeyEventListenerPriority={1}
/>
) : (
confirm(value)}
options={splitOrRequestOptions}
buttonSize={CONST.DROPDOWN_BUTTON_SIZE.LARGE}
+ enterKeyEventListenerPriority={1}
/>
);
@@ -817,12 +819,14 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
description={policyTagListName}
numberOfLinesTitle={2}
onPress={() =>
- Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(CONST.IOU.ACTION.CREATE, iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()),
+ )
}
style={[styles.moneyRequestMenuItem]}
disabled={didConfirm}
interactive={!isReadOnly}
- rightLabel={canUseViolations && Boolean(policy.requiresTag) ? translate('common.required') : ''}
+ rightLabel={canUseViolations && lodashGet(policy, 'requiresTag', false) ? translate('common.required') : ''}
/>
)}
{shouldShowTax && (
diff --git a/src/components/OfflineWithFeedback.tsx b/src/components/OfflineWithFeedback.tsx
index 2fad21fb54ef..975d154b885b 100644
--- a/src/components/OfflineWithFeedback.tsx
+++ b/src/components/OfflineWithFeedback.tsx
@@ -59,11 +59,6 @@ type OfflineWithFeedbackProps = ChildrenProps & {
type StrikethroughProps = Partial & {style: Array};
-function omitBy(obj: Record | undefined | null, predicate: (value: T) => boolean) {
- // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars
- return Object.fromEntries(Object.entries(obj ?? {}).filter(([_, value]) => !predicate(value)));
-}
-
function OfflineWithFeedback({
pendingAction,
canDismissError = true,
@@ -84,8 +79,12 @@ function OfflineWithFeedback({
const {isOffline} = useNetwork();
const hasErrors = !isEmptyObject(errors ?? {});
+
// Some errors have a null message. This is used to apply opacity only and to avoid showing redundant messages.
- const errorMessages = omitBy(errors, (e: string | ReceiptError) => e === null);
+ const errorEntries = Object.entries(errors ?? {});
+ const filteredErrorEntries = errorEntries.filter((errorEntry): errorEntry is [string, string | ReceiptError] => errorEntry[1] !== null);
+ const errorMessages = Object.fromEntries(filteredErrorEntries);
+
const hasErrorMessages = !isEmptyObject(errorMessages);
const isOfflinePendingAction = !!isOffline && !!pendingAction;
const isUpdateOrDeleteError = hasErrors && (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
diff --git a/src/components/Popover/types.ts b/src/components/Popover/types.ts
index 87a09895d50f..c6c0e98b546c 100644
--- a/src/components/Popover/types.ts
+++ b/src/components/Popover/types.ts
@@ -1,20 +1,11 @@
import type {RefObject} from 'react';
import type {View} from 'react-native';
-import type {ValueOf} from 'type-fest';
import type {PopoverAnchorPosition} from '@components/Modal/types';
import type BaseModalProps from '@components/Modal/types';
import type {WindowDimensionsProps} from '@components/withWindowDimensions/types';
-import type CONST from '@src/CONST';
+import type AnchorAlignment from '@src/types/utils/AnchorAlignment';
import type ChildrenProps from '@src/types/utils/ChildrenProps';
-type AnchorAlignment = {
- /** The horizontal anchor alignment of the popover */
- horizontal: ValueOf;
-
- /** The vertical anchor alignment of the popover */
- vertical: ValueOf;
-};
-
type PopoverDimensions = {
width: number;
height: number;
@@ -49,4 +40,4 @@ type PopoverProps = BaseModalProps &
type PopoverWithWindowDimensionsProps = PopoverProps & WindowDimensionsProps;
-export type {PopoverProps, PopoverWithWindowDimensionsProps, AnchorAlignment};
+export type {PopoverProps, PopoverWithWindowDimensionsProps};
diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx
index ff7d0fdfb8e5..34391933da32 100644
--- a/src/components/PopoverMenu.tsx
+++ b/src/components/PopoverMenu.tsx
@@ -9,9 +9,9 @@ import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import CONST from '@src/CONST';
import type {AnchorPosition} from '@src/styles';
+import type AnchorAlignment from '@src/types/utils/AnchorAlignment';
import type IconAsset from '@src/types/utils/IconAsset';
import MenuItem from './MenuItem';
-import type {AnchorAlignment} from './Popover/types';
import PopoverWithMeasuredContent from './PopoverWithMeasuredContent';
import Text from './Text';
diff --git a/src/components/PopoverProvider/index.tsx b/src/components/PopoverProvider/index.tsx
index a738d1f9798a..69728d7be126 100644
--- a/src/components/PopoverProvider/index.tsx
+++ b/src/components/PopoverProvider/index.tsx
@@ -27,9 +27,6 @@ function PopoverContextProvider(props: PopoverContextProps) {
}
activePopoverRef.current.close();
- if (activePopoverRef.current.onCloseCallback) {
- activePopoverRef.current.onCloseCallback();
- }
activePopoverRef.current = null;
setIsOpen(false);
}, []);
@@ -107,9 +104,6 @@ function PopoverContextProvider(props: PopoverContextProps) {
closePopover(activePopoverRef.current.anchorRef);
}
activePopoverRef.current = popoverParams;
- if (popoverParams?.onOpenCallback) {
- popoverParams.onOpenCallback();
- }
setIsOpen(true);
},
[closePopover],
diff --git a/src/components/PopoverProvider/types.ts b/src/components/PopoverProvider/types.ts
index 49705d7ea7a8..2a366ae2a712 100644
--- a/src/components/PopoverProvider/types.ts
+++ b/src/components/PopoverProvider/types.ts
@@ -16,8 +16,6 @@ type AnchorRef = {
ref: RefObject;
close: (anchorRef?: RefObject) => void;
anchorRef: RefObject;
- onOpenCallback?: () => void;
- onCloseCallback?: () => void;
};
export type {PopoverContextProps, PopoverContextValue, AnchorRef};
diff --git a/src/components/ProcessMoneyRequestHoldMenu.tsx b/src/components/ProcessMoneyRequestHoldMenu.tsx
index 5f32240aca9b..e3f6190d2a1b 100644
--- a/src/components/ProcessMoneyRequestHoldMenu.tsx
+++ b/src/components/ProcessMoneyRequestHoldMenu.tsx
@@ -3,11 +3,11 @@ import React from 'react';
import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
+import type AnchorAlignment from '@src/types/utils/AnchorAlignment';
import Button from './Button';
import HoldMenuSectionList from './HoldMenuSectionList';
import type {PopoverAnchorPosition} from './Modal/types';
import Popover from './Popover';
-import type {AnchorAlignment} from './Popover/types';
import Text from './Text';
import TextPill from './TextPill';
diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx
index 3c0e50b2c940..e2021360c11a 100644
--- a/src/components/ReportActionItem/MoneyReportView.tsx
+++ b/src/components/ReportActionItem/MoneyReportView.tsx
@@ -44,6 +44,7 @@ function MoneyReportView({report, policy, policyReportFields, shouldShowHorizont
const {isSmallScreenWidth} = useWindowDimensions();
const {canUseReportFields} = usePermissions();
const isSettled = ReportUtils.isSettled(report.reportID);
+ const isTotalUpdated = ReportUtils.hasUpdatedTotal(report);
const {totalDisplaySpend, nonReimbursableSpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(report);
@@ -119,7 +120,7 @@ function MoneyReportView({report, policy, policyReportFields, shouldShowHorizont
)}
{formattedTotalAmount}
diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 3533506797bb..3a3aef6cabcd 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -295,8 +295,12 @@ function MoneyRequestView({
shouldShowRightIcon={canEditMerchant}
titleStyle={styles.flex1}
onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.MERCHANT))}
- brickRoadIndicator={hasViolations('merchant') || (hasErrors && isEmptyMerchant && isPolicyExpenseChat) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
- error={hasErrors && isPolicyExpenseChat && isEmptyMerchant ? translate('common.error.enterMerchant') : ''}
+ brickRoadIndicator={
+ hasViolations('merchant') || (!isSettled && !isCancelled && hasErrors && isEmptyMerchant && isPolicyExpenseChat)
+ ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR
+ : undefined
+ }
+ error={!isSettled && !isCancelled && hasErrors && isPolicyExpenseChat && isEmptyMerchant ? translate('common.error.enterMerchant') : ''}
/>
{canUseViolations && }
@@ -336,7 +340,9 @@ function MoneyRequestView({
interactive={canEdit}
shouldShowRightIcon={canEdit}
titleStyle={styles.flex1}
- onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.TAG))}
+ onPress={() =>
+ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(CONST.IOU.ACTION.EDIT, CONST.IOU.TYPE.REQUEST, transaction?.transactionID ?? '', report.reportID))
+ }
brickRoadIndicator={hasViolations('tag') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
/>
{canUseViolations && }
diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx
index bdb3bbd2bb33..c572e9d5f896 100644
--- a/src/components/ReportActionItem/ReportPreview.tsx
+++ b/src/components/ReportActionItem/ReportPreview.tsx
@@ -130,9 +130,11 @@ function ReportPreview({
const isDraftExpenseReport = isPolicyExpenseChat && ReportUtils.isDraftExpenseReport(iouReport);
const isApproved = ReportUtils.isReportApproved(iouReport);
+ const canAllowSettlement = ReportUtils.hasUpdatedTotal(iouReport);
const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(iouReport);
const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID);
const numberOfScanningReceipts = transactionsWithReceipts.filter((transaction) => TransactionUtils.isReceiptBeingScanned(transaction)).length;
+
const hasReceipts = transactionsWithReceipts.length > 0;
const isScanning = hasReceipts && areAllRequestsBeingSmartScanned;
const hasErrors = (hasReceipts && hasMissingSmartscanFields) || (canUseViolations && ReportUtils.hasViolations(iouReportID, transactionViolations));
@@ -288,12 +290,11 @@ function ReportPreview({
)}
{shouldShowSettlementButton && (
chatReport && iouReport && IOU.payMoneyRequest(paymentType, chatReport, iouReport)}
+ onPress={(paymentType?: PaymentMethodType) => chatReport && iouReport && paymentType && IOU.payMoneyRequest(paymentType, chatReport, iouReport)}
enablePaymentsRoute={ROUTES.ENABLE_PAYMENTS}
addBankAccountRoute={bankAccountRoute}
shouldHidePaymentOptions={!shouldShowPayButton}
@@ -307,6 +308,7 @@ function ReportPreview({
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
}}
+ isDisabled={!canAllowSettlement}
/>
)}
{shouldShowSubmitButton && (
diff --git a/src/components/ReportActionItem/TaskView.tsx b/src/components/ReportActionItem/TaskView.tsx
index bdcc4d01b1a8..0d450e46568d 100644
--- a/src/components/ReportActionItem/TaskView.tsx
+++ b/src/components/ReportActionItem/TaskView.tsx
@@ -142,7 +142,7 @@ function TaskView({report, shouldShowHorizontalRule, ...props}: TaskViewProps) {
shouldParseTitle
description={translate('task.description')}
title={report.description ?? ''}
- onPress={() => Navigation.navigate(ROUTES.TASK_DESCRIPTION.getRoute(report.reportID))}
+ onPress={() => Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report.reportID))}
shouldShowRightIcon={isOpen}
disabled={disableState}
wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]}
diff --git a/src/components/ReportHeaderSkeletonView.tsx b/src/components/ReportHeaderSkeletonView.tsx
index 2113abd85e88..5332e0c5032c 100644
--- a/src/components/ReportHeaderSkeletonView.tsx
+++ b/src/components/ReportHeaderSkeletonView.tsx
@@ -24,12 +24,12 @@ function ReportHeaderSkeletonView({shouldAnimate = true, onBackButtonPress = ()
const {isSmallScreenWidth} = useWindowDimensions();
return (
-
-
+
+
{isSmallScreenWidth && (
diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx
index 7c7364cc58f0..5e39e0adb93d 100644
--- a/src/components/ReportWelcomeText.tsx
+++ b/src/components/ReportWelcomeText.tsx
@@ -12,6 +12,8 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {PersonalDetailsList, Policy, Report} from '@src/types/onyx';
+import {PressableWithoutFeedback} from './Pressable';
+import RenderHTML from './RenderHTML';
import Text from './Text';
import UserDetailsTooltip from './UserDetailsTooltip';
@@ -57,29 +59,47 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP
{isChatRoom ? translate('reportActionsView.welcomeToRoom', {roomName: ReportUtils.getReportName(report)}) : translate('reportActionsView.sayHello')}
-
+
{isPolicyExpenseChat && (
- <>
+
{translate('reportActionsView.beginningOfChatHistoryPolicyExpenseChatPartOne')}
{ReportUtils.getDisplayNameForParticipant(report?.ownerAccountID)}
{translate('reportActionsView.beginningOfChatHistoryPolicyExpenseChatPartTwo')}
{ReportUtils.getPolicyName(report)}
{translate('reportActionsView.beginningOfChatHistoryPolicyExpenseChatPartThree')}
- >
+
)}
{isChatRoom && (
<>
- {roomWelcomeMessage.phrase1}
- {roomWelcomeMessage.showReportName && (
- {
+ if (ReportUtils.canEditReportDescription(report, policy)) {
+ Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report.reportID));
+ return;
+ }
+ Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID));
+ }}
+ style={styles.renderHTML}
+ accessibilityLabel={translate('reportDescriptionPage.roomDescription')}
>
- {ReportUtils.getReportName(report)}
+
+
+ ) : (
+
+ {roomWelcomeMessage.phrase1}
+ {roomWelcomeMessage.showReportName && (
+
+ {ReportUtils.getReportName(report)}
+
+ )}
+ {roomWelcomeMessage.phrase2 !== undefined && {roomWelcomeMessage.phrase2} }
)}
- {roomWelcomeMessage.phrase2 !== undefined && {roomWelcomeMessage.phrase2} }
>
)}
{isDefault && (
@@ -112,7 +132,7 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP
{(moneyRequestOptions.includes(CONST.IOU.TYPE.SEND) || moneyRequestOptions.includes(CONST.IOU.TYPE.REQUEST)) && (
{translate('reportActionsView.usePlusButton', {additionalText})}
)}
-
+
>
);
}
diff --git a/src/components/SelectionList/RadioListItem.tsx b/src/components/SelectionList/RadioListItem.tsx
index 769eaa80df4b..ca0ae859c3ad 100644
--- a/src/components/SelectionList/RadioListItem.tsx
+++ b/src/components/SelectionList/RadioListItem.tsx
@@ -1,7 +1,6 @@
import React from 'react';
import {View} from 'react-native';
-import Text from '@components/Text';
-import Tooltip from '@components/Tooltip';
+import TextWithTooltip from '@components/TextWithTooltip';
import useThemeStyles from '@hooks/useThemeStyles';
import type {RadioListItemProps} from './types';
@@ -10,30 +9,18 @@ function RadioListItem({item, showTooltip, textStyles, alternateTextStyles}: Rad
return (
-
-
- {item.text}
-
-
+ textStyles={textStyles}
+ />
{!!item.alternateText && (
-
-
- {item.alternateText}
-
-
+ textStyles={alternateTextStyles}
+ />
)}
);
diff --git a/src/components/SelectionList/UserListItem.tsx b/src/components/SelectionList/UserListItem.tsx
index 471d21e52420..fede09c1b435 100644
--- a/src/components/SelectionList/UserListItem.tsx
+++ b/src/components/SelectionList/UserListItem.tsx
@@ -1,8 +1,7 @@
import React from 'react';
import {View} from 'react-native';
import SubscriptAvatar from '@components/SubscriptAvatar';
-import Text from '@components/Text';
-import Tooltip from '@components/Tooltip';
+import TextWithTooltip from '@components/TextWithTooltip';
import useThemeStyles from '@hooks/useThemeStyles';
import type {UserListItemProps} from './types';
@@ -18,29 +17,17 @@ function UserListItem({item, textStyles, alternateTextStyles, showTooltip, style
/>
)}
-
-
- {item.text}
-
-
+ textStyles={[textStyles, style]}
+ />
{!!item.alternateText && (
-
-
- {item.alternateText}
-
-
+ textStyles={[alternateTextStyles, style]}
+ />
)}
{!!item.rightElement && item.rightElement}
diff --git a/src/components/SettlementButton.js b/src/components/SettlementButton.tsx
similarity index 66%
rename from src/components/SettlementButton.js
rename to src/components/SettlementButton.tsx
index 493b8767ee9b..058def7a34ad 100644
--- a/src/components/SettlementButton.js
+++ b/src/components/SettlementButton.tsx
@@ -1,143 +1,130 @@
-import PropTypes from 'prop-types';
import React, {useEffect, useMemo} from 'react';
+import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native';
+import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
-import compose from '@libs/compose';
import * as ReportUtils from '@libs/ReportUtils';
-import iouReportPropTypes from '@pages/iouReportPropTypes';
import * as BankAccounts from '@userActions/BankAccounts';
import * as IOU from '@userActions/IOU';
import * as PaymentMethods from '@userActions/PaymentMethods';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type {Route} from '@src/ROUTES';
+import type {ButtonSizeValue} from '@src/styles/utils/types';
+import type {LastPaymentMethod, Report} from '@src/types/onyx';
+import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
+import type AnchorAlignment from '@src/types/utils/AnchorAlignment';
+import type {EmptyObject} from '@src/types/utils/EmptyObject';
import ButtonWithDropdownMenu from './ButtonWithDropdownMenu';
import * as Expensicons from './Icon/Expensicons';
import KYCWall from './KYCWall';
-import withNavigation from './withNavigation';
-const propTypes = {
+type KYCFlowEvent = GestureResponderEvent | KeyboardEvent | undefined;
+
+type TriggerKYCFlow = (event: KYCFlowEvent, iouPaymentType: PaymentMethodType) => void;
+
+type EnablePaymentsRoute = typeof ROUTES.ENABLE_PAYMENTS | typeof ROUTES.IOU_SEND_ENABLE_PAYMENTS | typeof ROUTES.SETTINGS_ENABLE_PAYMENTS;
+
+type SettlementButtonOnyxProps = {
+ /** The last payment method used per policy */
+ nvpLastPaymentMethod?: OnyxEntry;
+};
+
+type SettlementButtonProps = SettlementButtonOnyxProps & {
/** Callback to execute when this button is pressed. Receives a single payment type argument. */
- onPress: PropTypes.func.isRequired,
+ onPress: (paymentType?: PaymentMethodType) => void;
+
+ /** The route to redirect if user does not have a payment method setup */
+ enablePaymentsRoute: EnablePaymentsRoute;
/** Call the onPress function on main button when Enter key is pressed */
- pressOnEnter: PropTypes.bool,
+ pressOnEnter?: boolean;
/** Settlement currency type */
- currency: PropTypes.string,
+ currency?: string;
/** When the button is opened via an IOU, ID for the chatReport that the IOU is linked to */
- chatReportID: PropTypes.string,
+ chatReportID?: string;
/** The IOU/Expense report we are paying */
- iouReport: iouReportPropTypes,
-
- /** The route to redirect if user does not have a payment method setup */
- enablePaymentsRoute: PropTypes.string.isRequired,
-
- /** Should we show the approve button? */
- shouldHidePaymentOptions: PropTypes.bool,
+ iouReport?: OnyxEntry | EmptyObject;
/** Should we show the payment options? */
- shouldShowApproveButton: PropTypes.bool,
+ shouldHidePaymentOptions?: boolean;
- /** The last payment method used per policy */
- nvp_lastPaymentMethod: PropTypes.objectOf(PropTypes.string),
+ /** Should we show the payment options? */
+ shouldShowApproveButton?: boolean;
/** The policyID of the report we are paying */
- policyID: PropTypes.string,
+ policyID?: string;
/** Additional styles to add to the component */
- style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]),
+ style?: StyleProp;
/** Total money amount in form */
- formattedAmount: PropTypes.string,
+ formattedAmount?: string;
/** The size of button size */
- buttonSize: PropTypes.oneOf(_.values(CONST.DROPDOWN_BUTTON_SIZE)),
+ buttonSize?: ButtonSizeValue;
/** Route for the Add Bank Account screen for a given navigation stack */
- addBankAccountRoute: PropTypes.string,
+ addBankAccountRoute?: Route;
/** Route for the Add Debit Card screen for a given navigation stack */
- addDebitCardRoute: PropTypes.string,
+ addDebitCardRoute?: Route;
/** Whether the button should be disabled */
- isDisabled: PropTypes.bool,
+ isDisabled?: boolean;
/** Whether we should show a loading state for the main button */
- isLoading: PropTypes.bool,
+ isLoading?: boolean;
/** The anchor alignment of the popover menu for payment method dropdown */
- paymentMethodDropdownAnchorAlignment: PropTypes.shape({
- horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)),
- vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)),
- }),
+ paymentMethodDropdownAnchorAlignment?: AnchorAlignment;
/** The anchor alignment of the popover menu for KYC wall popover */
- kycWallAnchorAlignment: PropTypes.shape({
- horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)),
- vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)),
- }),
+ kycWallAnchorAlignment?: AnchorAlignment;
/** Whether the personal bank account option should be shown */
- shouldShowPersonalBankAccountOption: PropTypes.bool,
-};
+ shouldShowPersonalBankAccountOption?: boolean;
-const defaultProps = {
- isLoading: false,
- isDisabled: false,
- pressOnEnter: false,
- addBankAccountRoute: '',
- addDebitCardRoute: '',
- currency: CONST.CURRENCY.USD,
- chatReportID: '',
+ /** The priority to assign the enter key event listener to buttons. 0 is the highest priority. */
+ enterKeyEventListenerPriority?: number;
+};
- // The "iouReport" and "nvp_lastPaymentMethod" objects needs to be stable to prevent the "useMemo"
- // hook from being recreated unnecessarily, hence the use of CONST.EMPTY_ARRAY and CONST.EMPTY_OBJECT
- iouReport: CONST.EMPTY_OBJECT,
- nvp_lastPaymentMethod: CONST.EMPTY_OBJECT,
- shouldHidePaymentOptions: false,
- shouldShowApproveButton: false,
- style: [],
- policyID: '',
- formattedAmount: '',
- buttonSize: CONST.DROPDOWN_BUTTON_SIZE.MEDIUM,
- kycWallAnchorAlignment: {
+function SettlementButton({
+ addDebitCardRoute = '',
+ addBankAccountRoute = '',
+ kycWallAnchorAlignment = {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, // button is at left, so horizontal anchor is at LEFT
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, // we assume that popover menu opens below the button, anchor is at TOP
},
- paymentMethodDropdownAnchorAlignment: {
+ paymentMethodDropdownAnchorAlignment = {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, // caret for dropdown is at right, so horizontal anchor is at RIGHT
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, // we assume that popover menu opens below the button, anchor is at TOP
},
- shouldShowPersonalBankAccountOption: false,
-};
-
-function SettlementButton({
- addDebitCardRoute,
- addBankAccountRoute,
- kycWallAnchorAlignment,
- paymentMethodDropdownAnchorAlignment,
- buttonSize,
- chatReportID,
- currency,
+ buttonSize = CONST.DROPDOWN_BUTTON_SIZE.MEDIUM,
+ chatReportID = '',
+ currency = CONST.CURRENCY.USD,
enablePaymentsRoute,
- iouReport,
- isDisabled,
- isLoading,
- formattedAmount,
- nvp_lastPaymentMethod,
+ // The "iouReport" and "nvpLastPaymentMethod" objects needs to be stable to prevent the "useMemo"
+ // hook from being recreated unnecessarily, hence the use of CONST.EMPTY_ARRAY and CONST.EMPTY_OBJECT
+ iouReport = CONST.EMPTY_OBJECT,
+ nvpLastPaymentMethod = CONST.EMPTY_OBJECT,
+ isDisabled = false,
+ isLoading = false,
+ formattedAmount = '',
onPress,
- pressOnEnter,
- policyID,
- shouldHidePaymentOptions,
- shouldShowApproveButton,
+ pressOnEnter = false,
+ policyID = '',
+ shouldHidePaymentOptions = false,
+ shouldShowApproveButton = false,
style,
- shouldShowPersonalBankAccountOption,
-}) {
+ shouldShowPersonalBankAccountOption = false,
+ enterKeyEventListenerPriority = 0,
+}: SettlementButtonProps) {
const {translate} = useLocalize();
const {isOffline} = useNetwork();
@@ -180,7 +167,7 @@ function SettlementButton({
// To achieve the one tap pay experience we need to choose the correct payment type as default.
// If the user has previously chosen a specific payment option or paid for some request or expense,
// let's use the last payment method or use default.
- const paymentMethod = nvp_lastPaymentMethod[policyID] || '';
+ const paymentMethod = nvpLastPaymentMethod?.[policyID] ?? '';
if (canUseWallet) {
buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.EXPENSIFY]);
}
@@ -195,14 +182,14 @@ function SettlementButton({
// Put the preferred payment method to the front of the array, so it's shown as default
if (paymentMethod) {
- return _.sortBy(buttonOptions, (method) => (method.value === paymentMethod ? 0 : 1));
+ return buttonOptions.sort((method) => (method.value === paymentMethod ? 0 : 1));
}
return buttonOptions;
// We don't want to reorder the options when the preferred payment method changes while the button is still visible
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currency, formattedAmount, iouReport, policyID, translate, shouldHidePaymentOptions, shouldShowApproveButton]);
- const selectPaymentType = (event, iouPaymentType, triggerKYCFlow) => {
+ const selectPaymentType = (event: KYCFlowEvent, iouPaymentType: PaymentMethodType, triggerKYCFlow: TriggerKYCFlow) => {
if (iouPaymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY || iouPaymentType === CONST.IOU.PAYMENT_TYPE.VBBA) {
triggerKYCFlow(event, iouPaymentType);
BankAccounts.setPersonalBankAccountContinueKYCOnSuccess(ROUTES.ENABLE_PAYMENTS);
@@ -210,13 +197,17 @@ function SettlementButton({
}
if (iouPaymentType === CONST.IOU.REPORT_ACTION_TYPE.APPROVE) {
- IOU.approveMoneyRequest(iouReport);
+ IOU.approveMoneyRequest(iouReport ?? {});
return;
}
onPress(iouPaymentType);
};
+ const savePreferredPaymentMethod = (id: string, value: PaymentMethodType) => {
+ IOU.savePreferredPaymentMethod(id, value);
+ };
+
return (
selectPaymentType(event, iouPaymentType, triggerKYCFlow)}
pressOnEnter={pressOnEnter}
options={paymentButtonOptions}
- onOptionSelected={(option) => IOU.savePreferredPaymentMethod(policyID, option.value)}
+ onOptionSelected={(option) => savePreferredPaymentMethod(policyID, option.value)}
style={style}
buttonSize={buttonSize}
anchorAlignment={paymentMethodDropdownAnchorAlignment}
+ enterKeyEventListenerPriority={enterKeyEventListenerPriority}
/>
)}
);
}
-SettlementButton.propTypes = propTypes;
-SettlementButton.defaultProps = defaultProps;
SettlementButton.displayName = 'SettlementButton';
-export default compose(
- withNavigation,
- withOnyx({
- nvp_lastPaymentMethod: {
- key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
- },
- }),
-)(SettlementButton);
+export default withOnyx({
+ nvpLastPaymentMethod: {
+ key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
+ selector: (paymentMethod) => paymentMethod ?? {},
+ },
+})(SettlementButton);
diff --git a/src/components/TextInput/BaseTextInput/index.native.tsx b/src/components/TextInput/BaseTextInput/index.native.tsx
index c9b46ac0b6cb..6f00f06c4c5e 100644
--- a/src/components/TextInput/BaseTextInput/index.native.tsx
+++ b/src/components/TextInput/BaseTextInput/index.native.tsx
@@ -249,6 +249,8 @@ function BaseTextInput(
const hasLabel = Boolean(label?.length);
const isReadOnly = inputProps.readOnly ?? inputProps.disabled;
+ // Disabling this line for safeness as nullish coalescing works only if the value is undefined or null, and errorText can be an empty string
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const inputHelpText = errorText || hint;
const placeholderValue = !!prefixCharacter || isFocused || !hasLabel || (hasLabel && forceActiveLabel) ? placeholder : undefined;
const maxHeight = StyleSheet.flatten(containerStyles)?.maxHeight;
diff --git a/src/components/TextInput/BaseTextInput/index.tsx b/src/components/TextInput/BaseTextInput/index.tsx
index 90412a37d7a0..800eff15d242 100644
--- a/src/components/TextInput/BaseTextInput/index.tsx
+++ b/src/components/TextInput/BaseTextInput/index.tsx
@@ -244,6 +244,8 @@ function BaseTextInput(
const hasLabel = Boolean(label?.length);
const isReadOnly = inputProps.readOnly ?? inputProps.disabled;
+ // Disabling this line for safeness as nullish coalescing works only if the value is undefined or null, and errorText can be an empty string
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const inputHelpText = errorText || hint;
const newPlaceholder = !!prefixCharacter || isFocused || !hasLabel || (hasLabel && forceActiveLabel) ? placeholder : undefined;
const maxHeight = StyleSheet.flatten(containerStyles).maxHeight;
diff --git a/src/components/TextWithTooltip/index.native.tsx b/src/components/TextWithTooltip/index.native.tsx
new file mode 100644
index 000000000000..f8013ae00e4c
--- /dev/null
+++ b/src/components/TextWithTooltip/index.native.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import Text from '@components/Text';
+import type TextWithTooltipProps from './types';
+
+function TextWithTooltip({text, textStyles}: TextWithTooltipProps) {
+ return (
+
+ {text}
+
+ );
+}
+
+TextWithTooltip.displayName = 'TextWithTooltip';
+
+export default TextWithTooltip;
diff --git a/src/components/TextWithTooltip/index.tsx b/src/components/TextWithTooltip/index.tsx
new file mode 100644
index 000000000000..fd202db8de64
--- /dev/null
+++ b/src/components/TextWithTooltip/index.tsx
@@ -0,0 +1,41 @@
+import React, {useState} from 'react';
+import Text from '@components/Text';
+import Tooltip from '@components/Tooltip';
+import type TextWithTooltipProps from './types';
+
+type LayoutChangeEvent = {
+ target: HTMLElement;
+};
+
+function TextWithTooltip({text, shouldShowTooltip, textStyles}: TextWithTooltipProps) {
+ const [showTooltip, setShowTooltip] = useState(false);
+
+ return (
+
+ {
+ const target = (e.nativeEvent as unknown as LayoutChangeEvent).target;
+ if (!shouldShowTooltip) {
+ return;
+ }
+ if (target.scrollWidth > target.offsetWidth) {
+ setShowTooltip(true);
+ return;
+ }
+ setShowTooltip(false);
+ }}
+ >
+ {text}
+
+
+ );
+}
+
+TextWithTooltip.displayName = 'TextWithTooltip';
+
+export default TextWithTooltip;
diff --git a/src/components/TextWithTooltip/types.ts b/src/components/TextWithTooltip/types.ts
new file mode 100644
index 000000000000..80517b0b2acf
--- /dev/null
+++ b/src/components/TextWithTooltip/types.ts
@@ -0,0 +1,9 @@
+import type {StyleProp, TextStyle} from 'react-native';
+
+type TextWithTooltipProps = {
+ text: string;
+ shouldShowTooltip: boolean;
+ textStyles?: StyleProp;
+};
+
+export default TextWithTooltipProps;
diff --git a/src/components/ThreeDotsMenu/index.tsx b/src/components/ThreeDotsMenu/index.tsx
index 7384874a2746..5de074afff75 100644
--- a/src/components/ThreeDotsMenu/index.tsx
+++ b/src/components/ThreeDotsMenu/index.tsx
@@ -3,7 +3,6 @@ import type {StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
-import type {AnchorAlignment} from '@components/Popover/types';
import type {PopoverMenuItem} from '@components/PopoverMenu';
import PopoverMenu from '@components/PopoverMenu';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
@@ -15,6 +14,7 @@ import * as Browser from '@libs/Browser';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import type {AnchorPosition} from '@src/styles';
+import type AnchorAlignment from '@src/types/utils/AnchorAlignment';
import type IconAsset from '@src/types/utils/IconAsset';
type ThreeDotsMenuProps = {
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 145d8414c1e3..860c38358b89 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -489,7 +489,7 @@ export default {
chatWithAccountManager: 'Chat with your account manager here',
sayHello: 'Say hello!',
welcomeToRoom: ({roomName}: WelcomeToRoomParams) => `Welcome to ${roomName}!`,
- usePlusButton: ({additionalText}: UsePlusButtonParams) => `\n\nYou can also use the + button to ${additionalText}, or assign a task!`,
+ usePlusButton: ({additionalText}: UsePlusButtonParams) => `\nYou can also use the + button to ${additionalText}, or assign a task!`,
iouTypes: {
send: 'send money',
split: 'split a bill',
@@ -679,6 +679,7 @@ export default {
always: 'Immediately',
daily: 'Daily',
mute: 'Mute',
+ hidden: 'Hidden',
},
},
loginField: {
@@ -1053,10 +1054,10 @@ export default {
},
},
},
- welcomeMessagePage: {
- welcomeMessage: 'Welcome message',
- welcomeMessageOptional: 'Welcome message (optional)',
- explainerText: 'Set a custom welcome message that will be sent to users when they join this room.',
+ reportDescriptionPage: {
+ roomDescription: 'Room description',
+ roomDescriptionOptional: 'Room description (optional)',
+ explainerText: 'Set a custom decription for the room.',
},
languagePage: {
language: 'Language',
@@ -1969,7 +1970,7 @@ export default {
replies: 'Replies',
reply: 'Reply',
from: 'From',
- in: 'In',
+ in: 'in',
parentNavigationSummary: ({rootReportName, workspaceName}: ParentNavigationSummaryParams) => `From ${rootReportName}${workspaceName ? ` in ${workspaceName}` : ''}`,
},
qrCodes: {
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 517b3ba1e2f9..0dedf3dcdd34 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -481,7 +481,7 @@ export default {
chatWithAccountManager: 'Chatea con tu gestor de cuenta aquí',
sayHello: '¡Saluda!',
welcomeToRoom: ({roomName}: WelcomeToRoomParams) => `¡Bienvenido a ${roomName}!`,
- usePlusButton: ({additionalText}: UsePlusButtonParams) => `\n\n¡También puedes usar el botón + de abajo para ${additionalText}, o asignar una tarea!`,
+ usePlusButton: ({additionalText}: UsePlusButtonParams) => `\n¡También puedes usar el botón + de abajo para ${additionalText}, o asignar una tarea!`,
iouTypes: {
send: 'enviar dinero',
split: 'dividir una factura',
@@ -673,6 +673,7 @@ export default {
always: 'Inmediatamente',
daily: 'Cada día',
mute: 'Nunca',
+ hidden: 'Oculto',
},
},
loginField: {
@@ -1051,10 +1052,10 @@ export default {
},
},
},
- welcomeMessagePage: {
- welcomeMessage: 'Mensaje de bienvenida',
- welcomeMessageOptional: 'Mensaje de bienvenida (opcional)',
- explainerText: 'Configura un mensaje de bienvenida privado y personalizado que se enviará cuando los usuarios se unan a esta sala de chat.',
+ reportDescriptionPage: {
+ roomDescription: 'Descripción de la sala de chat',
+ roomDescriptionOptional: 'Descripción de la sala de chat (opcional)',
+ explainerText: 'Establece una descripción personalizada para la sala de chat.',
},
languagePage: {
language: 'Idioma',
diff --git a/src/libs/API/parameters/AddWorkspaceRoomParams.ts b/src/libs/API/parameters/AddWorkspaceRoomParams.ts
index f7cbff9565ef..847324586cb5 100644
--- a/src/libs/API/parameters/AddWorkspaceRoomParams.ts
+++ b/src/libs/API/parameters/AddWorkspaceRoomParams.ts
@@ -9,7 +9,7 @@ type AddWorkspaceRoomParams = {
reportName?: string;
visibility?: ValueOf;
writeCapability?: WriteCapability;
- welcomeMessage?: string;
+ description?: string;
};
export default AddWorkspaceRoomParams;
diff --git a/src/libs/API/parameters/ApproveMoneyRequestParams.ts b/src/libs/API/parameters/ApproveMoneyRequestParams.ts
new file mode 100644
index 000000000000..f35ff31702d6
--- /dev/null
+++ b/src/libs/API/parameters/ApproveMoneyRequestParams.ts
@@ -0,0 +1,6 @@
+type ApproveMoneyRequestParams = {
+ reportID: string;
+ approvedReportActionID: string;
+};
+
+export default ApproveMoneyRequestParams;
diff --git a/src/libs/API/parameters/CompleteSplitBillParams.ts b/src/libs/API/parameters/CompleteSplitBillParams.ts
new file mode 100644
index 000000000000..50054ba6fd10
--- /dev/null
+++ b/src/libs/API/parameters/CompleteSplitBillParams.ts
@@ -0,0 +1,13 @@
+type CompleteSplitBillParams = {
+ transactionID: string;
+ amount?: number;
+ currency?: string;
+ created?: string;
+ merchant?: string;
+ comment?: string;
+ category?: string;
+ tag?: string;
+ splits: string;
+};
+
+export default CompleteSplitBillParams;
diff --git a/src/libs/API/parameters/CreateDistanceRequestParams.ts b/src/libs/API/parameters/CreateDistanceRequestParams.ts
new file mode 100644
index 000000000000..c1eb1003a698
--- /dev/null
+++ b/src/libs/API/parameters/CreateDistanceRequestParams.ts
@@ -0,0 +1,17 @@
+type CreateDistanceRequestParams = {
+ comment: string;
+ iouReportID: string;
+ chatReportID: string;
+ transactionID: string;
+ reportActionID: string;
+ createdChatReportActionID: string;
+ createdIOUReportActionID: string;
+ reportPreviewReportActionID: string;
+ waypoints: string;
+ created: string;
+ category?: string;
+ tag?: string;
+ billable?: boolean;
+};
+
+export default CreateDistanceRequestParams;
diff --git a/src/libs/API/parameters/DeleteMoneyRequestParams.ts b/src/libs/API/parameters/DeleteMoneyRequestParams.ts
new file mode 100644
index 000000000000..6e7fe30811c4
--- /dev/null
+++ b/src/libs/API/parameters/DeleteMoneyRequestParams.ts
@@ -0,0 +1,6 @@
+type DeleteMoneyRequestParams = {
+ transactionID: string;
+ reportActionID: string;
+};
+
+export default DeleteMoneyRequestParams;
diff --git a/src/libs/API/parameters/DetachReceiptParams.ts b/src/libs/API/parameters/DetachReceiptParams.ts
new file mode 100644
index 000000000000..a19b525a0ef0
--- /dev/null
+++ b/src/libs/API/parameters/DetachReceiptParams.ts
@@ -0,0 +1,5 @@
+type DetachReceiptParams = {
+ transactionID: string;
+};
+
+export default DetachReceiptParams;
diff --git a/src/libs/API/parameters/EditMoneyRequestParams.ts b/src/libs/API/parameters/EditMoneyRequestParams.ts
new file mode 100644
index 000000000000..6d320510e267
--- /dev/null
+++ b/src/libs/API/parameters/EditMoneyRequestParams.ts
@@ -0,0 +1,14 @@
+type EditMoneyRequestParams = {
+ transactionID: string;
+ reportActionID: string;
+ created?: string;
+ amount?: number;
+ currency?: string;
+ comment?: string;
+ merchant?: string;
+ category?: string;
+ billable?: boolean;
+ tag?: string;
+};
+
+export default EditMoneyRequestParams;
diff --git a/src/libs/API/parameters/PayMoneyRequestParams.ts b/src/libs/API/parameters/PayMoneyRequestParams.ts
new file mode 100644
index 000000000000..edf05b6ce528
--- /dev/null
+++ b/src/libs/API/parameters/PayMoneyRequestParams.ts
@@ -0,0 +1,10 @@
+import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
+
+type PayMoneyRequestParams = {
+ iouReportID: string;
+ chatReportID: string;
+ reportActionID: string;
+ paymentMethodType: PaymentMethodType;
+};
+
+export default PayMoneyRequestParams;
diff --git a/src/libs/API/parameters/ReplaceReceiptParams.ts b/src/libs/API/parameters/ReplaceReceiptParams.ts
new file mode 100644
index 000000000000..1046c46bc930
--- /dev/null
+++ b/src/libs/API/parameters/ReplaceReceiptParams.ts
@@ -0,0 +1,6 @@
+type ReplaceReceiptParams = {
+ transactionID: string;
+ receipt: File;
+};
+
+export default ReplaceReceiptParams;
diff --git a/src/libs/API/parameters/RequestMoneyParams.ts b/src/libs/API/parameters/RequestMoneyParams.ts
new file mode 100644
index 000000000000..ccafdd692137
--- /dev/null
+++ b/src/libs/API/parameters/RequestMoneyParams.ts
@@ -0,0 +1,29 @@
+import type {ValueOf} from 'type-fest';
+import type CONST from '@src/CONST';
+import type {Receipt} from '@src/types/onyx/Transaction';
+
+type RequestMoneyParams = {
+ debtorEmail: string;
+ debtorAccountID: number;
+ amount: number;
+ currency: string;
+ comment: string;
+ created: string;
+ merchant: string;
+ iouReportID: string;
+ chatReportID: string;
+ transactionID: string;
+ reportActionID: string;
+ createdChatReportActionID: string;
+ createdIOUReportActionID: string;
+ reportPreviewReportActionID: string;
+ receipt: Receipt;
+ receiptState?: ValueOf;
+ category?: string;
+ tag?: string;
+ taxCode: string;
+ taxAmount: number;
+ billable?: boolean;
+};
+
+export default RequestMoneyParams;
diff --git a/src/libs/API/parameters/SendMoneyParams.ts b/src/libs/API/parameters/SendMoneyParams.ts
new file mode 100644
index 000000000000..b737ba2ea48b
--- /dev/null
+++ b/src/libs/API/parameters/SendMoneyParams.ts
@@ -0,0 +1,14 @@
+import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
+
+type SendMoneyParams = {
+ iouReportID: string;
+ chatReportID: string;
+ reportActionID: string;
+ paymentMethodType: PaymentMethodType;
+ transactionID: string;
+ newIOUReportDetails: string;
+ createdReportActionID: string;
+ reportPreviewReportActionID: string;
+};
+
+export default SendMoneyParams;
diff --git a/src/libs/API/parameters/SplitBillParams.ts b/src/libs/API/parameters/SplitBillParams.ts
new file mode 100644
index 000000000000..23d4ff3d6d78
--- /dev/null
+++ b/src/libs/API/parameters/SplitBillParams.ts
@@ -0,0 +1,17 @@
+type SplitBillParams = {
+ reportID: string;
+ amount: number;
+ splits: string;
+ comment: string;
+ currency: string;
+ merchant: string;
+ category: string;
+ tag: string;
+ billable: boolean;
+ transactionID: string;
+ reportActionID: string;
+ createdReportActionID?: string;
+ policyID?: string;
+};
+
+export default SplitBillParams;
diff --git a/src/libs/API/parameters/StartSplitBillParams.ts b/src/libs/API/parameters/StartSplitBillParams.ts
new file mode 100644
index 000000000000..30d21697ac67
--- /dev/null
+++ b/src/libs/API/parameters/StartSplitBillParams.ts
@@ -0,0 +1,17 @@
+import type {Receipt} from '@src/types/onyx/Transaction';
+
+type StartSplitBillParams = {
+ chatReportID: string;
+ reportActionID: string;
+ transactionID: string;
+ splits: string;
+ receipt: Receipt;
+ comment: string;
+ category: string;
+ tag: string;
+ isFromGroupDM: boolean;
+ createdReportActionID?: string;
+ billable: boolean;
+};
+
+export default StartSplitBillParams;
diff --git a/src/libs/API/parameters/SubmitReportParams.ts b/src/libs/API/parameters/SubmitReportParams.ts
new file mode 100644
index 000000000000..0132344aab6c
--- /dev/null
+++ b/src/libs/API/parameters/SubmitReportParams.ts
@@ -0,0 +1,7 @@
+type SubmitReportParams = {
+ reportID: string;
+ managerAccountID?: number;
+ reportActionID: string;
+};
+
+export default SubmitReportParams;
diff --git a/src/libs/API/parameters/UpdateMoneyRequestParams.ts b/src/libs/API/parameters/UpdateMoneyRequestParams.ts
new file mode 100644
index 000000000000..549d02260beb
--- /dev/null
+++ b/src/libs/API/parameters/UpdateMoneyRequestParams.ts
@@ -0,0 +1,9 @@
+import type {TransactionDetails} from '@libs/ReportUtils';
+
+type UpdateMoneyRequestParams = Partial & {
+ reportID?: string;
+ transactionID: string;
+ reportActionID?: string;
+};
+
+export default UpdateMoneyRequestParams;
diff --git a/src/libs/API/parameters/UpdateRoomDescriptionParams.ts b/src/libs/API/parameters/UpdateRoomDescriptionParams.ts
new file mode 100644
index 000000000000..4d78d07a9189
--- /dev/null
+++ b/src/libs/API/parameters/UpdateRoomDescriptionParams.ts
@@ -0,0 +1,6 @@
+type UpdateRoomDescriptionParams = {
+ reportID: string;
+ description: string;
+};
+
+export default UpdateRoomDescriptionParams;
diff --git a/src/libs/API/parameters/UpdateWelcomeMessageParams.ts b/src/libs/API/parameters/UpdateWelcomeMessageParams.ts
deleted file mode 100644
index a2d3b59fe3fa..000000000000
--- a/src/libs/API/parameters/UpdateWelcomeMessageParams.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-type UpdateWelcomeMessageParams = {
- reportID: string;
- welcomeMessage: string;
-};
-
-export default UpdateWelcomeMessageParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index b7c3dff7c342..4d784463c2f8 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -84,7 +84,7 @@ export type {default as TogglePinnedChatParams} from './TogglePinnedChatParams';
export type {default as DeleteCommentParams} from './DeleteCommentParams';
export type {default as UpdateCommentParams} from './UpdateCommentParams';
export type {default as UpdateReportNotificationPreferenceParams} from './UpdateReportNotificationPreferenceParams';
-export type {default as UpdateWelcomeMessageParams} from './UpdateWelcomeMessageParams';
+export type {default as UpdateRoomDescriptionParams} from './UpdateRoomDescriptionParams';
export type {default as UpdateReportWriteCapabilityParams} from './UpdateReportWriteCapabilityParams';
export type {default as AddWorkspaceRoomParams} from './AddWorkspaceRoomParams';
export type {default as UpdatePolicyRoomNameParams} from './UpdatePolicyRoomNameParams';
@@ -125,3 +125,17 @@ export type {default as CompleteEngagementModalParams} from './CompleteEngagemen
export type {default as SetNameValuePairParams} from './SetNameValuePairParams';
export type {default as SetReportFieldParams} from './SetReportFieldParams';
export type {default as SetReportNameParams} from './SetReportNameParams';
+export type {default as CompleteSplitBillParams} from './CompleteSplitBillParams';
+export type {default as UpdateMoneyRequestParams} from './UpdateMoneyRequestParams';
+export type {default as RequestMoneyParams} from './RequestMoneyParams';
+export type {default as SplitBillParams} from './SplitBillParams';
+export type {default as DeleteMoneyRequestParams} from './DeleteMoneyRequestParams';
+export type {default as CreateDistanceRequestParams} from './CreateDistanceRequestParams';
+export type {default as StartSplitBillParams} from './StartSplitBillParams';
+export type {default as SendMoneyParams} from './SendMoneyParams';
+export type {default as ApproveMoneyRequestParams} from './ApproveMoneyRequestParams';
+export type {default as EditMoneyRequestParams} from './EditMoneyRequestParams';
+export type {default as ReplaceReceiptParams} from './ReplaceReceiptParams';
+export type {default as SubmitReportParams} from './SubmitReportParams';
+export type {default as DetachReceiptParams} from './DetachReceiptParams';
+export type {default as PayMoneyRequestParams} from './PayMoneyRequestParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index c011fa395f0f..a4ab3db9a7cd 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -85,7 +85,7 @@ const WRITE_COMMANDS = {
DELETE_COMMENT: 'DeleteComment',
UPDATE_COMMENT: 'UpdateComment',
UPDATE_REPORT_NOTIFICATION_PREFERENCE: 'UpdateReportNotificationPreference',
- UPDATE_WELCOME_MESSAGE: 'UpdateWelcomeMessage',
+ UPDATE_ROOM_DESCRIPTION: 'UpdateRoomDescription',
UPDATE_REPORT_WRITE_CAPABILITY: 'UpdateReportWriteCapability',
ADD_WORKSPACE_ROOM: 'AddWorkspaceRoom',
UPDATE_POLICY_ROOM_NAME: 'UpdatePolicyRoomName',
@@ -116,6 +116,31 @@ const WRITE_COMMANDS = {
SET_NAME_VALUE_PAIR: 'SetNameValuePair',
SET_REPORT_FIELD: 'Report_SetFields',
SET_REPORT_NAME: 'RenameReport',
+ COMPLETE_SPLIT_BILL: 'CompleteSplitBill',
+ UPDATE_MONEY_REQUEST_DATE: 'UpdateMoneyRequestDate',
+ UPDATE_MONEY_REQUEST_BILLABLE: 'UpdateMoneyRequestBillable',
+ UPDATE_MONEY_REQUEST_MERCHANT: 'UpdateMoneyRequestMerchant',
+ UPDATE_MONEY_REQUEST_TAG: 'UpdateMoneyRequestTag',
+ UPDATE_MONEY_REQUEST_DISTANCE: 'UpdateMoneyRequestDistance',
+ UPDATE_MONEY_REQUEST_CATEGORY: 'UpdateMoneyRequestCategory',
+ UPDATE_MONEY_REQUEST_DESCRIPTION: 'UpdateMoneyRequestDescription',
+ UPDATE_MONEY_REQUEST_AMOUNT_AND_CURRENCY: 'UpdateMoneyRequestAmountAndCurrency',
+ UPDATE_DISTANCE_REQUEST: 'UpdateDistanceRequest',
+ REQUEST_MONEY: 'RequestMoney',
+ SPLIT_BILL: 'SplitBill',
+ SPLIT_BILL_AND_OPEN_REPORT: 'SplitBillAndOpenReport',
+ DELETE_MONEY_REQUEST: 'DeleteMoneyRequest',
+ CREATE_DISTANCE_REQUEST: 'CreateDistanceRequest',
+ START_SPLIT_BILL: 'StartSplitBill',
+ SEND_MONEY_ELSEWHERE: 'SendMoneyElsewhere',
+ SEND_MONEY_WITH_WALLET: 'SendMoneyWithWallet',
+ APPROVE_MONEY_REQUEST: 'ApproveMoneyRequest',
+ EDIT_MONEY_REQUEST: 'EditMoneyRequest',
+ REPLACE_RECEIPT: 'ReplaceReceipt',
+ SUBMIT_REPORT: 'SubmitReport',
+ DETACH_RECEIPT: 'DetachReceipt',
+ PAY_MONEY_REQUEST_WITH_WALLET: 'PayMoneyRequestWithWallet',
+ PAY_MONEY_REQUEST: 'PayMoneyRequest',
} as const;
type WriteCommand = ValueOf;
@@ -196,7 +221,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.DELETE_COMMENT]: Parameters.DeleteCommentParams;
[WRITE_COMMANDS.UPDATE_COMMENT]: Parameters.UpdateCommentParams;
[WRITE_COMMANDS.UPDATE_REPORT_NOTIFICATION_PREFERENCE]: Parameters.UpdateReportNotificationPreferenceParams;
- [WRITE_COMMANDS.UPDATE_WELCOME_MESSAGE]: Parameters.UpdateWelcomeMessageParams;
+ [WRITE_COMMANDS.UPDATE_ROOM_DESCRIPTION]: Parameters.UpdateRoomDescriptionParams;
[WRITE_COMMANDS.UPDATE_REPORT_WRITE_CAPABILITY]: Parameters.UpdateReportWriteCapabilityParams;
[WRITE_COMMANDS.ADD_WORKSPACE_ROOM]: Parameters.AddWorkspaceRoomParams;
[WRITE_COMMANDS.UPDATE_POLICY_ROOM_NAME]: Parameters.UpdatePolicyRoomNameParams;
@@ -229,6 +254,31 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.SET_NAME_VALUE_PAIR]: Parameters.SetNameValuePairParams;
[WRITE_COMMANDS.SET_REPORT_FIELD]: Parameters.SetReportFieldParams;
[WRITE_COMMANDS.SET_REPORT_NAME]: Parameters.SetReportNameParams;
+ [WRITE_COMMANDS.COMPLETE_SPLIT_BILL]: Parameters.CompleteSplitBillParams;
+ [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DATE]: Parameters.UpdateMoneyRequestParams;
+ [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_MERCHANT]: Parameters.UpdateMoneyRequestParams;
+ [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_BILLABLE]: Parameters.UpdateMoneyRequestParams;
+ [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_TAG]: Parameters.UpdateMoneyRequestParams;
+ [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DISTANCE]: Parameters.UpdateMoneyRequestParams;
+ [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_CATEGORY]: Parameters.UpdateMoneyRequestParams;
+ [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DESCRIPTION]: Parameters.UpdateMoneyRequestParams;
+ [WRITE_COMMANDS.UPDATE_DISTANCE_REQUEST]: Parameters.UpdateMoneyRequestParams;
+ [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_AMOUNT_AND_CURRENCY]: Parameters.UpdateMoneyRequestParams;
+ [WRITE_COMMANDS.REQUEST_MONEY]: Parameters.RequestMoneyParams;
+ [WRITE_COMMANDS.SPLIT_BILL]: Parameters.SplitBillParams;
+ [WRITE_COMMANDS.SPLIT_BILL_AND_OPEN_REPORT]: Parameters.SplitBillParams;
+ [WRITE_COMMANDS.DELETE_MONEY_REQUEST]: Parameters.DeleteMoneyRequestParams;
+ [WRITE_COMMANDS.CREATE_DISTANCE_REQUEST]: Parameters.CreateDistanceRequestParams;
+ [WRITE_COMMANDS.START_SPLIT_BILL]: Parameters.StartSplitBillParams;
+ [WRITE_COMMANDS.SEND_MONEY_ELSEWHERE]: Parameters.SendMoneyParams;
+ [WRITE_COMMANDS.SEND_MONEY_WITH_WALLET]: Parameters.SendMoneyParams;
+ [WRITE_COMMANDS.APPROVE_MONEY_REQUEST]: Parameters.ApproveMoneyRequestParams;
+ [WRITE_COMMANDS.EDIT_MONEY_REQUEST]: Parameters.EditMoneyRequestParams;
+ [WRITE_COMMANDS.REPLACE_RECEIPT]: Parameters.ReplaceReceiptParams;
+ [WRITE_COMMANDS.SUBMIT_REPORT]: Parameters.SubmitReportParams;
+ [WRITE_COMMANDS.DETACH_RECEIPT]: Parameters.DetachReceiptParams;
+ [WRITE_COMMANDS.PAY_MONEY_REQUEST_WITH_WALLET]: Parameters.PayMoneyRequestParams;
+ [WRITE_COMMANDS.PAY_MONEY_REQUEST]: Parameters.PayMoneyRequestParams;
};
const READ_COMMANDS = {
diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts
index fe79ea68a0b3..730f2a5d3295 100644
--- a/src/libs/EmojiUtils.ts
+++ b/src/libs/EmojiUtils.ts
@@ -548,6 +548,11 @@ const getEmojiReactionDetails = (emojiName: string, reaction: ReportActionReacti
};
};
+/**
+ * Given an emoji code, returns an base emoji code without skin tone
+ */
+const getRemovedSkinToneEmoji = (emoji: string) => emoji.replace(CONST.REGEX.EMOJI_SKIN_TONES, '');
+
function getSpacersIndexes(allEmojis: EmojiPickerList): number[] {
const spacersIndexes: number[] = [];
allEmojis.forEach((emoji, index) => {
@@ -582,5 +587,6 @@ export {
getAddedEmojis,
isFirstLetterEmoji,
hasAccountIDEmojiReacted,
+ getRemovedSkinToneEmoji,
getSpacersIndexes,
};
diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts
index edc24bf94720..6fbba1e750dc 100644
--- a/src/libs/ErrorUtils.ts
+++ b/src/libs/ErrorUtils.ts
@@ -38,7 +38,7 @@ function getAuthenticateErrorMessage(response: Response): keyof TranslationFlatO
* Method used to get an error object with microsecond as the key.
* @param error - error key or message to be saved
*/
-function getMicroSecondOnyxError(error: string): Errors {
+function getMicroSecondOnyxError(error: string | null): Errors {
return {[DateUtils.getMicroseconds()]: error};
}
@@ -46,7 +46,7 @@ function getMicroSecondOnyxError(error: string): Errors {
* Method used to get an error object with microsecond as the key and an object as the value.
* @param error - error key or message to be saved
*/
-function getMicroSecondOnyxErrorObject(error: Record): Record> {
+function getMicroSecondOnyxErrorObject(error: Errors): ErrorFields {
return {[DateUtils.getMicroseconds()]: error};
}
@@ -54,7 +54,7 @@ type OnyxDataWithErrors = {
errors?: Errors | null;
};
-function getLatestErrorMessage(onyxData: TOnyxData): string {
+function getLatestErrorMessage(onyxData: TOnyxData): string | null {
const errors = onyxData.errors ?? {};
if (Object.keys(errors).length === 0) {
@@ -66,7 +66,7 @@ function getLatestErrorMessage(onyxData: T
return errors[key];
}
-function getLatestErrorMessageField(onyxData: TOnyxData): Record {
+function getLatestErrorMessageField(onyxData: TOnyxData): Errors {
const errors = onyxData.errors ?? {};
if (Object.keys(errors).length === 0) {
@@ -82,7 +82,7 @@ type OnyxDataWithErrorFields = {
errorFields?: ErrorFields;
};
-function getLatestErrorField(onyxData: TOnyxData, fieldName: string): Record {
+function getLatestErrorField(onyxData: TOnyxData, fieldName: string): Errors {
const errorsForField = onyxData.errorFields?.[fieldName] ?? {};
if (Object.keys(errorsForField).length === 0) {
@@ -94,7 +94,7 @@ function getLatestErrorField(onyxData
return {[key]: errorsForField[key]};
}
-function getEarliestErrorField(onyxData: TOnyxData, fieldName: string): Record {
+function getEarliestErrorField(onyxData: TOnyxData, fieldName: string): Errors {
const errorsForField = onyxData.errorFields?.[fieldName] ?? {};
if (Object.keys(errorsForField).length === 0) {
@@ -113,7 +113,7 @@ type ErrorsList = Record;
* @param errors - An object containing current errors in the form
* @param message - Message to assign to the inputID errors
*/
-function addErrorMessage(errors: ErrorsList, inputID?: string, message?: TKey) {
+function addErrorMessage(errors: ErrorsList, inputID?: string, message?: TKey | Localize.MaybePhraseKey) {
if (!message || !inputID) {
return;
}
diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts
index c0fb4c6195b1..d70f4fc08102 100644
--- a/src/libs/IOUUtils.ts
+++ b/src/libs/IOUUtils.ts
@@ -54,13 +54,13 @@ function calculateAmount(numberOfParticipants: number, total: number, currency:
*
* @param isDeleting - whether the user is deleting the request
*/
-function updateIOUOwnerAndTotal(iouReport: OnyxEntry, actorAccountID: number, amount: number, currency: string, isDeleting = false): OnyxEntry {
+function updateIOUOwnerAndTotal>(iouReport: TReport, actorAccountID: number, amount: number, currency: string, isDeleting = false): TReport {
if (currency !== iouReport?.currency) {
return iouReport;
}
// Make a copy so we don't mutate the original object
- const iouReportUpdate: Report = {...iouReport};
+ const iouReportUpdate = {...iouReport};
// Let us ensure a valid value before updating the total amount.
iouReportUpdate.total = iouReportUpdate.total ?? 0;
diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts
index 46ca550eaa1a..0df9e25eff25 100644
--- a/src/libs/Localize/index.ts
+++ b/src/libs/Localize/index.ts
@@ -98,7 +98,7 @@ function translateLocal(phrase: TKey, ...variable
return translate(BaseLocaleListener.getPreferredLocale(), phrase, ...variables);
}
-type MaybePhraseKey = string | [string, Record & {isTranslated?: true}] | [];
+type MaybePhraseKey = string | null | [string, Record & {isTranslated?: true}] | [];
/**
* Return translated string for given error.
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
index 4606f867c3fc..110c13fa07bf 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
@@ -17,9 +17,9 @@ import type {
ProfileNavigatorParamList,
ReferralDetailsNavigatorParamList,
ReimbursementAccountNavigatorParamList,
+ ReportDescriptionNavigatorParamList,
ReportDetailsNavigatorParamList,
ReportSettingsNavigatorParamList,
- ReportWelcomeMessageNavigatorParamList,
RoomInviteNavigatorParamList,
RoomMembersNavigatorParamList,
SearchNavigatorParamList,
@@ -101,7 +101,6 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../pages/iou/MoneyRequestDatePage').default as React.ComponentType,
[SCREENS.MONEY_REQUEST.DESCRIPTION]: () => require('../../../pages/iou/MoneyRequestDescriptionPage').default as React.ComponentType,
[SCREENS.MONEY_REQUEST.CATEGORY]: () => require('../../../pages/iou/MoneyRequestCategoryPage').default as React.ComponentType,
- [SCREENS.MONEY_REQUEST.TAG]: () => require('../../../pages/iou/MoneyRequestTagPage').default as React.ComponentType,
[SCREENS.MONEY_REQUEST.MERCHANT]: () => require('../../../pages/iou/MoneyRequestMerchantPage').default as React.ComponentType,
[SCREENS.IOU_SEND.ADD_BANK_ACCOUNT]: () => require('../../../pages/AddPersonalBankAccountPage').default as React.ComponentType,
[SCREENS.IOU_SEND.ADD_DEBIT_CARD]: () => require('../../../pages/settings/Wallet/AddDebitCardPage').default as React.ComponentType,
@@ -139,12 +138,11 @@ const ReportSettingsModalStackNavigator = createModalStackNavigator({
[SCREENS.TASK.TITLE]: () => require('../../../pages/tasks/TaskTitlePage').default as React.ComponentType,
- [SCREENS.TASK.DESCRIPTION]: () => require('../../../pages/tasks/TaskDescriptionPage').default as React.ComponentType,
[SCREENS.TASK.ASSIGNEE]: () => require('../../../pages/tasks/TaskAssigneeSelectorModal').default as React.ComponentType,
});
-const ReportWelcomeMessageModalStackNavigator = createModalStackNavigator({
- [SCREENS.REPORT_WELCOME_MESSAGE_ROOT]: () => require('../../../pages/ReportWelcomeMessagePage').default as React.ComponentType,
+const ReportDescriptionModalStackNavigator = createModalStackNavigator({
+ [SCREENS.REPORT_DESCRIPTION_ROOT]: () => require('../../../pages/ReportDescriptionPage').default as React.ComponentType,
});
const ReportParticipantsModalStackNavigator = createModalStackNavigator({
@@ -309,7 +307,7 @@ export {
ReportDetailsModalStackNavigator,
ReportParticipantsModalStackNavigator,
ReportSettingsModalStackNavigator,
- ReportWelcomeMessageModalStackNavigator,
+ ReportDescriptionModalStackNavigator,
RoomInviteModalStackNavigator,
RoomMembersModalStackNavigator,
SearchModalStackNavigator,
diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx
index 7721a64adea9..93d2f8fba989 100644
--- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx
+++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx
@@ -62,8 +62,8 @@ function RightModalNavigator({navigation}: RightModalNavigatorProps) {
component={ModalStackNavigators.ReportSettingsModalStackNavigator}
/>
['config'] = {
},
},
},
- [SCREENS.RIGHT_MODAL.REPORT_WELCOME_MESSAGE]: {
+ [SCREENS.RIGHT_MODAL.REPORT_DESCRIPTION]: {
screens: {
- [SCREENS.REPORT_WELCOME_MESSAGE_ROOT]: ROUTES.REPORT_WELCOME_MESSAGE.route,
+ [SCREENS.REPORT_DESCRIPTION_ROOT]: ROUTES.REPORT_DESCRIPTION.route,
},
},
[SCREENS.RIGHT_MODAL.NEW_CHAT]: {
@@ -406,7 +406,6 @@ const config: LinkingOptions['config'] = {
[SCREENS.MONEY_REQUEST.CURRENCY]: ROUTES.MONEY_REQUEST_CURRENCY.route,
[SCREENS.MONEY_REQUEST.DESCRIPTION]: ROUTES.MONEY_REQUEST_DESCRIPTION.route,
[SCREENS.MONEY_REQUEST.CATEGORY]: ROUTES.MONEY_REQUEST_CATEGORY.route,
- [SCREENS.MONEY_REQUEST.TAG]: ROUTES.MONEY_REQUEST_TAG.route,
[SCREENS.MONEY_REQUEST.MERCHANT]: ROUTES.MONEY_REQUEST_MERCHANT.route,
[SCREENS.MONEY_REQUEST.RECEIPT]: ROUTES.MONEY_REQUEST_RECEIPT.route,
[SCREENS.MONEY_REQUEST.DISTANCE]: ROUTES.MONEY_REQUEST_DISTANCE.route,
@@ -425,7 +424,6 @@ const config: LinkingOptions['config'] = {
[SCREENS.RIGHT_MODAL.TASK_DETAILS]: {
screens: {
[SCREENS.TASK.TITLE]: ROUTES.TASK_TITLE.route,
- [SCREENS.TASK.DESCRIPTION]: ROUTES.TASK_DESCRIPTION.route,
[SCREENS.TASK.ASSIGNEE]: ROUTES.TASK_ASSIGNEE.route,
},
},
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 3c4cf17853f1..48ef69f27768 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -176,8 +176,8 @@ type ReportSettingsNavigatorParamList = {
[SCREENS.REPORT_SETTINGS.WRITE_CAPABILITY]: undefined;
};
-type ReportWelcomeMessageNavigatorParamList = {
- [SCREENS.REPORT_WELCOME_MESSAGE_ROOT]: {reportID: string};
+type ReportDescriptionNavigatorParamList = {
+ [SCREENS.REPORT_DESCRIPTION_ROOT]: {reportID: string};
};
type ParticipantsNavigatorParamList = {
@@ -225,12 +225,15 @@ type MoneyRequestNavigatorParamList = {
iouType: string;
reportID: string;
};
- [SCREENS.MONEY_REQUEST.TAG]: {
+ [SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT]: {
iouType: string;
+ transactionID: string;
reportID: string;
+ backTo: string;
};
- [SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT]: {
- iouType: string;
+ [SCREENS.MONEY_REQUEST.STEP_TAG]: {
+ action: ValueOf;
+ iouType: ValueOf;
transactionID: string;
reportID: string;
backTo: string;
@@ -284,7 +287,6 @@ type TeachersUniteNavigatorParamList = {
type TaskDetailsNavigatorParamList = {
[SCREENS.TASK.TITLE]: undefined;
- [SCREENS.TASK.DESCRIPTION]: undefined;
[SCREENS.TASK.ASSIGNEE]: {
reportID: string;
};
@@ -366,7 +368,7 @@ type RightModalNavigatorParamList = {
[SCREENS.RIGHT_MODAL.PROFILE]: NavigatorScreenParams;
[SCREENS.RIGHT_MODAL.REPORT_DETAILS]: NavigatorScreenParams;
[SCREENS.RIGHT_MODAL.REPORT_SETTINGS]: NavigatorScreenParams;
- [SCREENS.RIGHT_MODAL.REPORT_WELCOME_MESSAGE]: NavigatorScreenParams;
+ [SCREENS.RIGHT_MODAL.REPORT_DESCRIPTION]: NavigatorScreenParams;
[SCREENS.RIGHT_MODAL.PARTICIPANTS]: NavigatorScreenParams;
[SCREENS.RIGHT_MODAL.ROOM_MEMBERS]: NavigatorScreenParams;
[SCREENS.RIGHT_MODAL.ROOM_INVITE]: NavigatorScreenParams;
@@ -500,7 +502,7 @@ export type {
ReportDetailsNavigatorParamList,
ReportSettingsNavigatorParamList,
TaskDetailsNavigatorParamList,
- ReportWelcomeMessageNavigatorParamList,
+ ReportDescriptionNavigatorParamList,
ParticipantsNavigatorParamList,
RoomMembersNavigatorParamList,
RoomInviteNavigatorParamList,
diff --git a/src/libs/NumberUtils.ts b/src/libs/NumberUtils.ts
index ddbd42243758..d7eb87a2ed1e 100644
--- a/src/libs/NumberUtils.ts
+++ b/src/libs/NumberUtils.ts
@@ -47,18 +47,6 @@ function generateHexadecimalValue(num: number): string {
return result.join('').toUpperCase();
}
-/**
- * Clamp a number in a range.
- * This is a worklet so it should be used only from UI thread.
-
- * @returns clamped value between min and max
- */
-function clampWorklet(num: number, min: number, max: number): number {
- 'worklet';
-
- return Math.min(Math.max(num, min), max);
-}
-
/**
* Generates a random integer between a and b
* It's and equivalent of _.random(a, b)
@@ -81,4 +69,4 @@ function parseFloatAnyLocale(value: string): number {
return parseFloat(value ? value.replace(',', '.') : value);
}
-export {rand64, generateHexadecimalValue, generateRandomInt, clampWorklet, parseFloatAnyLocale};
+export {rand64, generateHexadecimalValue, generateRandomInt, parseFloatAnyLocale};
diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts
index 071cf0050688..a881dd411221 100644
--- a/src/libs/PersonalDetailsUtils.ts
+++ b/src/libs/PersonalDetailsUtils.ts
@@ -1,3 +1,4 @@
+import Str from 'expensify-common/lib/str';
import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import CONST from '@src/CONST';
@@ -8,6 +9,11 @@ import * as LocalePhoneNumber from './LocalePhoneNumber';
import * as Localize from './Localize';
import * as UserUtils from './UserUtils';
+type FirstAndLastName = {
+ firstName: string;
+ lastName: string;
+};
+
let personalDetails: Array = [];
let allPersonalDetails: OnyxEntry = {};
Onyx.connect({
@@ -195,6 +201,57 @@ function getEffectiveDisplayName(personalDetail?: PersonalDetails): string | und
return undefined;
}
+/**
+ * Creates a new displayName for a user based on passed personal details or login.
+ */
+function createDisplayName(login: string, passedPersonalDetails: Pick | OnyxEntry): string {
+ // If we have a number like +15857527441@expensify.sms then let's remove @expensify.sms and format it
+ // so that the option looks cleaner in our UI.
+ const userLogin = LocalePhoneNumber.formatPhoneNumber(login);
+
+ if (!passedPersonalDetails) {
+ return userLogin;
+ }
+
+ const firstName = passedPersonalDetails.firstName ?? '';
+ const lastName = passedPersonalDetails.lastName ?? '';
+ const fullName = `${firstName} ${lastName}`.trim();
+
+ // It's possible for fullName to be empty string, so we must use "||" to fallback to userLogin.
+ return fullName || userLogin;
+}
+
+/**
+ * Gets the first and last name from the user's personal details.
+ * If the login is the same as the displayName, then they don't exist,
+ * so we return empty strings instead.
+ */
+function extractFirstAndLastNameFromAvailableDetails({login, displayName, firstName, lastName}: PersonalDetails): FirstAndLastName {
+ // It's possible for firstName to be empty string, so we must use "||" to consider lastName instead.
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ if (firstName || lastName) {
+ return {firstName: firstName ?? '', lastName: lastName ?? ''};
+ }
+ if (login && Str.removeSMSDomain(login) === displayName) {
+ return {firstName: '', lastName: ''};
+ }
+
+ if (displayName) {
+ const firstSpaceIndex = displayName.indexOf(' ');
+ const lastSpaceIndex = displayName.lastIndexOf(' ');
+ if (firstSpaceIndex === -1) {
+ return {firstName: displayName, lastName: ''};
+ }
+
+ return {
+ firstName: displayName.substring(0, firstSpaceIndex).trim(),
+ lastName: displayName.substring(lastSpaceIndex).trim(),
+ };
+ }
+
+ return {firstName: '', lastName: ''};
+}
+
/**
* Whether personal details is empty
*/
@@ -213,4 +270,6 @@ export {
getFormattedStreet,
getStreetLines,
getEffectiveDisplayName,
+ createDisplayName,
+ extractFirstAndLastNameFromAvailableDetails,
};
diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index b6ee4ab3a353..d9e57f5e3bb8 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -15,10 +15,7 @@ type UnitRate = {rate: number};
* These are policies that we can use to create reports with in NewDot.
*/
function getActivePolicies(policies: OnyxCollection): Policy[] | undefined {
- return Object.values(policies ?? {}).filter(
- (policy): policy is Policy =>
- policy !== null && policy && (policy.isPolicyExpenseChatEnabled || policy.areChatRoomsEnabled) && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
- );
+ return Object.values(policies ?? {}).filter((policy): policy is Policy => policy !== null && policy && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
}
/**
@@ -107,7 +104,7 @@ function isExpensifyGuideTeam(email: string): boolean {
/**
* Checks if the current user is an admin of the policy.
*/
-const isPolicyAdmin = (policy: OnyxEntry): boolean => policy?.role === CONST.POLICY.ROLE.ADMIN;
+const isPolicyAdmin = (policy: OnyxEntry | EmptyObject): boolean => policy?.role === CONST.POLICY.ROLE.ADMIN;
const isPolicyMember = (policyID: string, policies: Record): boolean => Object.values(policies).some((policy) => policy?.id === policyID);
@@ -203,7 +200,7 @@ function isPendingDeletePolicy(policy: OnyxEntry): boolean {
return policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE;
}
-function isPaidGroupPolicy(policy: OnyxEntry): boolean {
+function isPaidGroupPolicy(policy: OnyxEntry | EmptyObject): boolean {
return policy?.type === CONST.POLICY.TYPE.TEAM || policy?.type === CONST.POLICY.TYPE.CORPORATE;
}
diff --git a/src/libs/ReceiptUtils.ts b/src/libs/ReceiptUtils.ts
index 948d8094d551..f31a1aa811a0 100644
--- a/src/libs/ReceiptUtils.ts
+++ b/src/libs/ReceiptUtils.ts
@@ -50,7 +50,7 @@ function getThumbnailAndImageURIs(transaction: OnyxEntry, receiptPa
}
// For local files, we won't have a thumbnail yet
- if (isReceiptImage && (path.startsWith('blob:') || path.startsWith('file:'))) {
+ if (isReceiptImage && typeof path === 'string' && (path.startsWith('blob:') || path.startsWith('file:'))) {
return {thumbnail: null, image: path, isLocalFile: true, filename};
}
diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts
index 6e7cd0cdd999..1aeb6e6e7343 100644
--- a/src/libs/ReportActionsUtils.ts
+++ b/src/libs/ReportActionsUtils.ts
@@ -432,9 +432,9 @@ function replaceBaseURLInPolicyChangeLogAction(reportAction: ReportAction): Repo
return updatedReportAction;
}
-function getLastVisibleAction(reportID: string, actionsToMerge: ReportActions = {}): OnyxEntry {
- const reportActions = Object.values(fastMerge(allReportActions?.[reportID] ?? {}, actionsToMerge, true));
- const visibleReportActions = Object.values(reportActions ?? {}).filter((action) => shouldReportActionBeVisibleAsLastAction(action));
+function getLastVisibleAction(reportID: string, actionsToMerge: OnyxCollection = {}): OnyxEntry {
+ const reportActions = Object.values(fastMerge(allReportActions?.[reportID] ?? {}, actionsToMerge ?? {}, true));
+ const visibleReportActions = Object.values(reportActions ?? {}).filter((action): action is ReportAction => shouldReportActionBeVisibleAsLastAction(action));
const sortedReportActions = getSortedReportActions(visibleReportActions, true);
if (sortedReportActions.length === 0) {
return null;
@@ -442,7 +442,7 @@ function getLastVisibleAction(reportID: string, actionsToMerge: ReportActions =
return sortedReportActions[0];
}
-function getLastVisibleMessage(reportID: string, actionsToMerge: ReportActions = {}): LastVisibleMessage {
+function getLastVisibleMessage(reportID: string, actionsToMerge: OnyxCollection = {}): LastVisibleMessage {
const lastVisibleAction = getLastVisibleAction(reportID, actionsToMerge);
const message = lastVisibleAction?.message?.[0];
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 568ce49ff961..3c9481370744 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -13,6 +13,7 @@ import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvata
import CONST from '@src/CONST';
import type {ParentNavigationSummaryParams, TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
+import type {Route} from '@src/ROUTES';
import ROUTES from '@src/ROUTES';
import type {
Beta,
@@ -34,7 +35,7 @@ import type {ChangeLog, IOUMessage, OriginalMessageActionName, OriginalMessageCr
import type {Status} from '@src/types/onyx/PersonalDetails';
import type {NotificationPreference} from '@src/types/onyx/Report';
import type {Message, ReportActionBase, ReportActions} from '@src/types/onyx/ReportAction';
-import type {Receipt, WaypointCollection} from '@src/types/onyx/Transaction';
+import type {Receipt, TransactionChanges, WaypointCollection} from '@src/types/onyx/Transaction';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type IconAsset from '@src/types/utils/IconAsset';
@@ -165,27 +166,6 @@ type OptimisticIOUReportAction = Pick<
| 'whisperedToAccountIDs'
>;
-type OptimisticReportPreview = Pick<
- ReportAction,
- | 'actionName'
- | 'reportActionID'
- | 'pendingAction'
- | 'originalMessage'
- | 'message'
- | 'created'
- | 'actorAccountID'
- | 'childMoneyRequestCount'
- | 'childLastMoneyRequestComment'
- | 'childRecentReceiptTransactionIDs'
- | 'childReportID'
- | 'whisperedToAccountIDs'
-> & {reportID?: string; accountID?: number};
-
-type UpdateReportPreview = Pick<
- ReportAction,
- 'created' | 'message' | 'childLastMoneyRequestComment' | 'childMoneyRequestCount' | 'childRecentReceiptTransactionIDs' | 'whisperedToAccountIDs'
->;
-
type ReportRouteParams = {
reportID: string;
isSubReportPageRoute: boolean;
@@ -193,7 +173,7 @@ type ReportRouteParams = {
type ReportOfflinePendingActionAndErrors = {
addWorkspaceRoomOrChatPendingAction: PendingAction | undefined;
- addWorkspaceRoomOrChatErrors: Record | null | undefined;
+ addWorkspaceRoomOrChatErrors: Errors | null | undefined;
};
type OptimisticApprovedReportAction = Pick<
@@ -223,6 +203,8 @@ type OptimisticChatReport = Pick<
Report,
| 'type'
| 'chatType'
+ | 'chatReportID'
+ | 'iouReportID'
| 'isOwnPolicyExpenseChat'
| 'isPinned'
| 'lastActorAccountID'
@@ -234,6 +216,7 @@ type OptimisticChatReport = Pick<
| 'notificationPreference'
| 'oldPolicyName'
| 'ownerAccountID'
+ | 'pendingFields'
| 'parentReportActionID'
| 'parentReportID'
| 'participantAccountIDs'
@@ -244,7 +227,7 @@ type OptimisticChatReport = Pick<
| 'stateNum'
| 'statusNum'
| 'visibility'
- | 'welcomeMessage'
+ | 'description'
| 'writeCapability'
>;
@@ -307,23 +290,21 @@ type OptimisticTaskReport = Pick<
| 'lastVisibleActionCreated'
>;
-type TransactionDetails =
- | {
- created: string;
- amount: number;
- currency: string;
- merchant: string;
- waypoints?: WaypointCollection;
- comment: string;
- category: string;
- billable: boolean;
- tag: string;
- mccGroup?: ValueOf;
- cardID: number;
- originalAmount: number;
- originalCurrency: string;
- }
- | undefined;
+type TransactionDetails = {
+ created: string;
+ amount: number;
+ currency: string;
+ merchant: string;
+ waypoints?: WaypointCollection | string;
+ comment: string;
+ category: string;
+ billable: boolean;
+ tag: string;
+ mccGroup?: ValueOf;
+ cardID: number;
+ originalAmount: number;
+ originalCurrency: string;
+};
type OptimisticIOUReport = Pick<
Report,
@@ -332,6 +313,7 @@ type OptimisticIOUReport = Pick<
| 'chatReportID'
| 'currency'
| 'managerID'
+ | 'policyID'
| 'ownerAccountID'
| 'participantAccountIDs'
| 'visibleChatMemberAccountIDs'
@@ -503,7 +485,7 @@ Onyx.connect({
},
});
-function getChatType(report: OnyxEntry): ValueOf | undefined {
+function getChatType(report: OnyxEntry | Participant | EmptyObject): ValueOf | undefined {
return report?.chatType;
}
@@ -552,7 +534,7 @@ function getRootParentReport(report: OnyxEntry | undefined | EmptyObject
return getRootParentReport(!isEmptyObject(parentReport) ? parentReport : null);
}
-function getPolicy(policyID: string): Policy | EmptyObject {
+function getPolicy(policyID: string | undefined): Policy | EmptyObject {
if (!allPolicies || !policyID) {
return {};
}
@@ -614,13 +596,19 @@ function isExpenseReport(report: OnyxEntry | EmptyObject): boolean {
}
/**
- * Checks if a report is an IOU report.
+ * Checks if a report is an IOU report using report or reportID
*/
function isIOUReport(reportOrID: OnyxEntry | string | EmptyObject): boolean {
const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null : reportOrID;
return report?.type === CONST.REPORT.TYPE.IOU;
}
+/**
+ * Checks if a report is an IOU report using report
+ */
+function isIOUReportUsingReport(report: OnyxEntry | EmptyObject): report is Report {
+ return report?.type === CONST.REPORT.TYPE.IOU;
+}
/**
* Checks if a report is a task report.
*/
@@ -783,7 +771,7 @@ function isUserCreatedPolicyRoom(report: OnyxEntry): boolean {
/**
* Whether the provided report is a Policy Expense chat.
*/
-function isPolicyExpenseChat(report: OnyxEntry): boolean {
+function isPolicyExpenseChat(report: OnyxEntry | Participant | EmptyObject): boolean {
return getChatType(report) === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT || (report?.isPolicyExpenseChat ?? false);
}
@@ -856,7 +844,7 @@ function isPublicAnnounceRoom(report: OnyxEntry): boolean {
* If the report is a policy expense, the route should be for adding bank account for that policy
* else since the report is a personal IOU, the route should be for personal bank account.
*/
-function getBankAccountRoute(report: OnyxEntry): string {
+function getBankAccountRoute(report: OnyxEntry): Route {
return isPolicyExpenseChat(report) ? ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute('', report?.policyID) : ROUTES.SETTINGS_ADD_BANK_ACCOUNT;
}
@@ -1995,7 +1983,7 @@ function getMoneyRequestReportName(report: OnyxEntry, policy: OnyxEntry<
* into a flat object. Used for displaying transactions and sending them in API commands
*/
-function getTransactionDetails(transaction: OnyxEntry, createdDateFormat: string = CONST.DATE.FNS_FORMAT_STRING): TransactionDetails {
+function getTransactionDetails(transaction: OnyxEntry, createdDateFormat: string = CONST.DATE.FNS_FORMAT_STRING): TransactionDetails | undefined {
if (!transaction) {
return;
}
@@ -2269,13 +2257,15 @@ function getReportPreviewMessage(
});
}
+ let linkedTransaction;
if (!isEmptyObject(reportAction) && shouldConsiderReceiptBeingScanned && reportAction && ReportActionsUtils.isMoneyRequestAction(reportAction)) {
- const linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction);
+ linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction);
+ }
- if (!isEmptyObject(linkedTransaction) && TransactionUtils.hasReceipt(linkedTransaction) && TransactionUtils.isReceiptBeingScanned(linkedTransaction)) {
- return Localize.translateLocal('iou.receiptScanning');
- }
+ if (!isEmptyObject(linkedTransaction) && TransactionUtils.hasReceipt(linkedTransaction) && TransactionUtils.isReceiptBeingScanned(linkedTransaction)) {
+ return Localize.translateLocal('iou.receiptScanning');
}
+
const originalMessage = reportAction?.originalMessage as IOUMessage | undefined;
// Show Paid preview message if it's settled or if the amount is paid & stuck at receivers end for only chat reports.
@@ -2302,14 +2292,17 @@ function getReportPreviewMessage(
return Localize.translateLocal('iou.waitingOnBankAccount', {submitterDisplayName});
}
- const containsNonReimbursable = hasNonReimbursableTransactions(report.reportID);
-
const lastActorID = reportAction?.actorAccountID;
+ let amount = originalMessage?.amount;
+ let currency = originalMessage?.currency ? originalMessage?.currency : report.currency;
+
+ if (!isEmptyObject(linkedTransaction)) {
+ amount = TransactionUtils.getAmount(linkedTransaction, isExpenseReport(report));
+ currency = TransactionUtils.getCurrency(linkedTransaction);
+ }
// if we have the amount in the originalMessage and lastActorID, we can use that to display the preview message for the latest request
- if (originalMessage?.amount !== undefined && lastActorID && !isPreviewMessageForParentChatReport) {
- const amount = originalMessage?.amount;
- const currency = originalMessage?.currency ?? report.currency ?? '';
+ if (amount !== undefined && lastActorID && !isPreviewMessageForParentChatReport) {
const amountToDisplay = CurrencyUtils.convertToDisplayString(Math.abs(amount), currency);
// We only want to show the actor name in the preview if it's not the current user who took the action
@@ -2317,6 +2310,8 @@ function getReportPreviewMessage(
return `${requestorName ? `${requestorName}: ` : ''}${Localize.translateLocal('iou.requestedAmount', {formattedAmount: amountToDisplay})}`;
}
+ const containsNonReimbursable = hasNonReimbursableTransactions(report.reportID);
+
return Localize.translateLocal(containsNonReimbursable ? 'iou.payerSpentAmount' : 'iou.payerOwesAmount', {payer: payerName ?? '', amount: formattedAmount});
}
@@ -2326,7 +2321,7 @@ function getReportPreviewMessage(
*
* At the moment, we only allow changing one transaction field at a time.
*/
-function getModifiedExpenseOriginalMessage(oldTransaction: OnyxEntry, transactionChanges: ExpenseOriginalMessage, isFromExpenseReport: boolean): ExpenseOriginalMessage {
+function getModifiedExpenseOriginalMessage(oldTransaction: OnyxEntry, transactionChanges: TransactionChanges, isFromExpenseReport: boolean): ExpenseOriginalMessage {
const originalMessage: ExpenseOriginalMessage = {};
// Remark: Comment field is the only one which has new/old prefixes for the keys (newComment/ oldComment),
// all others have old/- pattern such as oldCreated/created
@@ -2604,6 +2599,15 @@ function getParsedComment(text: string): string {
return text.length <= CONST.MAX_MARKUP_LENGTH ? parser.replace(text) : lodashEscape(text);
}
+function getReportDescriptionText(report: Report): string {
+ if (!report.description) {
+ return '';
+ }
+
+ const parser = new ExpensiMark();
+ return parser.htmlToText(report.description);
+}
+
function buildOptimisticAddCommentReportAction(text?: string, file?: File, actorAccountID?: number): OptimisticReportAction {
const parser = new ExpensiMark();
const commentText = getParsedComment(text ?? '');
@@ -2924,7 +2928,7 @@ function buildOptimisticIOUReportAction(
comment: string,
participants: Participant[],
transactionID: string,
- paymentType: PaymentMethodType,
+ paymentType?: PaymentMethodType,
iouReportID = '',
isSettlingUp = false,
isSendMoneyFlow = false,
@@ -2969,8 +2973,8 @@ function buildOptimisticIOUReportAction(
originalMessage.participantAccountIDs = currentUserAccountID ? [currentUserAccountID] : [];
} else {
originalMessage.participantAccountIDs = currentUserAccountID
- ? [currentUserAccountID, ...participants.map((participant) => participant.accountID)]
- : participants.map((participant) => participant.accountID);
+ ? [currentUserAccountID, ...participants.map((participant) => participant.accountID ?? -1)]
+ : participants.map((participant) => participant.accountID ?? -1);
}
}
@@ -3112,13 +3116,7 @@ function buildOptimisticSubmittedReportAction(amount: number, currency: string,
* @param [comment] - User comment for the IOU.
* @param [transaction] - optimistic first transaction of preview
*/
-function buildOptimisticReportPreview(
- chatReport: OnyxEntry,
- iouReport: OnyxEntry,
- comment = '',
- transaction: OnyxEntry = null,
- childReportID?: string,
-): OptimisticReportPreview {
+function buildOptimisticReportPreview(chatReport: OnyxEntry, iouReport: Report, comment = '', transaction: OnyxEntry = null, childReportID?: string): ReportAction {
const hasReceipt = TransactionUtils.hasReceipt(transaction);
const isReceiptBeingScanned = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction);
const message = getReportPreviewMessage(iouReport);
@@ -3155,9 +3153,9 @@ function buildOptimisticReportPreview(
* Builds an optimistic modified expense action with a randomly generated reportActionID.
*/
function buildOptimisticModifiedExpenseReportAction(
- transactionThread: OnyxEntry,
+ transactionThread: OnyxEntry,
oldTransaction: OnyxEntry,
- transactionChanges: ExpenseOriginalMessage,
+ transactionChanges: TransactionChanges,
isFromExpenseReport: boolean,
): OptimisticModifiedExpenseReportAction {
const originalMessage = getModifiedExpenseOriginalMessage(oldTransaction, transactionChanges, isFromExpenseReport);
@@ -3198,13 +3196,7 @@ function buildOptimisticModifiedExpenseReportAction(
* @param [transaction] - optimistic newest transaction of a report preview
*
*/
-function updateReportPreview(
- iouReport: OnyxEntry,
- reportPreviewAction: OnyxEntry,
- isPayRequest = false,
- comment = '',
- transaction: OnyxEntry = null,
-): UpdateReportPreview {
+function updateReportPreview(iouReport: OnyxEntry, reportPreviewAction: ReportAction, isPayRequest = false, comment = '', transaction: OnyxEntry = null): ReportAction {
const hasReceipt = TransactionUtils.hasReceipt(transaction);
const recentReceiptTransactions = reportPreviewAction?.childRecentReceiptTransactionIDs ?? {};
const transactionsToKeep = TransactionUtils.getRecentTransactions(recentReceiptTransactions);
@@ -3296,7 +3288,7 @@ function buildOptimisticChatReport(
notificationPreference: NotificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS,
parentReportActionID = '',
parentReportID = '',
- welcomeMessage = '',
+ description = '',
): OptimisticChatReport {
const currentTime = DateUtils.getDBTime();
const isNewlyCreatedWorkspaceChat = chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT && isOwnPolicyExpenseChat;
@@ -3325,7 +3317,7 @@ function buildOptimisticChatReport(
stateNum: 0,
statusNum: 0,
visibility,
- welcomeMessage,
+ description,
writeCapability,
};
}
@@ -3854,7 +3846,7 @@ function canFlagReportAction(reportAction: OnyxEntry, reportID: st
reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CHRONOSOOOLIST;
if (ReportActionsUtils.isWhisperAction(reportAction)) {
// Allow flagging welcome message whispers as they can be set by any room creator
- if (report?.welcomeMessage && !isCurrentUserAction && isOriginalMessageHaveHtml && reportAction?.originalMessage?.html === report.welcomeMessage) {
+ if (report?.description && !isCurrentUserAction && isOriginalMessageHaveHtml && reportAction?.originalMessage?.html === report.description) {
return true;
}
@@ -4200,7 +4192,7 @@ function isValidReportIDFromPath(reportIDFromPath: string): boolean {
/**
* Return the errors we have when creating a chat or a workspace room
*/
-function getAddWorkspaceRoomOrChatReportErrors(report: OnyxEntry): Record | null | undefined {
+function getAddWorkspaceRoomOrChatReportErrors(report: OnyxEntry): Errors | null | undefined {
// We are either adding a workspace room, or we're creating a chat, it isn't possible for both of these to have errors for the same report at the same time, so
// simply looking up the first truthy value will get the relevant property if it's set.
return report?.errorFields?.addWorkspaceRoom ?? report?.errorFields?.createChat;
@@ -4585,11 +4577,19 @@ function getRoom(type: ValueOf, policyID: string)
}
/**
- * We only want policy owners and admins to be able to modify the welcome message, but not in thread chat.
+ * We only want policy members who are members of the report to be able to modify the report description, but not in thread chat.
*/
-function shouldDisableWelcomeMessage(report: OnyxEntry, policy: OnyxEntry): boolean {
- return isMoneyRequestReport(report) || isArchivedRoom(report) || !isChatRoom(report) || isChatThread(report) || !PolicyUtils.isPolicyAdmin(policy);
+function canEditReportDescription(report: OnyxEntry, policy: OnyxEntry | undefined): boolean {
+ return (
+ !isMoneyRequestReport(report) &&
+ !isArchivedRoom(report) &&
+ isChatRoom(report) &&
+ !isChatThread(report) &&
+ !isEmpty(policy) &&
+ (getVisibleMemberIDs(report).includes(currentUserAccountID ?? 0) || getParticipantsIDs(report).includes(currentUserAccountID ?? 0))
+ );
}
+
/**
* Checks if report action has error when smart scanning
*/
@@ -4648,6 +4648,21 @@ function shouldDisplayThreadReplies(reportAction: OnyxEntry, repor
return hasReplies && !!reportAction?.childCommenterCount && !isThreadFirstChat(reportAction, reportID);
}
+/**
+ * Check if money report has any transactions updated optimistically
+ */
+function hasUpdatedTotal(report: OnyxEntry): boolean {
+ if (!report) {
+ return true;
+ }
+
+ const transactions = TransactionUtils.getAllReportTransactions(report.reportID);
+ const hasPendingTransaction = transactions.some((transaction) => !!transaction.pendingAction);
+ const hasTransactionWithDifferentCurrency = transactions.some((transaction) => transaction.currency !== report.currency);
+
+ return !(hasPendingTransaction && hasTransactionWithDifferentCurrency);
+}
+
/**
* Disable reply in thread action if:
*
@@ -4931,9 +4946,9 @@ export {
getReimbursementDeQueuedActionMessage,
getPersonalDetailsForAccountID,
getRoom,
+ canEditReportDescription,
doesTransactionThreadHaveViolations,
hasViolations,
- shouldDisableWelcomeMessage,
navigateToPrivateNotes,
canEditWriteCapability,
hasSmartscanError,
@@ -4945,7 +4960,10 @@ export {
getAllAncestorReportActions,
isReportParticipant,
isValidReport,
+ getReportDescriptionText,
isReportFieldOfTypeTitle,
+ isIOUReportUsingReport,
+ hasUpdatedTotal,
isReportFieldDisabled,
getAvailableReportFields,
getAllAncestorReportActionIDs,
@@ -4961,4 +4979,6 @@ export type {
OptimisticCreatedReportAction,
OptimisticClosedReportAction,
Ancestor,
+ OptimisticIOUReportAction,
+ TransactionDetails,
};
diff --git a/src/libs/SelectionScraper/index.ts b/src/libs/SelectionScraper/index.ts
index 955fe693c856..8d7f3e7055fc 100644
--- a/src/libs/SelectionScraper/index.ts
+++ b/src/libs/SelectionScraper/index.ts
@@ -112,6 +112,9 @@ const replaceNodes = (dom: Node, isChildOfEditorElement: boolean): Node => {
// Encoding HTML chars '< >' in the text, because any HTML will be removed in stripHTML method.
if (dom.type.toString() === 'text' && dom instanceof DataNode) {
data = Str.htmlEncode(dom.data);
+ if (dom.parent instanceof Element && dom.parent?.attribs?.id === 'email-with-break-opportunities') {
+ data = data.replaceAll('\u200b', '');
+ }
} else if (dom instanceof Element) {
domName = dom.name;
if (dom.attribs?.[tagAttribute]) {
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index 02c32e089016..2347f5b9f5c5 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -253,7 +253,7 @@ function getOptionData({
const result: ReportUtils.OptionData = {
text: '',
alternateText: null,
- allReportErrors: undefined,
+ allReportErrors: OptionsListUtils.getAllReportErrors(report, reportActions),
brickRoadIndicator: null,
tooltipText: null,
subtitle: null,
@@ -295,7 +295,6 @@ function getOptionData({
result.isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
result.shouldShowSubscript = ReportUtils.shouldReportShowSubscript(report);
result.pendingAction = report.pendingFields ? report.pendingFields.addWorkspaceRoom || report.pendingFields.createChat : undefined;
- result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions);
result.brickRoadIndicator = hasErrors || hasViolations ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : '';
result.ownerAccountID = report.ownerAccountID;
result.managerID = report.managerID;
diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts
index 8e48eddea0ac..8a814f311481 100644
--- a/src/libs/TransactionUtils.ts
+++ b/src/libs/TransactionUtils.ts
@@ -6,16 +6,12 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {RecentWaypoint, Report, ReportAction, Transaction, TransactionViolation} from '@src/types/onyx';
import type {PolicyTaxRate, PolicyTaxRates} from '@src/types/onyx/PolicyTaxRates';
-import type {Comment, Receipt, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction';
+import type {Comment, Receipt, TransactionChanges, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isCorporateCard, isExpensifyCard} from './CardUtils';
import DateUtils from './DateUtils';
import * as NumberUtils from './NumberUtils';
-type AdditionalTransactionChanges = {comment?: string; waypoints?: WaypointCollection};
-
-type TransactionChanges = Partial & AdditionalTransactionChanges;
-
let allTransactions: OnyxCollection = {};
Onyx.connect({
diff --git a/src/libs/UserUtils.ts b/src/libs/UserUtils.ts
index 6ec386679a32..a8c918bc5def 100644
--- a/src/libs/UserUtils.ts
+++ b/src/libs/UserUtils.ts
@@ -176,8 +176,8 @@ function getAvatar(avatarSource?: AvatarSource, accountID?: number): AvatarSourc
* @param avatarURL - the avatar source from user's personalDetails
* @param accountID - the accountID of the user
*/
-function getAvatarUrl(avatarURL: string, accountID: number): string {
- return isDefaultAvatar(avatarURL) ? getDefaultAvatarURL(accountID) : avatarURL;
+function getAvatarUrl(avatarSource: AvatarSource | undefined, accountID: number): AvatarSource {
+ return isDefaultAvatar(avatarSource) ? getDefaultAvatarURL(accountID) : avatarSource;
}
/**
diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts
index 9b2b1d01b80b..7ee7d6c4f048 100644
--- a/src/libs/ValidationUtils.ts
+++ b/src/libs/ValidationUtils.ts
@@ -4,6 +4,7 @@ import {URL_REGEX_WITH_REQUIRED_PROTOCOL} from 'expensify-common/lib/Url';
import isDate from 'lodash/isDate';
import isEmpty from 'lodash/isEmpty';
import isObject from 'lodash/isObject';
+import type {OnyxCollection} from 'react-native-onyx';
import CONST from '@src/CONST';
import type {Report} from '@src/types/onyx';
import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
@@ -74,7 +75,7 @@ function isValidPastDate(date: string | Date): boolean {
/**
* Used to validate a value that is "required".
*/
-function isRequiredFulfilled(value: string | Date | unknown[] | Record): boolean {
+function isRequiredFulfilled(value: string | Date | unknown[] | Record | null): boolean {
if (typeof value === 'string') {
return !StringUtils.isEmptyString(value);
}
@@ -361,8 +362,8 @@ function isReservedRoomName(roomName: string): boolean {
/**
* Checks if the room name already exists.
*/
-function isExistingRoomName(roomName: string, reports: Record, policyID: string): boolean {
- return Object.values(reports).some((report) => report && report.policyID === policyID && report.reportName === roomName);
+function isExistingRoomName(roomName: string, reports: OnyxCollection, policyID: string): boolean {
+ return Object.values(reports ?? {}).some((report) => report && report.policyID === policyID && report.reportName === roomName);
}
/**
diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts
index c0558d7487cf..9a05ba4d87ed 100644
--- a/src/libs/Violations/ViolationsUtils.ts
+++ b/src/libs/Violations/ViolationsUtils.ts
@@ -1,5 +1,6 @@
import reject from 'lodash/reject';
import Onyx from 'react-native-onyx';
+import type {OnyxUpdate} from 'react-native-onyx';
import type {Phrase, PhraseParameters} from '@libs/Localize';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -17,17 +18,13 @@ const ViolationsUtils = {
policyTags: PolicyTags,
policyRequiresCategories: boolean,
policyCategories: PolicyCategories,
- ): {
- onyxMethod: string;
- key: string;
- value: TransactionViolation[];
- } {
+ ): OnyxUpdate {
let newTransactionViolations = [...transactionViolations];
if (policyRequiresCategories) {
const hasCategoryOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === 'categoryOutOfPolicy');
const hasMissingCategoryViolation = transactionViolations.some((violation) => violation.name === 'missingCategory');
- const isCategoryInPolicy = Boolean(policyCategories[transaction.category]?.enabled);
+ const isCategoryInPolicy = !!policyCategories[transaction.category ?? '']?.enabled;
// Add 'categoryOutOfPolicy' violation if category is not in policy
if (!hasCategoryOutOfPolicyViolation && transaction.category && !isCategoryInPolicy) {
@@ -53,7 +50,7 @@ const ViolationsUtils = {
if (policyRequiresTags) {
const hasTagOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === 'tagOutOfPolicy');
const hasMissingTagViolation = transactionViolations.some((violation) => violation.name === 'missingTag');
- const isTagInPolicy = Boolean(policyTags[transaction.tag]?.enabled);
+ const isTagInPolicy = !!policyTags[transaction.tag ?? '']?.enabled;
// Add 'tagOutOfPolicy' violation if tag is not in policy
if (!hasTagOutOfPolicyViolation && transaction.tag && !isTagInPolicy) {
@@ -155,7 +152,7 @@ const ViolationsUtils = {
brokenBankConnection: violation.data?.brokenBankConnection ?? false,
isAdmin: violation.data?.isAdmin ?? false,
email: violation.data?.email,
- isTransactionOlderThan7Days: Boolean(violation.data?.isTransactionOlderThan7Days),
+ isTransactionOlderThan7Days: !!violation.data?.isTransactionOlderThan7Days,
member: violation.data?.member,
});
case 'smartscanFailed':
diff --git a/src/libs/actions/EmojiPickerAction.ts b/src/libs/actions/EmojiPickerAction.ts
index 56a5f34c0b8e..0fc21713c608 100644
--- a/src/libs/actions/EmojiPickerAction.ts
+++ b/src/libs/actions/EmojiPickerAction.ts
@@ -26,6 +26,7 @@ type EmojiPickerRef = {
anchorOrigin?: AnchorOrigin,
onWillShow?: OnWillShowPicker,
id?: string,
+ activeEmoji?: string,
) => void;
isActive: (id: string) => boolean;
clearActive: () => void;
@@ -55,12 +56,13 @@ function showEmojiPicker(
anchorOrigin?: AnchorOrigin,
onWillShow: OnWillShowPicker = () => {},
id?: string,
+ activeEmoji?: string,
) {
if (!emojiPickerRef.current) {
return;
}
- emojiPickerRef.current.showEmojiPicker(onModalHide, onEmojiSelected, emojiPopoverAnchor, anchorOrigin, onWillShow, id);
+ emojiPickerRef.current.showEmojiPicker(onModalHide, onEmojiSelected, emojiPopoverAnchor, anchorOrigin, onWillShow, id, activeEmoji);
}
/**
diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.ts
similarity index 62%
rename from src/libs/actions/IOU.js
rename to src/libs/actions/IOU.ts
index 4db89a1e926b..a7a82e642e62 100644
--- a/src/libs/actions/IOU.js
+++ b/src/libs/actions/IOU.ts
@@ -1,12 +1,29 @@
+import type {StackScreenProps} from '@react-navigation/stack';
import {format} from 'date-fns';
import fastMerge from 'expensify-common/lib/fastMerge';
import Str from 'expensify-common/lib/str';
-import lodashGet from 'lodash/get';
-import lodashHas from 'lodash/has';
import Onyx from 'react-native-onyx';
-import _ from 'underscore';
+import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx';
+import type {ValueOf} from 'type-fest';
import ReceiptGeneric from '@assets/images/receipt-generic.png';
import * as API from '@libs/API';
+import type {
+ ApproveMoneyRequestParams,
+ CompleteSplitBillParams,
+ CreateDistanceRequestParams,
+ DeleteMoneyRequestParams,
+ DetachReceiptParams,
+ EditMoneyRequestParams,
+ PayMoneyRequestParams,
+ ReplaceReceiptParams,
+ RequestMoneyParams,
+ SendMoneyParams,
+ SplitBillParams,
+ StartSplitBillParams,
+ SubmitReportParams,
+ UpdateMoneyRequestParams,
+} from '@libs/API/parameters';
+import {WRITE_COMMANDS} from '@libs/API/types';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import DateUtils from '@libs/DateUtils';
import * as ErrorUtils from '@libs/ErrorUtils';
@@ -21,128 +38,198 @@ import Permissions from '@libs/Permissions';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
+import type {OptimisticChatReport, OptimisticCreatedReportAction, OptimisticIOUReportAction, TransactionDetails} from '@libs/ReportUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
import * as UserUtils from '@libs/UserUtils';
import ViolationsUtils from '@libs/Violations/ViolationsUtils';
+import type {MoneyRequestNavigatorParamList} 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 * as OnyxTypes from '@src/types/onyx';
+import type {Participant, Split} from '@src/types/onyx/IOU';
+import type {ErrorFields, Errors} from '@src/types/onyx/OnyxCommon';
+import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
+import type ReportAction from '@src/types/onyx/ReportAction';
+import type {OnyxData} from '@src/types/onyx/Request';
+import type {Comment, Receipt, ReceiptSource, TaxRate, TransactionChanges, WaypointCollection} from '@src/types/onyx/Transaction';
+import type {EmptyObject} from '@src/types/utils/EmptyObject';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
import * as Policy from './Policy';
import * as Report from './Report';
-let betas;
+type MoneyRequestRoute = StackScreenProps['route'];
+
+type IOURequestType = ValueOf;
+
+type OneOnOneIOUReport = OnyxTypes.Report | undefined | null;
+
+type MoneyRequestInformation = {
+ payerAccountID: number;
+ payerEmail: string;
+ iouReport: OnyxTypes.Report;
+ chatReport: OnyxTypes.Report;
+ transaction: OnyxTypes.Transaction;
+ iouAction: OptimisticIOUReportAction;
+ createdChatReportActionID: string;
+ createdIOUReportActionID: string;
+ reportPreviewAction: OnyxTypes.ReportAction;
+ onyxData: OnyxData;
+};
+
+type SplitData = {
+ chatReportID: string;
+ transactionID: string;
+ reportActionID: string;
+ policyID?: string;
+ createdReportActionID?: string;
+};
+
+type SplitsAndOnyxData = {
+ splitData: SplitData;
+ splits: Split[];
+ onyxData: OnyxData;
+};
+
+type UpdateMoneyRequestData = {
+ params: UpdateMoneyRequestParams;
+ onyxData: OnyxData;
+};
+
+type PayMoneyRequestData = {
+ params: PayMoneyRequestParams;
+ optimisticData: OnyxUpdate[];
+ successData: OnyxUpdate[];
+ failureData: OnyxUpdate[];
+};
+
+type SendMoneyParamsData = {
+ params: SendMoneyParams;
+ optimisticData: OnyxUpdate[];
+ successData: OnyxUpdate[];
+ failureData: OnyxUpdate[];
+};
+
+type OutstandingChildRequest = {
+ hasOutstandingChildRequest?: boolean;
+};
+
+let betas: OnyxTypes.Beta[] = [];
Onyx.connect({
key: ONYXKEYS.BETAS,
- callback: (val) => (betas = val || []),
+ callback: (value) => (betas = value ?? []),
});
-let allPersonalDetails;
+let allPersonalDetails: OnyxTypes.PersonalDetailsList = {};
Onyx.connect({
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
- callback: (val) => {
- allPersonalDetails = val || {};
+ callback: (value) => {
+ allPersonalDetails = value ?? {};
},
});
-let allReports;
+let allReports: OnyxCollection = null;
Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
- callback: (val) => (allReports = val),
+ callback: (value) => (allReports = value),
});
-let allTransactions;
+let allTransactions: NonNullable> = {};
Onyx.connect({
key: ONYXKEYS.COLLECTION.TRANSACTION,
waitForCollectionCallback: true,
- callback: (val) => {
- if (!val) {
+ callback: (value) => {
+ if (!value) {
allTransactions = {};
return;
}
- allTransactions = val;
+ allTransactions = value;
},
});
-let allTransactionDrafts = {};
+let allTransactionDrafts: NonNullable> = {};
Onyx.connect({
key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT,
waitForCollectionCallback: true,
- callback: (val) => {
- allTransactionDrafts = val || {};
+ callback: (value) => {
+ allTransactionDrafts = value ?? {};
},
});
-let allTransactionViolations;
+let allTransactionViolations: NonNullable> = {};
Onyx.connect({
key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
waitForCollectionCallback: true,
- callback: (val) => {
- if (!val) {
+ callback: (value) => {
+ if (!value) {
allTransactionViolations = {};
return;
}
- allTransactionViolations = val;
+ allTransactionViolations = value;
},
});
-let allDraftSplitTransactions;
+let allDraftSplitTransactions: NonNullable> = {};
Onyx.connect({
key: ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT,
waitForCollectionCallback: true,
- callback: (val) => {
- allDraftSplitTransactions = val || {};
+ callback: (value) => {
+ allDraftSplitTransactions = value ?? {};
},
});
-let allNextSteps = {};
+let allNextSteps: NonNullable> = {};
Onyx.connect({
key: ONYXKEYS.COLLECTION.NEXT_STEP,
waitForCollectionCallback: true,
- callback: (val) => {
- allNextSteps = val || {};
+ callback: (value) => {
+ allNextSteps = value ?? {};
},
});
-let userAccountID = '';
+let userAccountID = -1;
let currentUserEmail = '';
Onyx.connect({
key: ONYXKEYS.SESSION,
- callback: (val) => {
- currentUserEmail = lodashGet(val, 'email', '');
- userAccountID = lodashGet(val, 'accountID', '');
+ callback: (value) => {
+ currentUserEmail = value?.email ?? '';
+ userAccountID = value?.accountID ?? -1;
},
});
-let currentUserPersonalDetails = {};
+let currentUserPersonalDetails: OnyxTypes.PersonalDetails | EmptyObject = {};
Onyx.connect({
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
- callback: (val) => {
- currentUserPersonalDetails = lodashGet(val, userAccountID, {});
+ callback: (value) => {
+ currentUserPersonalDetails = value?.[userAccountID] ?? {};
},
});
-let currentDate = '';
+let currentDate: OnyxEntry = '';
Onyx.connect({
key: ONYXKEYS.CURRENT_DATE,
- callback: (val) => {
- currentDate = val;
+ callback: (value) => {
+ currentDate = value;
},
});
/**
* Initialize money request info
- * @param {String} reportID to attach the transaction to
- * @param {Boolean} isFromGlobalCreate
- * @param {String} [iouRequestType] one of manual/scan/distance
+ * @param reportID to attach the transaction to
+ * @param iouRequestType one of manual/scan/distance
*/
-function startMoneyRequest_temporaryForRefactor(reportID, isFromGlobalCreate, iouRequestType = CONST.IOU.REQUEST_TYPE.MANUAL) {
+// eslint-disable-next-line @typescript-eslint/naming-convention
+function startMoneyRequest_temporaryForRefactor(reportID: string, isFromGlobalCreate: boolean, iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL) {
// Generate a brand new transactionID
const newTransactionID = CONST.IOU.OPTIMISTIC_TRANSACTION_ID;
+ // Disabling this line since currentDate can be an empty string
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const created = currentDate || format(new Date(), 'yyyy-MM-dd');
- const comment = {};
+ const comment: Comment = {};
// Add initial empty waypoints when starting a distance request
if (iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE) {
@@ -158,7 +245,7 @@ function startMoneyRequest_temporaryForRefactor(reportID, isFromGlobalCreate, io
amount: 0,
comment,
created,
- currency: lodashGet(currentUserPersonalDetails, 'localCurrencyCode', CONST.CURRENCY.USD),
+ currency: currentUserPersonalDetails.localCurrencyCode ?? CONST.CURRENCY.USD,
iouRequestType,
reportID,
transactionID: newTransactionID,
@@ -167,123 +254,88 @@ function startMoneyRequest_temporaryForRefactor(reportID, isFromGlobalCreate, io
});
}
-/**
- * @param {String} transactionID
- */
-function clearMoneyRequest(transactionID) {
+function clearMoneyRequest(transactionID: string) {
Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, null);
}
-/**
- * @param {String} transactionID
- * @param {Number} amount
- * @param {String} currency
- */
-function setMoneyRequestAmount_temporaryForRefactor(transactionID, amount, currency) {
+// eslint-disable-next-line @typescript-eslint/naming-convention
+function setMoneyRequestAmount_temporaryForRefactor(transactionID: string, amount: number, currency: string, removeOriginalCurrency = false) {
+ if (removeOriginalCurrency) {
+ Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency, originalCurrency: null});
+ return;
+ }
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency});
}
-/**
- * @param {String} transactionID
- * @param {String} created
- */
-function setMoneyRequestCreated_temporaryForRefactor(transactionID, created) {
+// eslint-disable-next-line @typescript-eslint/naming-convention
+function setMoneyRequestCreated_temporaryForRefactor(transactionID: string, created: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {created});
}
-/**
- * @param {String} transactionID
- * @param {String} currency
- */
-function setMoneyRequestCurrency_temporaryForRefactor(transactionID, currency) {
+// eslint-disable-next-line @typescript-eslint/naming-convention
+function setMoneyRequestCurrency_temporaryForRefactor(transactionID: string, currency: string, removeOriginalCurrency = false) {
+ if (removeOriginalCurrency) {
+ Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {currency, originalCurrency: null});
+ return;
+ }
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {currency});
}
-/**
- * @param {String} transactionID
- * @param {String} comment
- */
-function setMoneyRequestDescription_temporaryForRefactor(transactionID, comment) {
+// eslint-disable-next-line @typescript-eslint/naming-convention
+function setMoneyRequestOriginalCurrency_temporaryForRefactor(transactionID: string, originalCurrency: string) {
+ Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {originalCurrency});
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+function setMoneyRequestDescription_temporaryForRefactor(transactionID: string, comment: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {comment: {comment: comment.trim()}});
}
-/**
- * @param {String} transactionID
- * @param {String} merchant
- */
-function setMoneyRequestMerchant_temporaryForRefactor(transactionID, merchant) {
+// eslint-disable-next-line @typescript-eslint/naming-convention
+function setMoneyRequestMerchant_temporaryForRefactor(transactionID: string, merchant: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {merchant: merchant.trim()});
}
-/**
- * @param {String} transactionID
- * @param {String} category
- */
-function setMoneyRequestCategory_temporaryForRefactor(transactionID, category) {
+// eslint-disable-next-line @typescript-eslint/naming-convention
+function setMoneyRequestCategory_temporaryForRefactor(transactionID: string, category: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {category});
}
-/*
- * @param {String} transactionID
- */
-function resetMoneyRequestCategory_temporaryForRefactor(transactionID) {
+// eslint-disable-next-line @typescript-eslint/naming-convention
+function resetMoneyRequestCategory_temporaryForRefactor(transactionID: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {category: null});
}
-/*
- * @param {String} transactionID
- * @param {String} tag
- */
-function setMoneyRequestTag_temporaryForRefactor(transactionID, tag) {
+function setMoneyRequestTag(transactionID: string, tag: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {tag});
}
-/*
- * @param {String} transactionID
- */
-function resetMoneyRequestTag_temporaryForRefactor(transactionID) {
- Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {tag: null});
-}
-
-/**
- * @param {String} transactionID
- * @param {Boolean} billable
- */
-function setMoneyRequestBillable_temporaryForRefactor(transactionID, billable) {
+// eslint-disable-next-line @typescript-eslint/naming-convention
+function setMoneyRequestBillable_temporaryForRefactor(transactionID: string, billable: boolean) {
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {billable});
}
-/**
- * @param {String} transactionID
- * @param {Object[]} participants
- */
-function setMoneyRequestParticipants_temporaryForRefactor(transactionID, participants) {
+// eslint-disable-next-line @typescript-eslint/naming-convention
+function setMoneyRequestParticipants_temporaryForRefactor(transactionID: string, participants: Participant[]) {
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {participants});
}
-/**
- * @param {String} transactionID
- * @param {String} source
- * @param {String} filename
- * @param {Boolean} isDraft
- */
-function setMoneyRequestReceipt(transactionID, source, filename, isDraft) {
+function setMoneyRequestReceipt(transactionID: string, source: string, filename: string, isDraft: boolean) {
Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {
receipt: {source},
filename,
});
}
-/**
- * Reset money request info from the store with its initial value
- * @param {String} id
- */
+/** Reset money request info from the store with its initial value */
function resetMoneyRequestInfo(id = '') {
+ // Disabling this line since currentDate can be an empty string
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const created = currentDate || format(new Date(), CONST.DATE.FNS_FORMAT_STRING);
Onyx.merge(ONYXKEYS.IOU, {
id,
amount: 0,
- currency: lodashGet(currentUserPersonalDetails, 'localCurrencyCode', CONST.CURRENCY.USD),
+ currency: currentUserPersonalDetails.localCurrencyCode ?? CONST.CURRENCY.USD,
comment: '',
participants: [],
merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT,
@@ -298,27 +350,15 @@ function resetMoneyRequestInfo(id = '') {
});
}
-/**
- * Helper function to get the receipt error for money requests, or the generic error if there's no receipt
- *
- * @param {Object} receipt
- * @param {String} filename
- * @param {Boolean} [isScanRequest]
- * @returns {Object}
- */
-function getReceiptError(receipt, filename, isScanRequest = true) {
- return _.isEmpty(receipt) || !isScanRequest
+/** Helper function to get the receipt error for money requests, or the generic error if there's no receipt */
+function getReceiptError(receipt?: Receipt, filename?: string, isScanRequest = true): Errors | ErrorFields {
+ return isEmptyObject(receipt) || !isScanRequest
? ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage')
- : ErrorUtils.getMicroSecondOnyxErrorObject({error: CONST.IOU.RECEIPT_ERROR, source: receipt.source, filename});
+ : ErrorUtils.getMicroSecondOnyxErrorObject({error: CONST.IOU.RECEIPT_ERROR, source: receipt.source?.toString() ?? '', filename: filename ?? ''});
}
-/**
- * Return the object to update hasOutstandingChildRequest
- * @param {Object} [policy]
- * @param {Boolean} needsToBeManuallySubmitted
- * @returns {Object}
- */
-function getOutstandingChildRequest(policy, needsToBeManuallySubmitted) {
+/** Return the object to update hasOutstandingChildRequest */
+function getOutstandingChildRequest(needsToBeManuallySubmitted: boolean, policy: OnyxEntry | EmptyObject = null): OutstandingChildRequest {
if (!needsToBeManuallySubmitted) {
return {
hasOutstandingChildRequest: false,
@@ -335,49 +375,31 @@ function getOutstandingChildRequest(policy, needsToBeManuallySubmitted) {
return {};
}
-/**
- * Builds the Onyx data for a money request.
- *
- * @param {Object} chatReport
- * @param {Object} iouReport
- * @param {Object} transaction
- * @param {Object} chatCreatedAction
- * @param {Object} iouCreatedAction
- * @param {Object} iouAction
- * @param {Object} optimisticPersonalDetailListAction
- * @param {Object} reportPreviewAction
- * @param {Array} optimisticPolicyRecentlyUsedCategories
- * @param {Array} optimisticPolicyRecentlyUsedTags
- * @param {boolean} isNewChatReport
- * @param {boolean} shouldCreateNewMoneyRequestReport
- * @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts)
- * @param {Array} policyTags
- * @param {Array} policyCategories
- * @param {Boolean} needsToBeManuallySubmitted
- * @returns {Array} - An array containing the optimistic data, success data, and failure data.
- */
+/** Builds the Onyx data for a money request */
function buildOnyxDataForMoneyRequest(
- chatReport,
- iouReport,
- transaction,
- chatCreatedAction,
- iouCreatedAction,
- iouAction,
- optimisticPersonalDetailListAction,
- reportPreviewAction,
- optimisticPolicyRecentlyUsedCategories,
- optimisticPolicyRecentlyUsedTags,
- isNewChatReport,
- shouldCreateNewMoneyRequestReport,
- policy,
- policyTags,
- policyCategories,
+ chatReport: OnyxEntry,
+ iouReport: OnyxTypes.Report,
+ transaction: OnyxTypes.Transaction,
+ chatCreatedAction: OptimisticCreatedReportAction,
+ iouCreatedAction: OptimisticCreatedReportAction,
+ iouAction: OptimisticIOUReportAction,
+ optimisticPersonalDetailListAction: OnyxTypes.PersonalDetailsList,
+ reportPreviewAction: ReportAction,
+ optimisticPolicyRecentlyUsedCategories: string[],
+ optimisticPolicyRecentlyUsedTags: OnyxTypes.RecentlyUsedTags,
+ isNewChatReport: boolean,
+ shouldCreateNewMoneyRequestReport: boolean,
+ policy?: OnyxTypes.Policy | EmptyObject,
+ policyTags?: OnyxTypes.PolicyTags,
+ policyCategories?: OnyxTypes.PolicyCategories,
needsToBeManuallySubmitted = true,
-) {
+): [OnyxUpdate[], OnyxUpdate[], OnyxUpdate[]] {
const isScanRequest = TransactionUtils.isScanRequest(transaction);
const outstandingChildRequest = getOutstandingChildRequest(needsToBeManuallySubmitted, policy);
- const optimisticData = [
- {
+ const optimisticData: OnyxUpdate[] = [];
+
+ if (chatReport) {
+ optimisticData.push({
// Use SET for new reports because it doesn't exist yet, is faster and we need the data to be available when we navigate to the chat page
onyxMethod: isNewChatReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`,
@@ -389,14 +411,17 @@ function buildOnyxDataForMoneyRequest(
...outstandingChildRequest,
...(isNewChatReport ? {pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}} : {}),
},
- },
+ });
+ }
+
+ optimisticData.push(
{
onyxMethod: shouldCreateNewMoneyRequestReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
value: {
...iouReport,
- lastMessageText: iouAction.message[0].text,
- lastMessageHtml: iouAction.message[0].html,
+ lastMessageText: iouAction.message?.[0].text,
+ lastMessageHtml: iouAction.message?.[0].html,
pendingFields: {
...(shouldCreateNewMoneyRequestReport ? {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD} : {preview: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
},
@@ -407,22 +432,38 @@ function buildOnyxDataForMoneyRequest(
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`,
value: transaction,
},
- {
- onyxMethod: isNewChatReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`,
- value: {
- ...(isNewChatReport ? {[chatCreatedAction.reportActionID]: chatCreatedAction} : {}),
- [reportPreviewAction.reportActionID]: reportPreviewAction,
- },
- },
- {
- onyxMethod: shouldCreateNewMoneyRequestReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
- value: {
- ...(shouldCreateNewMoneyRequestReport ? {[iouCreatedAction.reportActionID]: iouCreatedAction} : {}),
- [iouAction.reportActionID]: iouAction,
- },
- },
+ isNewChatReport
+ ? {
+ onyxMethod: Onyx.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`,
+ value: {
+ [chatCreatedAction.reportActionID]: chatCreatedAction,
+ [reportPreviewAction.reportActionID]: reportPreviewAction,
+ },
+ }
+ : {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`,
+ value: {
+ [reportPreviewAction.reportActionID]: reportPreviewAction,
+ },
+ },
+ shouldCreateNewMoneyRequestReport
+ ? {
+ onyxMethod: Onyx.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
+ value: {
+ [iouCreatedAction.reportActionID]: iouCreatedAction as OnyxTypes.ReportAction,
+ [iouAction.reportActionID]: iouAction as OnyxTypes.ReportAction,
+ },
+ }
+ : {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
+ value: {
+ [iouAction.reportActionID]: iouAction as OnyxTypes.ReportAction,
+ },
+ },
// Remove the temporary transaction used during the creation flow
{
@@ -430,9 +471,9 @@ function buildOnyxDataForMoneyRequest(
key: `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${CONST.IOU.OPTIMISTIC_TRANSACTION_ID}`,
value: null,
},
- ];
+ );
- if (!_.isEmpty(optimisticPolicyRecentlyUsedCategories)) {
+ if (optimisticPolicyRecentlyUsedCategories.length) {
optimisticData.push({
onyxMethod: Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${iouReport.policyID}`,
@@ -440,7 +481,7 @@ function buildOnyxDataForMoneyRequest(
});
}
- if (!_.isEmpty(optimisticPolicyRecentlyUsedTags)) {
+ if (!isEmptyObject(optimisticPolicyRecentlyUsedTags)) {
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${iouReport.policyID}`,
@@ -448,7 +489,7 @@ function buildOnyxDataForMoneyRequest(
});
}
- if (!_.isEmpty(optimisticPersonalDetailListAction)) {
+ if (!isEmptyObject(optimisticPersonalDetailListAction)) {
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
@@ -456,19 +497,20 @@ function buildOnyxDataForMoneyRequest(
});
}
- const successData = [
- ...(isNewChatReport
- ? [
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`,
- value: {
- pendingFields: null,
- errorFields: null,
- },
- },
- ]
- : []),
+ const successData: OnyxUpdate[] = [];
+
+ if (isNewChatReport) {
+ successData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`,
+ value: {
+ pendingFields: null,
+ errorFields: null,
+ },
+ });
+ }
+
+ successData.push(
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
@@ -488,7 +530,7 @@ function buildOnyxDataForMoneyRequest(
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`,
value: {
...(isNewChatReport
? {
@@ -521,17 +563,17 @@ function buildOnyxDataForMoneyRequest(
},
},
},
- ];
+ );
- const failureData = [
+ const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`,
value: {
- iouReportID: chatReport.iouReportID,
- lastReadTime: chatReport.lastReadTime,
+ iouReportID: chatReport?.iouReportID,
+ lastReadTime: chatReport?.lastReadTime,
pendingFields: null,
- hasOutstandingChildRequest: chatReport.hasOutstandingChildRequest,
+ hasOutstandingChildRequest: chatReport?.hasOutstandingChildRequest,
...(isNewChatReport
? {
errorFields: {
@@ -570,12 +612,14 @@ function buildOnyxDataForMoneyRequest(
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`,
value: {
...(isNewChatReport
? {
[chatCreatedAction.reportActionID]: {
- errors: getReceiptError(transaction.receipt, transaction.filename || transaction.receipt.filename, isScanRequest),
+ // Disabling this line since transaction.filename can be an empty string
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ errors: getReceiptError(transaction?.receipt, transaction.filename || transaction.receipt?.filename, isScanRequest),
},
[reportPreviewAction.reportActionID]: {
errors: ErrorUtils.getMicroSecondOnyxError(null),
@@ -584,7 +628,9 @@ function buildOnyxDataForMoneyRequest(
: {
[reportPreviewAction.reportActionID]: {
created: reportPreviewAction.created,
- errors: getReceiptError(transaction.receipt, transaction.filename || transaction.receipt.filename, isScanRequest),
+ // Disabling this line since transaction.filename can be an empty string
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ errors: getReceiptError(transaction?.receipt, transaction.filename || transaction.receipt?.filename, isScanRequest),
},
}),
},
@@ -596,7 +642,9 @@ function buildOnyxDataForMoneyRequest(
...(shouldCreateNewMoneyRequestReport
? {
[iouCreatedAction.reportActionID]: {
- errors: getReceiptError(transaction.receipt, transaction.filename || transaction.receipt.filename, isScanRequest),
+ // Disabling this line since transaction.filename can be an empty string
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ errors: getReceiptError(transaction.receipt, transaction.filename || transaction.receipt?.filename, isScanRequest),
},
[iouAction.reportActionID]: {
errors: ErrorUtils.getMicroSecondOnyxError(null),
@@ -604,7 +652,9 @@ function buildOnyxDataForMoneyRequest(
}
: {
[iouAction.reportActionID]: {
- errors: getReceiptError(transaction.receipt, transaction.filename || transaction.receipt.filename, isScanRequest),
+ // Disabling this line since transaction.filename can be an empty string
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ errors: getReceiptError(transaction.receipt, transaction.filename || transaction.receipt?.filename, isScanRequest),
},
}),
},
@@ -612,11 +662,11 @@ function buildOnyxDataForMoneyRequest(
];
// Policy won't be set for P2P cases for which we don't need to compute violations
- if (!policy || !policy.id) {
+ if (!policy?.id) {
return [optimisticData, successData, failureData];
}
- const violationsOnyxData = ViolationsUtils.getViolationsOnyxData(transaction, [], policy.requiresTag, policyTags, policy.requiresCategory, policyCategories);
+ const violationsOnyxData = ViolationsUtils.getViolationsOnyxData(transaction, [], !!policy.requiresTag, policyTags ?? {}, !!policy.requiresCategory, policyCategories ?? {});
if (violationsOnyxData) {
optimisticData.push(violationsOnyxData);
@@ -633,71 +683,39 @@ function buildOnyxDataForMoneyRequest(
/**
* Gathers all the data needed to make a money request. It attempts to find existing reports, iouReports, and receipts. If it doesn't find them, then
* it creates optimistic versions of them and uses those instead
- *
- * @param {Object} parentChatReport
- * @param {Object} participant
- * @param {String} comment
- * @param {Number} amount
- * @param {String} currency
- * @param {String} created
- * @param {String} merchant
- * @param {Number} [payeeAccountID]
- * @param {String} [payeeEmail]
- * @param {Object} [receipt]
- * @param {String} [existingTransactionID]
- * @param {String} [category]
- * @param {String} [tag]
- * @param {Boolean} [billable]
- * @param {Object} [policy]
- * @param {Object} [policyTags]
- * @param {Object} [policyCategories]
- * @param {Number} [moneyRequestReportID] - If user requests money via the report composer on some money request report, we always add a request to that specific report.
- * @returns {Object} data
- * @returns {String} data.payerEmail
- * @returns {Object} data.iouReport
- * @returns {Object} data.chatReport
- * @returns {Object} data.transaction
- * @returns {Object} data.iouAction
- * @returns {Object} data.createdChatReportActionID
- * @returns {Object} data.createdIOUReportActionID
- * @returns {Object} data.reportPreviewAction
- * @returns {Object} data.onyxData
- * @returns {Object} data.onyxData.optimisticData
- * @returns {Object} data.onyxData.successData
- * @returns {Object} data.onyxData.failureData
*/
function getMoneyRequestInformation(
- parentChatReport,
- participant,
- comment,
- amount,
- currency,
- created,
- merchant,
+ parentChatReport: OnyxEntry | EmptyObject,
+ participant: Participant,
+ comment: string,
+ amount: number,
+ currency: string,
+ created: string,
+ merchant: string,
+ receipt: Receipt | undefined,
+ existingTransactionID: string | undefined,
+ category: string | undefined,
+ tag: string | undefined,
+ billable: boolean | undefined,
+ policy: OnyxTypes.Policy | EmptyObject | undefined,
+ policyTags: OnyxTypes.PolicyTags | undefined,
+ policyCategories: OnyxTypes.PolicyCategories | undefined,
payeeAccountID = userAccountID,
payeeEmail = currentUserEmail,
- receipt = undefined,
- existingTransactionID = undefined,
- category = undefined,
- tag = undefined,
- billable = undefined,
- policy = undefined,
- policyTags = undefined,
- policyCategories = undefined,
- moneyRequestReportID = 0,
-) {
- const payerEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login);
+ moneyRequestReportID = '',
+): MoneyRequestInformation {
+ const payerEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login ?? '');
const payerAccountID = Number(participant.accountID);
const isPolicyExpenseChat = participant.isPolicyExpenseChat;
// STEP 1: Get existing chat report OR build a new optimistic one
let isNewChatReport = false;
- let chatReport = lodashGet(parentChatReport, 'reportID', null) ? parentChatReport : null;
+ let chatReport = !isEmptyObject(parentChatReport) && parentChatReport?.reportID ? parentChatReport : null;
// If this is a policyExpenseChat, the chatReport must exist and we can get it from Onyx.
// report is null if the flow is initiated from the global create menu. However, participant always stores the reportID if it exists, which is the case for policyExpenseChats
if (!chatReport && isPolicyExpenseChat) {
- chatReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${participant.reportID}`];
+ chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${participant.reportID}`] ?? null;
}
if (!chatReport) {
@@ -712,22 +730,22 @@ function getMoneyRequestInformation(
// STEP 2: Get the money request report. If the moneyRequestReportID has been provided, we want to add the transaction to this specific report.
// If no such reportID has been provided, let's use the chatReport.iouReportID property. In case that is not present, build a new optimistic money request report.
- let iouReport = null;
+ let iouReport: OnyxEntry = null;
const shouldCreateNewMoneyRequestReport = !moneyRequestReportID && (!chatReport.iouReportID || ReportUtils.hasIOUWaitingOnCurrentUserBankAccount(chatReport));
- if (moneyRequestReportID > 0) {
- iouReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${moneyRequestReportID}`];
+ if (moneyRequestReportID) {
+ iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${moneyRequestReportID}`] ?? null;
} else if (!shouldCreateNewMoneyRequestReport) {
- iouReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`];
+ iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`] ?? null;
}
// Check if the Scheduled Submit is enabled in case of expense report
let needsToBeManuallySubmitted = true;
let isFromPaidPolicy = false;
if (isPolicyExpenseChat) {
- isFromPaidPolicy = PolicyUtils.isPaidGroupPolicy(policy);
+ isFromPaidPolicy = PolicyUtils.isPaidGroupPolicy(policy ?? null);
// If the scheduled submit is turned off on the policy, user needs to manually submit the report which is indicated by GBR in LHN
- needsToBeManuallySubmitted = isFromPaidPolicy && !(lodashGet(policy, 'harvesting.enabled', policy.isHarvestingEnabled) || false);
+ needsToBeManuallySubmitted = isFromPaidPolicy && !(policy?.harvesting?.enabled ?? policy?.isHarvestingEnabled);
// If the linked expense report on paid policy is not draft, we need to create a new draft expense report
if (iouReport && isFromPaidPolicy && !ReportUtils.isDraftExpenseReport(iouReport)) {
@@ -738,7 +756,7 @@ function getMoneyRequestInformation(
if (iouReport) {
if (isPolicyExpenseChat) {
iouReport = {...iouReport};
- if (lodashGet(iouReport, 'currency') === currency) {
+ if (iouReport?.currency === currency && typeof iouReport.total === 'number') {
// Because of the Expense reports are stored as negative values, we substract the total from the amount
iouReport.total -= amount;
}
@@ -747,16 +765,16 @@ function getMoneyRequestInformation(
}
} else {
iouReport = isPolicyExpenseChat
- ? ReportUtils.buildOptimisticExpenseReport(chatReport.reportID, chatReport.policyID, payeeAccountID, amount, currency)
+ ? ReportUtils.buildOptimisticExpenseReport(chatReport.reportID, chatReport.policyID ?? '', payeeAccountID, amount, currency)
: ReportUtils.buildOptimisticIOUReport(payeeAccountID, payerAccountID, amount, chatReport.reportID, currency);
}
// STEP 3: Build optimistic receipt and transaction
- const receiptObject = {};
+ const receiptObject: Receipt = {};
let filename;
- if (receipt && receipt.source) {
+ if (receipt?.source) {
receiptObject.source = receipt.source;
- receiptObject.state = receipt.state || CONST.IOU.RECEIPT_STATE.SCANREADY;
+ receiptObject.state = receipt.state ?? CONST.IOU.RECEIPT_STATE.SCANREADY;
filename = receipt.name;
}
let optimisticTransaction = TransactionUtils.buildOptimisticTransaction(
@@ -787,7 +805,7 @@ function getMoneyRequestInformation(
// to remind me to do this.
const existingTransaction = allTransactionDrafts[`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${CONST.IOU.OPTIMISTIC_TRANSACTION_ID}`];
if (existingTransaction && existingTransaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE) {
- optimisticTransaction = fastMerge(existingTransaction, optimisticTransaction);
+ optimisticTransaction = fastMerge(existingTransaction, optimisticTransaction, false);
}
// STEP 4: Build optimistic reportActions. We need:
@@ -806,7 +824,7 @@ function getMoneyRequestInformation(
comment,
[participant],
optimisticTransaction.transactionID,
- '',
+ undefined,
iouReport.reportID,
false,
false,
@@ -833,12 +851,14 @@ function getMoneyRequestInformation(
[payerAccountID]: {
accountID: payerAccountID,
avatar: UserUtils.getDefaultAvatarURL(payerAccountID),
+ // Disabling this line since participant.displayName can be an empty string
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
displayName: LocalePhoneNumber.formatPhoneNumber(participant.displayName || payerEmail),
login: participant.login,
isOptimisticPersonalDetail: true,
},
}
- : undefined;
+ : {};
// STEP 5: Build Onyx Data
const [optimisticData, successData, failureData] = buildOnyxDataForMoneyRequest(
@@ -867,8 +887,8 @@ function getMoneyRequestInformation(
chatReport,
transaction: optimisticTransaction,
iouAction,
- createdChatReportActionID: isNewChatReport ? optimisticCreatedActionForChat.reportActionID : 0,
- createdIOUReportActionID: shouldCreateNewMoneyRequestReport ? optimisticCreatedActionForIOU.reportActionID : 0,
+ createdChatReportActionID: isNewChatReport ? optimisticCreatedActionForChat.reportActionID : '0',
+ createdIOUReportActionID: shouldCreateNewMoneyRequestReport ? optimisticCreatedActionForIOU.reportActionID : '0',
reportPreviewAction,
onyxData: {
optimisticData,
@@ -878,33 +898,31 @@ function getMoneyRequestInformation(
};
}
-/**
- * Requests money based on a distance (eg. mileage from a map)
- *
- * @param {Object} report
- * @param {Object} participant
- * @param {String} comment
- * @param {String} created
- * @param {String} [category]
- * @param {String} [tag]
- * @param {Number} amount
- * @param {String} currency
- * @param {String} merchant
- * @param {Boolean} [billable]
- * @param {Object} validWaypoints
- * @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts)
- * @param {Array} policyTags
- * @param {Array} policyCategories
- */
-function createDistanceRequest(report, participant, comment, created, category, tag, amount, currency, merchant, billable, validWaypoints, policy, policyTags, policyCategories) {
+/** Requests money based on a distance (eg. mileage from a map) */
+function createDistanceRequest(
+ report: OnyxTypes.Report,
+ participant: Participant,
+ comment: string,
+ created: string,
+ category: string | undefined,
+ tag: string | undefined,
+ amount: number,
+ currency: string,
+ merchant: string,
+ billable: boolean | undefined,
+ validWaypoints: WaypointCollection,
+ policy: OnyxTypes.Policy | EmptyObject | undefined,
+ policyTags: OnyxTypes.PolicyTags,
+ policyCategories: OnyxTypes.PolicyCategories,
+) {
// If the report is an iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function
const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
const currentChatReport = isMoneyRequestReport ? ReportUtils.getReport(report.chatReportID) : report;
- const moneyRequestReportID = isMoneyRequestReport ? report.reportID : 0;
+ const moneyRequestReportID = isMoneyRequestReport ? report.reportID : '';
const currentCreated = DateUtils.enrichMoneyRequestTimestamp(created);
- const optimisticReceipt = {
- source: ReceiptGeneric,
+ const optimisticReceipt: Receipt = {
+ source: ReceiptGeneric as ReceiptSource,
state: CONST.IOU.RECEIPT_STATE.OPEN,
};
const {iouReport, chatReport, transaction, iouAction, createdChatReportActionID, createdIOUReportActionID, reportPreviewAction, onyxData} = getMoneyRequestInformation(
@@ -915,8 +933,6 @@ function createDistanceRequest(report, participant, comment, created, category,
currency,
currentCreated,
merchant,
- userAccountID,
- currentUserEmail,
optimisticReceipt,
undefined,
category,
@@ -925,71 +941,76 @@ function createDistanceRequest(report, participant, comment, created, category,
policy,
policyTags,
policyCategories,
+ userAccountID,
+ currentUserEmail,
moneyRequestReportID,
);
- API.write(
- 'CreateDistanceRequest',
- {
- comment,
- iouReportID: iouReport.reportID,
- chatReportID: chatReport.reportID,
- transactionID: transaction.transactionID,
- reportActionID: iouAction.reportActionID,
- createdChatReportActionID,
- createdIOUReportActionID,
- reportPreviewReportActionID: reportPreviewAction.reportActionID,
- waypoints: JSON.stringify(validWaypoints),
- created: currentCreated,
- category,
- tag,
- billable,
- },
- onyxData,
- );
+
+ const parameters: CreateDistanceRequestParams = {
+ comment,
+ iouReportID: iouReport.reportID,
+ chatReportID: chatReport.reportID,
+ transactionID: transaction.transactionID,
+ reportActionID: iouAction.reportActionID,
+ createdChatReportActionID,
+ createdIOUReportActionID,
+ reportPreviewReportActionID: reportPreviewAction.reportActionID,
+ waypoints: JSON.stringify(validWaypoints),
+ created: currentCreated,
+ category,
+ tag,
+ billable,
+ };
+
+ API.write(WRITE_COMMANDS.CREATE_DISTANCE_REQUEST, parameters, onyxData);
Navigation.dismissModal(isMoneyRequestReport ? report.reportID : chatReport.reportID);
Report.notifyNewAction(chatReport.reportID, userAccountID);
}
/**
- * @param {String} transactionID
- * @param {String} transactionThreadReportID
- * @param {Object} transactionChanges
- * @param {String} [transactionChanges.created] Present when updated the date field
- * @param {Boolean} onlyIncludeChangedFields
- * When 'true', then the returned params will only include the transaction details for the fields that were changed.
- * When `false`, then the returned params will include all the transaction details, regardless of which fields were changed.
- * This setting is necessary while the UpdateDistanceRequest API is refactored to be fully 1:1:1 in https://github.com/Expensify/App/issues/28358
- * @returns {object}
- */
-function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, onlyIncludeChangedFields) {
- const optimisticData = [];
- const successData = [];
- const failureData = [];
+ * @param transactionChanges
+ * @param [transactionChanges.created] Present when updated the date field
+ * @param onlyIncludeChangedFields
+ * When 'true', then the returned params will only include the transaction details for the fields that were changed.
+ * When `false`, then the returned params will include all the transaction details, regardless of which fields were changed.
+ * This setting is necessary while the UpdateDistanceRequest API is refactored to be fully 1:1:1 in https://github.com/Expensify/App/issues/28358
+ */
+function getUpdateMoneyRequestParams(
+ transactionID: string,
+ transactionThreadReportID: string,
+ transactionChanges: TransactionChanges,
+ onlyIncludeChangedFields: boolean,
+): UpdateMoneyRequestData {
+ const optimisticData: OnyxUpdate[] = [];
+ const successData: OnyxUpdate[] = [];
+ const failureData: OnyxUpdate[] = [];
// Step 1: Set any "pending fields" (ones updated while the user was offline) to have error messages in the failureData
- const pendingFields = _.mapObject(transactionChanges, () => CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
- const clearedPendingFields = _.mapObject(transactionChanges, () => null);
- const errorFields = _.mapObject(pendingFields, () => ({
- [DateUtils.getMicroseconds()]: Localize.translateLocal('iou.error.genericEditFailureMessage'),
- }));
+ const pendingFields = Object.fromEntries(Object.keys(transactionChanges).map((key) => [key, CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE]));
+ const clearedPendingFields = Object.fromEntries(Object.keys(transactionChanges).map((key) => [key, null]));
+ const errorFields = Object.fromEntries(Object.keys(pendingFields).map((key) => [key, {[DateUtils.getMicroseconds()]: Localize.translateLocal('iou.error.genericEditFailureMessage')}]));
// Step 2: Get all the collections being updated
- const transactionThread = allReports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`];
- const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
- const iouReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThread.parentReportID}`];
+ const transactionThread = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`] ?? null;
+ const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
+ const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThread?.parentReportID}`] ?? null;
const isFromExpenseReport = ReportUtils.isExpenseReport(iouReport);
const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction);
- const updatedTransaction = TransactionUtils.getUpdatedTransaction(transaction, transactionChanges, isFromExpenseReport);
+ const updatedTransaction = transaction ? TransactionUtils.getUpdatedTransaction(transaction, transactionChanges, isFromExpenseReport) : null;
const transactionDetails = ReportUtils.getTransactionDetails(updatedTransaction);
- // This needs to be a JSON string since we're sending this to the MapBox API
- transactionDetails.waypoints = JSON.stringify(transactionDetails.waypoints);
+ if (transactionDetails?.waypoints) {
+ // This needs to be a JSON string since we're sending this to the MapBox API
+ transactionDetails.waypoints = JSON.stringify(transactionDetails.waypoints);
+ }
- const dataToIncludeInParams = onlyIncludeChangedFields ? _.pick(transactionDetails, _.keys(transactionChanges)) : transactionDetails;
+ const dataToIncludeInParams: Partial | undefined = onlyIncludeChangedFields
+ ? Object.fromEntries(Object.entries(transactionDetails ?? {}).filter(([key]) => Object.keys(transactionChanges).includes(key)))
+ : transactionDetails;
- const params = {
+ const params: UpdateMoneyRequestParams = {
...dataToIncludeInParams,
- reportID: iouReport.reportID,
+ reportID: iouReport?.reportID,
transactionID,
};
@@ -997,30 +1018,30 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t
// We don't create a modified report action if we're updating the waypoints,
// since there isn't actually any optimistic data we can create for them and the report action is created on the server
// with the response from the MapBox API
- if (!_.has(transactionChanges, 'waypoints')) {
+ if (!('waypoints' in transactionChanges)) {
const updatedReportAction = ReportUtils.buildOptimisticModifiedExpenseReportAction(transactionThread, transaction, transactionChanges, isFromExpenseReport);
params.reportActionID = updatedReportAction.reportActionID;
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread?.reportID}`,
value: {
- [updatedReportAction.reportActionID]: updatedReportAction,
+ [updatedReportAction.reportActionID]: updatedReportAction as OnyxTypes.ReportAction,
},
});
successData.push({
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread?.reportID}`,
value: {
[updatedReportAction.reportActionID]: {pendingAction: null},
},
});
failureData.push({
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread?.reportID}`,
value: {
[updatedReportAction.reportActionID]: {
- ...updatedReportAction,
+ ...(updatedReportAction as OnyxTypes.ReportAction),
errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericEditFailureMessage'),
},
},
@@ -1030,23 +1051,25 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t
// Should only update if the transaction matches the currency of the report, else we wait for the update
// from the server with the currency conversion
let updatedMoneyRequestReport = {...iouReport};
- if (updatedTransaction.currency === iouReport.currency && updatedTransaction.modifiedAmount) {
+ if (updatedTransaction?.currency === iouReport?.currency && updatedTransaction?.modifiedAmount) {
const diff = TransactionUtils.getAmount(transaction, true) - TransactionUtils.getAmount(updatedTransaction, true);
- if (ReportUtils.isExpenseReport(iouReport)) {
+ if (ReportUtils.isExpenseReport(iouReport) && typeof updatedMoneyRequestReport.total === 'number') {
updatedMoneyRequestReport.total += diff;
} else {
- updatedMoneyRequestReport = IOUUtils.updateIOUOwnerAndTotal(iouReport, updatedReportAction.actorAccountID, diff, TransactionUtils.getCurrency(transaction), false);
+ updatedMoneyRequestReport = iouReport
+ ? IOUUtils.updateIOUOwnerAndTotal(iouReport, updatedReportAction.actorAccountID ?? -1, diff, TransactionUtils.getCurrency(transaction), false)
+ : {};
}
updatedMoneyRequestReport.cachedTotal = CurrencyUtils.convertToDisplayString(updatedMoneyRequestReport.total, updatedTransaction.currency);
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`,
value: updatedMoneyRequestReport,
});
successData.push({
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`,
value: {pendingAction: null},
});
}
@@ -1059,54 +1082,53 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t
value: {
...updatedTransaction,
pendingFields,
- isLoading: _.has(transactionChanges, 'waypoints'),
+ isLoading: 'waypoints' in transactionChanges,
errorFields: null,
},
});
- if (isScanning && (_.has(transactionChanges, 'amount') || _.has(transactionChanges, 'currency'))) {
+ if (isScanning && ('amount' in transactionChanges || 'currency' in transactionChanges)) {
optimisticData.push(
- ...[
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
- value: {
- [transactionThread.parentReportActionID]: {
- whisperedToAccountIDs: [],
- },
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`,
+ value: {
+ [transactionThread?.parentReportActionID ?? '']: {
+ whisperedToAccountIDs: [],
},
},
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.parentReportID}`,
- value: {
- [iouReport.parentReportActionID]: {
- whisperedToAccountIDs: [],
- },
+ },
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.parentReportID}`,
+ value: {
+ [iouReport?.parentReportActionID ?? '']: {
+ whisperedToAccountIDs: [],
},
},
- ],
+ },
);
}
+
// Update recently used categories if the category is changed
- if (_.has(transactionChanges, 'category')) {
- const optimisticPolicyRecentlyUsedCategories = Policy.buildOptimisticPolicyRecentlyUsedCategories(iouReport.policyID, transactionChanges.category);
- if (!_.isEmpty(optimisticPolicyRecentlyUsedCategories)) {
+ if ('category' in transactionChanges) {
+ const optimisticPolicyRecentlyUsedCategories = Policy.buildOptimisticPolicyRecentlyUsedCategories(iouReport?.policyID, transactionChanges.category);
+ if (optimisticPolicyRecentlyUsedCategories.length) {
optimisticData.push({
onyxMethod: Onyx.METHOD.SET,
- key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${iouReport.policyID}`,
+ key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${iouReport?.policyID}`,
value: optimisticPolicyRecentlyUsedCategories,
});
}
}
// Update recently used categories if the tag is changed
- if (_.has(transactionChanges, 'tag')) {
- const optimisticPolicyRecentlyUsedTags = Policy.buildOptimisticPolicyRecentlyUsedTags(iouReport.policyID, transactionChanges.tag);
- if (!_.isEmpty(optimisticPolicyRecentlyUsedTags)) {
+ if ('tag' in transactionChanges) {
+ const optimisticPolicyRecentlyUsedTags = Policy.buildOptimisticPolicyRecentlyUsedTags(iouReport?.policyID, transactionChanges.tag);
+ if (!isEmptyObject(optimisticPolicyRecentlyUsedTags)) {
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${iouReport.policyID}`,
+ key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${iouReport?.policyID}`,
value: optimisticPolicyRecentlyUsedTags,
});
}
@@ -1123,7 +1145,7 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t
},
});
- if (_.has(transactionChanges, 'waypoints')) {
+ if ('waypoints' in transactionChanges) {
// Delete the draft transaction when editing waypoints when the server responds successfully and there are no errors
successData.push({
onyxMethod: Onyx.METHOD.SET,
@@ -1143,12 +1165,14 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t
},
});
- // Reset the iouReport to it's original state
- failureData.push({
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
- value: iouReport,
- });
+ if (iouReport) {
+ // Reset the iouReport to it's original state
+ failureData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
+ value: iouReport,
+ });
+ }
return {
params,
@@ -1156,166 +1180,95 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t
};
}
-/**
- * Updates the created date of a money request
- *
- * @param {String} transactionID
- * @param {String} transactionThreadReportID
- * @param {String} val
- */
-function updateMoneyRequestDate(transactionID, transactionThreadReportID, val) {
- const transactionChanges = {
- created: val,
+/** Updates the created date of a money request */
+function updateMoneyRequestDate(transactionID: string, transactionThreadReportID: string, value: string) {
+ const transactionChanges: TransactionChanges = {
+ created: value,
};
const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true);
- API.write('UpdateMoneyRequestDate', params, onyxData);
+ API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DATE, params, onyxData);
}
-/**
- * Updates the billable field of a money request
- *
- * @param {String} transactionID
- * @param {String} transactionThreadReportID
- * @param {Boolean} val
- */
-function updateMoneyRequestBillable(transactionID, transactionThreadReportID, val) {
- const transactionChanges = {
- billable: val,
+/** Updates the billable field of a money request */
+function updateMoneyRequestBillable(transactionID: string, transactionThreadReportID: string, value: boolean) {
+ const transactionChanges: TransactionChanges = {
+ billable: value,
};
const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true);
- API.write('UpdateMoneyRequestBillable', params, onyxData);
+ API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_BILLABLE, params, onyxData);
}
-/**
- * Updates the merchant field of a money request
- *
- * @param {String} transactionID
- * @param {Number} transactionThreadReportID
- * @param {String} val
- */
-function updateMoneyRequestMerchant(transactionID, transactionThreadReportID, val) {
- const transactionChanges = {
- merchant: val,
+/** Updates the merchant field of a money request */
+function updateMoneyRequestMerchant(transactionID: string, transactionThreadReportID: string, value: string) {
+ const transactionChanges: TransactionChanges = {
+ merchant: value,
};
const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true);
- API.write('UpdateMoneyRequestMerchant', params, onyxData);
+ API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_MERCHANT, params, onyxData);
}
-/**
- * Updates the tag of a money request
- *
- * @param {String} transactionID
- * @param {Number} transactionThreadReportID
- * @param {String} tag
- */
-function updateMoneyRequestTag(transactionID, transactionThreadReportID, tag) {
- const transactionChanges = {
+/** Updates the tag of a money request */
+function updateMoneyRequestTag(transactionID: string, transactionThreadReportID: string, tag: string) {
+ const transactionChanges: TransactionChanges = {
tag,
};
const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true);
- API.write('UpdateMoneyRequestTag', params, onyxData);
+ API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_TAG, params, onyxData);
}
-/**
- * Updates the waypoints of a distance money request
- *
- * @param {String} transactionID
- * @param {Number} transactionThreadReportID
- * @param {Object} waypoints
- */
-function updateMoneyRequestDistance(transactionID, transactionThreadReportID, waypoints) {
- const transactionChanges = {
+/** Updates the waypoints of a distance money request */
+function updateMoneyRequestDistance(transactionID: string, transactionThreadReportID: string, waypoints: WaypointCollection) {
+ const transactionChanges: TransactionChanges = {
waypoints,
};
const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true);
- API.write('UpdateMoneyRequestDistance', params, onyxData);
+ API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DISTANCE, params, onyxData);
}
-/**
- * Updates the category of a money request
- *
- * @param {String} transactionID
- * @param {Number} transactionThreadReportID
- * @param {String} category
- */
-function updateMoneyRequestCategory(transactionID, transactionThreadReportID, category) {
- const transactionChanges = {
+/** Updates the category of a money request */
+function updateMoneyRequestCategory(transactionID: string, transactionThreadReportID: string, category: string) {
+ const transactionChanges: TransactionChanges = {
category,
};
const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true);
- API.write('UpdateMoneyRequestCategory', params, onyxData);
+ API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_CATEGORY, params, onyxData);
}
-/**
- * Updates the description of a money request
- *
- * @param {String} transactionID
- * @param {Number} transactionThreadReportID
- * @param {String} comment
- */
-function updateMoneyRequestDescription(transactionID, transactionThreadReportID, comment) {
- const transactionChanges = {
+/** Updates the description of a money request */
+function updateMoneyRequestDescription(transactionID: string, transactionThreadReportID: string, comment: string) {
+ const transactionChanges: TransactionChanges = {
comment,
};
const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true);
- API.write('UpdateMoneyRequestDescription', params, onyxData);
+ API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DESCRIPTION, params, onyxData);
}
-/**
- * Edits an existing distance request
- *
- * @param {String} transactionID
- * @param {String} transactionThreadReportID
- * @param {Object} transactionChanges
- * @param {String} [transactionChanges.created]
- * @param {Number} [transactionChanges.amount]
- * @param {Object} [transactionChanges.comment]
- * @param {Object} [transactionChanges.waypoints]
- *
- */
-function updateDistanceRequest(transactionID, transactionThreadReportID, transactionChanges) {
+/** Edits an existing distance request */
+function updateDistanceRequest(transactionID: string, transactionThreadReportID: string, transactionChanges: TransactionChanges) {
const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, false);
- API.write('UpdateDistanceRequest', params, onyxData);
+ API.write(WRITE_COMMANDS.UPDATE_DISTANCE_REQUEST, params, onyxData);
}
/**
* Request money from another user
- *
- * @param {Object} report
- * @param {Number} amount - always in the smallest unit of the currency
- * @param {String} currency
- * @param {String} created
- * @param {String} merchant
- * @param {String} payeeEmail
- * @param {Number} payeeAccountID
- * @param {Object} participant
- * @param {String} comment
- * @param {Object} [receipt]
- * @param {String} [category]
- * @param {String} [tag]
- * @param {String} [taxCode]
- * @param {Number} [taxAmount]
- * @param {Boolean} [billable]
- * @param {Object} [policy]
- * @param {Object} [policyTags]
- * @param {Object} [policyCategories]
+ * @param amount - always in the smallest unit of the currency
*/
function requestMoney(
- report,
- amount,
- currency,
- created,
- merchant,
- payeeEmail,
- payeeAccountID,
- participant,
- comment,
- receipt = undefined,
- category = undefined,
- tag = undefined,
+ report: OnyxTypes.Report,
+ amount: number,
+ currency: string,
+ created: string,
+ merchant: string,
+ payeeEmail: string,
+ payeeAccountID: number,
+ participant: Participant,
+ comment: string,
+ receipt: Receipt,
+ category?: string,
+ tag?: string,
taxCode = '',
taxAmount = 0,
- billable = undefined,
+ billable?: boolean,
policy = undefined,
policyTags = undefined,
policyCategories = undefined,
@@ -1323,7 +1276,7 @@ function requestMoney(
// If the report is iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function
const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
const currentChatReport = isMoneyRequestReport ? ReportUtils.getReport(report.chatReportID) : report;
- const moneyRequestReportID = isMoneyRequestReport ? report.reportID : 0;
+ const moneyRequestReportID = isMoneyRequestReport ? report.reportID : '';
const currentCreated = DateUtils.enrichMoneyRequestTimestamp(created);
const {payerAccountID, payerEmail, iouReport, chatReport, transaction, iouAction, createdChatReportActionID, createdIOUReportActionID, reportPreviewAction, onyxData} =
getMoneyRequestInformation(
@@ -1334,8 +1287,6 @@ function requestMoney(
currency,
currentCreated,
merchant,
- payeeAccountID,
- payeeEmail,
receipt,
undefined,
category,
@@ -1344,37 +1295,37 @@ function requestMoney(
policy,
policyTags,
policyCategories,
+ payeeAccountID,
+ payeeEmail,
moneyRequestReportID,
);
const activeReportID = isMoneyRequestReport ? report.reportID : chatReport.reportID;
- API.write(
- 'RequestMoney',
- {
- debtorEmail: payerEmail,
- debtorAccountID: payerAccountID,
- amount,
- currency,
- comment,
- created: currentCreated,
- merchant,
- iouReportID: iouReport.reportID,
- chatReportID: chatReport.reportID,
- transactionID: transaction.transactionID,
- reportActionID: iouAction.reportActionID,
- createdChatReportActionID,
- createdIOUReportActionID,
- reportPreviewReportActionID: reportPreviewAction.reportActionID,
- receipt,
- receiptState: lodashGet(receipt, 'state'),
- category,
- tag,
- taxCode,
- taxAmount,
- billable,
- },
- onyxData,
- );
+ const parameters: RequestMoneyParams = {
+ debtorEmail: payerEmail,
+ debtorAccountID: payerAccountID,
+ amount,
+ currency,
+ comment,
+ created: currentCreated,
+ merchant,
+ iouReportID: iouReport.reportID,
+ chatReportID: chatReport.reportID,
+ transactionID: transaction.transactionID,
+ reportActionID: iouAction.reportActionID,
+ createdChatReportActionID,
+ createdIOUReportActionID,
+ reportPreviewReportActionID: reportPreviewAction.reportActionID,
+ receipt,
+ receiptState: receipt?.state,
+ category,
+ tag,
+ taxCode,
+ taxAmount,
+ billable,
+ };
+
+ API.write(WRITE_COMMANDS.REQUEST_MONEY, parameters, onyxData);
resetMoneyRequestInfo();
Navigation.dismissModal(activeReportID);
Report.notifyNewAction(activeReportID, payeeAccountID);
@@ -1391,29 +1342,30 @@ function requestMoney(
* {email: 'user2', amount: 100, iouReportID: '100', chatReportID: '110', transactionID: '120', reportActionID: '130'},
* {email: 'user3', amount: 100, iouReportID: '200', chatReportID: '210', transactionID: '220', reportActionID: '230'}
* ]
- * @param {Array} participants
- * @param {String} currentUserLogin
- * @param {Number} currentUserAccountID
- * @param {Number} amount - always in the smallest unit of the currency
- * @param {String} comment
- * @param {String} currency
- * @param {String} merchant
- * @param {String} category
- * @param {String} tag
- * @param {String} existingSplitChatReportID - the report ID where the split bill happens, could be a group chat or a workspace chat
- * @param {Boolean} billable
- *
- * @return {Object}
- */
-function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag, existingSplitChatReportID = '', billable = false) {
+ * @param amount - always in the smallest unit of the currency
+ * @param existingSplitChatReportID - the report ID where the split bill happens, could be a group chat or a workspace chat
+ */
+function createSplitsAndOnyxData(
+ participants: Participant[],
+ currentUserLogin: string,
+ currentUserAccountID: number,
+ amount: number,
+ comment: string,
+ currency: string,
+ merchant: string,
+ category: string,
+ tag: string,
+ existingSplitChatReportID = '',
+ billable = false,
+): SplitsAndOnyxData {
const currentUserEmailForIOUSplit = OptionsListUtils.addSMSDomainIfPhoneNumber(currentUserLogin);
- const participantAccountIDs = _.map(participants, (participant) => Number(participant.accountID));
+ const participantAccountIDs = participants.map((participant) => Number(participant.accountID));
const existingSplitChatReport =
existingSplitChatReportID || participants[0].reportID
- ? allReports[`${ONYXKEYS.COLLECTION.REPORT}${existingSplitChatReportID || participants[0].reportID}`]
+ ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${existingSplitChatReportID || participants[0].reportID}`]
: ReportUtils.getChatByParticipants(participantAccountIDs);
- const splitChatReport = existingSplitChatReport || ReportUtils.buildOptimisticChatReport(participantAccountIDs);
- const isOwnPolicyExpenseChat = splitChatReport.isOwnPolicyExpenseChat;
+ const splitChatReport = existingSplitChatReport ?? ReportUtils.buildOptimisticChatReport(participantAccountIDs);
+ const isOwnPolicyExpenseChat = !!splitChatReport.isOwnPolicyExpenseChat;
const splitTransaction = TransactionUtils.buildOptimisticTransaction(
amount,
@@ -1441,7 +1393,7 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco
comment,
participants,
splitTransaction.transactionID,
- '',
+ undefined,
'',
false,
false,
@@ -1450,8 +1402,8 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco
);
splitChatReport.lastReadTime = DateUtils.getDBTime();
- splitChatReport.lastMessageText = splitIOUReportAction.message[0].text;
- splitChatReport.lastMessageHtml = splitIOUReportAction.message[0].html;
+ splitChatReport.lastMessageText = splitIOUReportAction.message?.[0].text;
+ splitChatReport.lastMessageHtml = splitIOUReportAction.message?.[0].html;
// If we have an existing splitChatReport (group chat or workspace) use it's pending fields, otherwise indicate that we are adding a chat
if (!existingSplitChatReport) {
@@ -1460,7 +1412,7 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco
};
}
- const optimisticData = [
+ const optimisticData: OnyxUpdate[] = [
{
// Use set for new reports because it doesn't exist yet, is faster,
// and we need the data to be available when we navigate to the chat page
@@ -1468,14 +1420,22 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco
key: `${ONYXKEYS.COLLECTION.REPORT}${splitChatReport.reportID}`,
value: splitChatReport,
},
- {
- onyxMethod: existingSplitChatReport ? Onyx.METHOD.MERGE : Onyx.METHOD.SET,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${splitChatReport.reportID}`,
- value: {
- ...(existingSplitChatReport ? {} : {[splitCreatedReportAction.reportActionID]: splitCreatedReportAction}),
- [splitIOUReportAction.reportActionID]: splitIOUReportAction,
- },
- },
+ existingSplitChatReport
+ ? {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${splitChatReport.reportID}`,
+ value: {
+ [splitIOUReportAction.reportActionID]: splitIOUReportAction as OnyxTypes.ReportAction,
+ },
+ }
+ : {
+ onyxMethod: Onyx.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${splitChatReport.reportID}`,
+ value: {
+ [splitCreatedReportAction.reportActionID]: splitCreatedReportAction as OnyxTypes.ReportAction,
+ [splitIOUReportAction.reportActionID]: splitIOUReportAction as OnyxTypes.ReportAction,
+ },
+ },
{
onyxMethod: Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${splitTransaction.transactionID}`,
@@ -1483,7 +1443,7 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco
},
];
- const successData = [
+ const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${splitChatReport.reportID}`,
@@ -1512,7 +1472,7 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco
});
}
- const failureData = [
+ const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${splitTransaction.transactionID}`,
@@ -1562,16 +1522,16 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco
// Loop through participants creating individual chats, iouReports and reportActionIDs as needed
const splitAmount = IOUUtils.calculateAmount(participants.length, amount, currency, false);
- const splits = [{email: currentUserEmailForIOUSplit, accountID: currentUserAccountID, amount: IOUUtils.calculateAmount(participants.length, amount, currency, true)}];
+ const splits: Split[] = [{email: currentUserEmailForIOUSplit, accountID: currentUserAccountID, amount: IOUUtils.calculateAmount(participants.length, amount, currency, true)}];
const hasMultipleParticipants = participants.length > 1;
- _.each(participants, (participant) => {
+ participants.forEach((participant) => {
// In a case when a participant is a workspace, even when a current user is not an owner of the workspace
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(participant);
// In case the participant is a workspace, email & accountID should remain undefined and won't be used in the rest of this code
// participant.login is undefined when the request is initiated from a group DM with an unknown user, so we need to add a default
- const email = isOwnPolicyExpenseChat || isPolicyExpenseChat ? '' : OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login || '').toLowerCase();
+ const email = isOwnPolicyExpenseChat || isPolicyExpenseChat ? '' : OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login ?? '').toLowerCase();
const accountID = isOwnPolicyExpenseChat || isPolicyExpenseChat ? 0 : Number(participant.accountID);
if (email === currentUserEmailForIOUSplit) {
return;
@@ -1579,10 +1539,10 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco
// STEP 1: Get existing chat report OR build a new optimistic one
// If we only have one participant and the request was initiated from the global create menu, i.e. !existingGroupChatReportID, the oneOnOneChatReport is the groupChatReport
- let oneOnOneChatReport;
+ let oneOnOneChatReport: OptimisticChatReport;
let isNewOneOnOneChatReport = false;
let shouldCreateOptimisticPersonalDetails = false;
- const personalDetailExists = lodashHas(allPersonalDetails, accountID);
+ const personalDetailExists = accountID in allPersonalDetails;
// If this is a split between two people only and the function
// wasn't provided with an existing group chat report id
@@ -1596,22 +1556,24 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco
const existingChatReport = ReportUtils.getChatByParticipants([accountID]);
isNewOneOnOneChatReport = !existingChatReport;
shouldCreateOptimisticPersonalDetails = isNewOneOnOneChatReport && !personalDetailExists;
- oneOnOneChatReport = existingChatReport || ReportUtils.buildOptimisticChatReport([accountID]);
+ oneOnOneChatReport = existingChatReport ?? ReportUtils.buildOptimisticChatReport([accountID]);
}
// STEP 2: Get existing IOU/Expense report and update its total OR build a new optimistic one
// For Control policy expense chats, if the report is already approved, create a new expense report
- let oneOnOneIOUReport = oneOnOneChatReport.iouReportID ? lodashGet(allReports, `${ONYXKEYS.COLLECTION.REPORT}${oneOnOneChatReport.iouReportID}`, undefined) : undefined;
+ let oneOnOneIOUReport: OneOnOneIOUReport = oneOnOneChatReport.iouReportID ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${oneOnOneChatReport.iouReportID}`] : null;
const shouldCreateNewOneOnOneIOUReport =
- _.isUndefined(oneOnOneIOUReport) || (isOwnPolicyExpenseChat && ReportUtils.isControlPolicyExpenseReport(oneOnOneIOUReport) && ReportUtils.isReportApproved(oneOnOneIOUReport));
+ !oneOnOneIOUReport || (isOwnPolicyExpenseChat && ReportUtils.isControlPolicyExpenseReport(oneOnOneIOUReport) && ReportUtils.isReportApproved(oneOnOneIOUReport));
- if (shouldCreateNewOneOnOneIOUReport) {
+ if (!oneOnOneIOUReport || shouldCreateNewOneOnOneIOUReport) {
oneOnOneIOUReport = isOwnPolicyExpenseChat
- ? ReportUtils.buildOptimisticExpenseReport(oneOnOneChatReport.reportID, oneOnOneChatReport.policyID, currentUserAccountID, splitAmount, currency)
+ ? ReportUtils.buildOptimisticExpenseReport(oneOnOneChatReport.reportID, oneOnOneChatReport.policyID ?? '', currentUserAccountID, splitAmount, currency)
: ReportUtils.buildOptimisticIOUReport(currentUserAccountID, accountID, splitAmount, oneOnOneChatReport.reportID, currency);
} else if (isOwnPolicyExpenseChat) {
- // Because of the Expense reports are stored as negative values, we subtract the total from the amount
- oneOnOneIOUReport.total -= splitAmount;
+ if (typeof oneOnOneIOUReport?.total === 'number') {
+ // Because of the Expense reports are stored as negative values, we subtract the total from the amount
+ oneOnOneIOUReport.total -= splitAmount;
+ }
} else {
oneOnOneIOUReport = IOUUtils.updateIOUOwnerAndTotal(oneOnOneIOUReport, currentUserAccountID, splitAmount, currency);
}
@@ -1650,7 +1612,7 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco
comment,
[participant],
oneOnOneTransaction.transactionID,
- '',
+ undefined,
oneOnOneIOUReport.reportID,
undefined,
undefined,
@@ -1660,17 +1622,19 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco
);
// Add optimistic personal details for new participants
- const oneOnOnePersonalDetailListAction = shouldCreateOptimisticPersonalDetails
+ const oneOnOnePersonalDetailListAction: OnyxTypes.PersonalDetailsList = shouldCreateOptimisticPersonalDetails
? {
[accountID]: {
accountID,
avatar: UserUtils.getDefaultAvatarURL(accountID),
+ // Disabling this line since participant.displayName can be an empty string
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
displayName: LocalePhoneNumber.formatPhoneNumber(participant.displayName || email),
login: participant.login,
isOptimisticPersonalDetail: true,
},
}
- : undefined;
+ : {};
let oneOnOneReportPreviewAction = ReportActionsUtils.getReportPreviewAction(oneOnOneChatReport.reportID, oneOnOneIOUReport.reportID);
if (oneOnOneReportPreviewAction) {
@@ -1720,14 +1684,14 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco
failureData.push(...oneOnOneFailureData);
});
- const splitData = {
+ const splitData: SplitData = {
chatReportID: splitChatReport.reportID,
transactionID: splitTransaction.transactionID,
reportActionID: splitIOUReportAction.reportActionID,
policyID: splitChatReport.policyID,
};
- if (_.isEmpty(existingSplitChatReport)) {
+ if (!existingSplitChatReport) {
splitData.createdReportActionID = splitCreatedReportAction.reportActionID;
}
@@ -1739,19 +1703,22 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco
}
/**
- * @param {Array} participants
- * @param {String} currentUserLogin
- * @param {Number} currentUserAccountID
- * @param {Number} amount - always in smallest currency unit
- * @param {String} comment
- * @param {String} currency
- * @param {String} merchant
- * @param {String} category
- * @param {String} tag
- * @param {String} existingSplitChatReportID - Either a group DM or a workspace chat
- * @param {Boolean} billable
- */
-function splitBill(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag, existingSplitChatReportID = '', billable = false) {
+ * @param amount - always in smallest currency unit
+ * @param existingSplitChatReportID - Either a group DM or a workspace chat
+ */
+function splitBill(
+ participants: Participant[],
+ currentUserLogin: string,
+ currentUserAccountID: number,
+ amount: number,
+ comment: string,
+ currency: string,
+ merchant: string,
+ category: string,
+ tag: string,
+ existingSplitChatReportID = '',
+ billable = false,
+) {
const {splitData, splits, onyxData} = createSplitsAndOnyxData(
participants,
currentUserLogin,
@@ -1765,65 +1732,64 @@ function splitBill(participants, currentUserLogin, currentUserAccountID, amount,
existingSplitChatReportID,
billable,
);
- API.write(
- 'SplitBill',
- {
- reportID: splitData.chatReportID,
- amount,
- splits: JSON.stringify(splits),
- currency,
- comment,
- category,
- merchant,
- tag,
- billable,
- transactionID: splitData.transactionID,
- reportActionID: splitData.reportActionID,
- createdReportActionID: splitData.createdReportActionID,
- policyID: splitData.policyID,
- },
- onyxData,
- );
- resetMoneyRequestInfo();
+ const parameters: SplitBillParams = {
+ reportID: splitData.chatReportID,
+ amount,
+ splits: JSON.stringify(splits),
+ currency,
+ comment,
+ category,
+ merchant,
+ tag,
+ billable,
+ transactionID: splitData.transactionID,
+ reportActionID: splitData.reportActionID,
+ createdReportActionID: splitData.createdReportActionID,
+ policyID: splitData.policyID,
+ };
+
+ API.write(WRITE_COMMANDS.SPLIT_BILL, parameters, onyxData);
+
+ resetMoneyRequestInfo();
Navigation.dismissModal();
Report.notifyNewAction(splitData.chatReportID, currentUserAccountID);
}
/**
- * @param {Array} participants
- * @param {String} currentUserLogin
- * @param {Number} currentUserAccountID
- * @param {Number} amount - always in smallest currency unit
- * @param {String} comment
- * @param {String} currency
- * @param {String} merchant
- * @param {String} category
- * @param {String} tag
- * @param {Boolean} billable
- */
-function splitBillAndOpenReport(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag, billable) {
- const {splitData, splits, onyxData} = createSplitsAndOnyxData(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag, billable);
+ * @param amount - always in smallest currency unit
+ */
+function splitBillAndOpenReport(
+ participants: Participant[],
+ currentUserLogin: string,
+ currentUserAccountID: number,
+ amount: number,
+ comment: string,
+ currency: string,
+ merchant: string,
+ category: string,
+ tag: string,
+ billable: boolean,
+) {
+ const {splitData, splits, onyxData} = createSplitsAndOnyxData(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag);
- API.write(
- 'SplitBillAndOpenReport',
- {
- reportID: splitData.chatReportID,
- amount,
- splits: JSON.stringify(splits),
- currency,
- merchant,
- comment,
- category,
- tag,
- billable,
- transactionID: splitData.transactionID,
- reportActionID: splitData.reportActionID,
- createdReportActionID: splitData.createdReportActionID,
- policyID: splitData.policyID,
- },
- onyxData,
- );
+ const parameters: SplitBillParams = {
+ reportID: splitData.chatReportID,
+ amount,
+ splits: JSON.stringify(splits),
+ currency,
+ merchant,
+ comment,
+ category,
+ tag,
+ billable,
+ transactionID: splitData.transactionID,
+ reportActionID: splitData.reportActionID,
+ createdReportActionID: splitData.createdReportActionID,
+ policyID: splitData.policyID,
+ };
+
+ API.write(WRITE_COMMANDS.SPLIT_BILL_AND_OPEN_REPORT, parameters, onyxData);
resetMoneyRequestInfo();
Navigation.dismissModal(splitData.chatReportID);
@@ -1833,28 +1799,30 @@ function splitBillAndOpenReport(participants, currentUserLogin, currentUserAccou
/** Used exclusively for starting a split bill request that contains a receipt, the split request will be completed once the receipt is scanned
* or user enters details manually.
*
- * @param {Array} participants
- * @param {String} currentUserLogin
- * @param {Number} currentUserAccountID
- * @param {String} comment
- * @param {String} category
- * @param {String} tag
- * @param {Object} receipt
- * @param {String} existingSplitChatReportID - Either a group DM or a workspace chat
- * @param {Boolean} billable
- */
-function startSplitBill(participants, currentUserLogin, currentUserAccountID, comment, category, tag, receipt, existingSplitChatReportID = '', billable = false) {
+ * @param existingSplitChatReportID - Either a group DM or a workspace chat
+ */
+function startSplitBill(
+ participants: Participant[],
+ currentUserLogin: string,
+ currentUserAccountID: number,
+ comment: string,
+ category: string,
+ tag: string,
+ receipt: Receipt,
+ existingSplitChatReportID = '',
+ billable = false,
+) {
const currentUserEmailForIOUSplit = OptionsListUtils.addSMSDomainIfPhoneNumber(currentUserLogin);
- const participantAccountIDs = _.map(participants, (participant) => Number(participant.accountID));
+ const participantAccountIDs = participants.map((participant) => Number(participant.accountID));
const existingSplitChatReport =
existingSplitChatReportID || participants[0].reportID
- ? allReports[`${ONYXKEYS.COLLECTION.REPORT}${existingSplitChatReportID || participants[0].reportID}`]
+ ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${existingSplitChatReportID || participants[0].reportID}`]
: ReportUtils.getChatByParticipants(participantAccountIDs);
- const splitChatReport = existingSplitChatReport || ReportUtils.buildOptimisticChatReport(participantAccountIDs);
- const isOwnPolicyExpenseChat = splitChatReport.isOwnPolicyExpenseChat || false;
+ const splitChatReport = existingSplitChatReport ?? ReportUtils.buildOptimisticChatReport(participantAccountIDs);
+ const isOwnPolicyExpenseChat = !!splitChatReport.isOwnPolicyExpenseChat;
const {name: filename, source, state = CONST.IOU.RECEIPT_STATE.SCANREADY} = receipt;
- const receiptObject = {state, source};
+ const receiptObject: Receipt = {state, source};
// ReportID is -2 (aka "deleted") on the group transaction
const splitTransaction = TransactionUtils.buildOptimisticTransaction(
@@ -1883,7 +1851,7 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co
comment,
participants,
splitTransaction.transactionID,
- '',
+ undefined,
'',
false,
false,
@@ -1892,8 +1860,8 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co
);
splitChatReport.lastReadTime = DateUtils.getDBTime();
- splitChatReport.lastMessageText = splitIOUReportAction.message[0].text;
- splitChatReport.lastMessageHtml = splitIOUReportAction.message[0].html;
+ splitChatReport.lastMessageText = splitIOUReportAction.message?.[0].text;
+ splitChatReport.lastMessageHtml = splitIOUReportAction.message?.[0].html;
// If we have an existing splitChatReport (group chat or workspace) use it's pending fields, otherwise indicate that we are adding a chat
if (!existingSplitChatReport) {
@@ -1902,7 +1870,7 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co
};
}
- const optimisticData = [
+ const optimisticData: OnyxUpdate[] = [
{
// Use set for new reports because it doesn't exist yet, is faster,
// and we need the data to be available when we navigate to the chat page
@@ -1910,14 +1878,22 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co
key: `${ONYXKEYS.COLLECTION.REPORT}${splitChatReport.reportID}`,
value: splitChatReport,
},
- {
- onyxMethod: existingSplitChatReport ? Onyx.METHOD.MERGE : Onyx.METHOD.SET,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${splitChatReport.reportID}`,
- value: {
- ...(existingSplitChatReport ? {} : {[splitChatCreatedReportAction.reportActionID]: splitChatCreatedReportAction}),
- [splitIOUReportAction.reportActionID]: splitIOUReportAction,
- },
- },
+ existingSplitChatReport
+ ? {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${splitChatReport.reportID}`,
+ value: {
+ [splitIOUReportAction.reportActionID]: splitIOUReportAction as OnyxTypes.ReportAction,
+ },
+ }
+ : {
+ onyxMethod: Onyx.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${splitChatReport.reportID}`,
+ value: {
+ [splitChatCreatedReportAction.reportActionID]: splitChatCreatedReportAction,
+ [splitIOUReportAction.reportActionID]: splitIOUReportAction as OnyxTypes.ReportAction,
+ },
+ },
{
onyxMethod: Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${splitTransaction.transactionID}`,
@@ -1925,7 +1901,7 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co
},
];
- const successData = [
+ const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${splitChatReport.reportID}`,
@@ -1949,7 +1925,7 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co
});
}
- const failureData = [
+ const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${splitTransaction.transactionID}`,
@@ -1995,10 +1971,12 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co
);
}
- const splits = [{email: currentUserEmailForIOUSplit, accountID: currentUserAccountID}];
+ const splits: Split[] = [{email: currentUserEmailForIOUSplit, accountID: currentUserAccountID}];
- _.each(participants, (participant) => {
- const email = participant.isOwnPolicyExpenseChat ? '' : OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login || participant.text).toLowerCase();
+ participants.forEach((participant) => {
+ // Disabling this line since participant.login can be an empty string
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ const email = participant.isOwnPolicyExpenseChat ? '' : OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login || participant.text || '').toLowerCase();
const accountID = participant.isOwnPolicyExpenseChat ? 0 : Number(participant.accountID);
if (email === currentUserEmailForIOUSplit) {
return;
@@ -2013,7 +1991,7 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co
return;
}
- const participantPersonalDetails = allPersonalDetails[participant.accountID];
+ const participantPersonalDetails = allPersonalDetails[participant?.accountID ?? -1];
if (!participantPersonalDetails) {
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
@@ -2022,7 +2000,11 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co
[accountID]: {
accountID,
avatar: UserUtils.getDefaultAvatarURL(accountID),
+ // Disabling this line since participant.displayName can be an empty string
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
displayName: LocalePhoneNumber.formatPhoneNumber(participant.displayName || email),
+ // Disabling this line since participant.login can be an empty string
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
login: participant.login || participant.text,
isOptimisticPersonalDetail: true,
},
@@ -2036,7 +2018,7 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co
});
});
- _.each(participants, (participant) => {
+ participants.forEach((participant) => {
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(participant);
if (!isPolicyExpenseChat) {
return;
@@ -2045,7 +2027,7 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co
const optimisticPolicyRecentlyUsedCategories = Policy.buildOptimisticPolicyRecentlyUsedCategories(participant.policyID, category);
const optimisticPolicyRecentlyUsedTags = Policy.buildOptimisticPolicyRecentlyUsedTags(participant.policyID, tag);
- if (!_.isEmpty(optimisticPolicyRecentlyUsedCategories)) {
+ if (optimisticPolicyRecentlyUsedCategories.length > 0) {
optimisticData.push({
onyxMethod: Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${participant.policyID}`,
@@ -2053,7 +2035,7 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co
});
}
- if (!_.isEmpty(optimisticPolicyRecentlyUsedTags)) {
+ if (!isEmptyObject(optimisticPolicyRecentlyUsedTags)) {
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${participant.policyID}`,
@@ -2073,44 +2055,42 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co
},
});
- API.write(
- 'StartSplitBill',
- {
- chatReportID: splitChatReport.reportID,
- reportActionID: splitIOUReportAction.reportActionID,
- transactionID: splitTransaction.transactionID,
- splits: JSON.stringify(splits),
- receipt,
- comment,
- category,
- tag,
- isFromGroupDM: !existingSplitChatReport,
- billable,
- ...(existingSplitChatReport ? {} : {createdReportActionID: splitChatCreatedReportAction.reportActionID}),
- },
- {optimisticData, successData, failureData},
- );
+ const parameters: StartSplitBillParams = {
+ chatReportID: splitChatReport.reportID,
+ reportActionID: splitIOUReportAction.reportActionID,
+ transactionID: splitTransaction.transactionID,
+ splits: JSON.stringify(splits),
+ receipt,
+ comment,
+ category,
+ tag,
+ isFromGroupDM: !existingSplitChatReport,
+ billable,
+ ...(existingSplitChatReport ? {} : {createdReportActionID: splitChatCreatedReportAction.reportActionID}),
+ };
+
+ API.write(WRITE_COMMANDS.START_SPLIT_BILL, parameters, {optimisticData, successData, failureData});
resetMoneyRequestInfo();
Navigation.dismissModalWithReport(splitChatReport);
- Report.notifyNewAction(splitChatReport.chatReportID, currentUserAccountID);
+ Report.notifyNewAction(splitChatReport.chatReportID ?? '', currentUserAccountID);
}
/** Used for editing a split bill while it's still scanning or when SmartScan fails, it completes a split bill started by startSplitBill above.
*
- * @param {number} chatReportID - The group chat or workspace reportID
- * @param {Object} reportAction - The split action that lives in the chatReport above
- * @param {Object} updatedTransaction - The updated **draft** split transaction
- * @param {Number} sessionAccountID - accountID of the current user
- * @param {String} sessionEmail - email of the current user
+ * @param chatReportID - The group chat or workspace reportID
+ * @param reportAction - The split action that lives in the chatReport above
+ * @param updatedTransaction - The updated **draft** split transaction
+ * @param sessionAccountID - accountID of the current user
+ * @param sessionEmail - email of the current user
*/
-function completeSplitBill(chatReportID, reportAction, updatedTransaction, sessionAccountID, sessionEmail) {
+function completeSplitBill(chatReportID: string, reportAction: OnyxTypes.ReportAction, updatedTransaction: OnyxTypes.Transaction, sessionAccountID: number, sessionEmail: string) {
const currentUserEmailForIOUSplit = OptionsListUtils.addSMSDomainIfPhoneNumber(sessionEmail);
const {transactionID} = updatedTransaction;
const unmodifiedTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
// Save optimistic updated transaction and action
- const optimisticData = [
+ const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
@@ -2133,7 +2113,7 @@ function completeSplitBill(chatReportID, reportAction, updatedTransaction, sessi
},
];
- const successData = [
+ const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
@@ -2142,11 +2122,11 @@ function completeSplitBill(chatReportID, reportAction, updatedTransaction, sessi
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`,
- value: null,
+ value: {pendingAction: null},
},
];
- const failureData = [
+ const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
@@ -2167,25 +2147,25 @@ function completeSplitBill(chatReportID, reportAction, updatedTransaction, sessi
},
];
- const splitParticipants = updatedTransaction.comment.splits;
+ const splitParticipants: Split[] = updatedTransaction.comment.splits ?? [];
const {modifiedAmount: amount, modifiedCurrency: currency} = updatedTransaction;
// Exclude the current user when calculating the split amount, `calculateAmount` takes it into account
- const splitAmount = IOUUtils.calculateAmount(splitParticipants.length - 1, amount, currency, false);
+ const splitAmount = IOUUtils.calculateAmount(splitParticipants.length - 1, amount ?? 0, currency ?? '', false);
- const splits = [{email: currentUserEmailForIOUSplit}];
- _.each(splitParticipants, (participant) => {
+ const splits: Split[] = [{email: currentUserEmailForIOUSplit}];
+ splitParticipants.forEach((participant) => {
// Skip creating the transaction for the current user
if (participant.email === currentUserEmailForIOUSplit) {
return;
}
- const isPolicyExpenseChat = !_.isEmpty(participant.policyID);
+ const isPolicyExpenseChat = !!participant.policyID;
if (!isPolicyExpenseChat) {
// In case this is still the optimistic accountID saved in the splits array, return early as we cannot know
// if there is an existing chat between the split creator and this participant
// Instead, we will rely on Auth generating the report IDs and the user won't see any optimistic chats or reports created
- const participantPersonalDetails = allPersonalDetails[participant.accountID] || {};
+ const participantPersonalDetails: OnyxTypes.PersonalDetails | EmptyObject = allPersonalDetails[participant?.accountID ?? -1] ?? {};
if (!participantPersonalDetails || participantPersonalDetails.isOptimisticPersonalDetail) {
splits.push({
email: participant.email,
@@ -2194,36 +2174,38 @@ function completeSplitBill(chatReportID, reportAction, updatedTransaction, sessi
}
}
- let oneOnOneChatReport;
+ let oneOnOneChatReport: OnyxTypes.Report | null;
let isNewOneOnOneChatReport = false;
if (isPolicyExpenseChat) {
// The workspace chat reportID is saved in the splits array when starting a split bill with a workspace
- oneOnOneChatReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${participant.chatReportID}`];
+ oneOnOneChatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${participant.chatReportID}`] ?? null;
} else {
- const existingChatReport = ReportUtils.getChatByParticipants([participant.accountID]);
+ const existingChatReport = ReportUtils.getChatByParticipants(participant.accountID ? [participant.accountID] : []);
isNewOneOnOneChatReport = !existingChatReport;
- oneOnOneChatReport = existingChatReport || ReportUtils.buildOptimisticChatReport([participant.accountID]);
+ oneOnOneChatReport = existingChatReport ?? ReportUtils.buildOptimisticChatReport(participant.accountID ? [participant.accountID] : []);
}
- let oneOnOneIOUReport = oneOnOneChatReport.iouReportID ? lodashGet(allReports, `${ONYXKEYS.COLLECTION.REPORT}${oneOnOneChatReport.iouReportID}`, undefined) : undefined;
+ let oneOnOneIOUReport: OneOnOneIOUReport = oneOnOneChatReport?.iouReportID ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${oneOnOneChatReport.iouReportID}`] : null;
const shouldCreateNewOneOnOneIOUReport =
- _.isUndefined(oneOnOneIOUReport) || (isPolicyExpenseChat && ReportUtils.isControlPolicyExpenseReport(oneOnOneIOUReport) && ReportUtils.isReportApproved(oneOnOneIOUReport));
+ !oneOnOneIOUReport || (isPolicyExpenseChat && ReportUtils.isControlPolicyExpenseReport(oneOnOneIOUReport) && ReportUtils.isReportApproved(oneOnOneIOUReport));
- if (shouldCreateNewOneOnOneIOUReport) {
+ if (!oneOnOneIOUReport || shouldCreateNewOneOnOneIOUReport) {
oneOnOneIOUReport = isPolicyExpenseChat
- ? ReportUtils.buildOptimisticExpenseReport(oneOnOneChatReport.reportID, participant.policyID, sessionAccountID, splitAmount, currency)
- : ReportUtils.buildOptimisticIOUReport(sessionAccountID, participant.accountID, splitAmount, oneOnOneChatReport.reportID, currency);
+ ? ReportUtils.buildOptimisticExpenseReport(oneOnOneChatReport?.reportID ?? '', participant.policyID ?? '', sessionAccountID, splitAmount, currency ?? '')
+ : ReportUtils.buildOptimisticIOUReport(sessionAccountID, participant.accountID ?? -1, splitAmount, oneOnOneChatReport?.reportID ?? '', currency ?? '');
} else if (isPolicyExpenseChat) {
- // Because of the Expense reports are stored as negative values, we subtract the total from the amount
- oneOnOneIOUReport.total -= splitAmount;
+ if (typeof oneOnOneIOUReport?.total === 'number') {
+ // Because of the Expense reports are stored as negative values, we subtract the total from the amount
+ oneOnOneIOUReport.total -= splitAmount;
+ }
} else {
- oneOnOneIOUReport = IOUUtils.updateIOUOwnerAndTotal(oneOnOneIOUReport, sessionAccountID, splitAmount, currency);
+ oneOnOneIOUReport = IOUUtils.updateIOUOwnerAndTotal(oneOnOneIOUReport, sessionAccountID, splitAmount, currency ?? '');
}
const oneOnOneTransaction = TransactionUtils.buildOptimisticTransaction(
isPolicyExpenseChat ? -splitAmount : splitAmount,
- currency,
- oneOnOneIOUReport.reportID,
+ currency ?? '',
+ oneOnOneIOUReport?.reportID ?? '',
updatedTransaction.comment.comment,
updatedTransaction.modifiedCreated,
CONST.IOU.TYPE.SPLIT,
@@ -2238,15 +2220,15 @@ function completeSplitBill(chatReportID, reportAction, updatedTransaction, sessi
const oneOnOneIOUAction = ReportUtils.buildOptimisticIOUReportAction(
CONST.IOU.REPORT_ACTION_TYPE.CREATE,
splitAmount,
- currency,
- updatedTransaction.comment.comment,
+ currency ?? '',
+ updatedTransaction.comment.comment ?? '',
[participant],
oneOnOneTransaction.transactionID,
- '',
- oneOnOneIOUReport.reportID,
+ undefined,
+ oneOnOneIOUReport?.reportID,
);
- let oneOnOneReportPreviewAction = ReportActionsUtils.getReportPreviewAction(oneOnOneChatReport.reportID, oneOnOneIOUReport.reportID);
+ let oneOnOneReportPreviewAction = ReportActionsUtils.getReportPreviewAction(oneOnOneChatReport?.reportID ?? '', oneOnOneIOUReport?.reportID ?? '');
if (oneOnOneReportPreviewAction) {
oneOnOneReportPreviewAction = ReportUtils.updateReportPreview(oneOnOneIOUReport, oneOnOneReportPreviewAction);
} else {
@@ -2262,7 +2244,7 @@ function completeSplitBill(chatReportID, reportAction, updatedTransaction, sessi
oneOnOneIOUAction,
{},
oneOnOneReportPreviewAction,
- {},
+ [],
{},
isNewOneOnOneChatReport,
shouldCreateNewOneOnOneIOUReport,
@@ -2272,8 +2254,8 @@ function completeSplitBill(chatReportID, reportAction, updatedTransaction, sessi
email: participant.email,
accountID: participant.accountID,
policyID: participant.policyID,
- iouReportID: oneOnOneIOUReport.reportID,
- chatReportID: oneOnOneChatReport.reportID,
+ iouReportID: oneOnOneIOUReport?.reportID,
+ chatReportID: oneOnOneChatReport?.reportID,
transactionID: oneOnOneTransaction.transactionID,
reportActionID: oneOnOneIOUAction.reportActionID,
createdChatReportActionID: oneOnOneCreatedActionForChat.reportActionID,
@@ -2294,59 +2276,48 @@ function completeSplitBill(chatReportID, reportAction, updatedTransaction, sessi
comment: transactionComment,
category: transactionCategory,
tag: transactionTag,
- } = ReportUtils.getTransactionDetails(updatedTransaction);
+ } = ReportUtils.getTransactionDetails(updatedTransaction) ?? {};
- API.write(
- 'CompleteSplitBill',
- {
- transactionID,
- amount: transactionAmount,
- currency: transactionCurrency,
- created: transactionCreated,
- merchant: transactionMerchant,
- comment: transactionComment,
- category: transactionCategory,
- tag: transactionTag,
- splits: JSON.stringify(splits),
- },
- {optimisticData, successData, failureData},
- );
+ const parameters: CompleteSplitBillParams = {
+ transactionID,
+ amount: transactionAmount,
+ currency: transactionCurrency,
+ created: transactionCreated,
+ merchant: transactionMerchant,
+ comment: transactionComment,
+ category: transactionCategory,
+ tag: transactionTag,
+ splits: JSON.stringify(splits),
+ };
+
+ API.write(WRITE_COMMANDS.COMPLETE_SPLIT_BILL, parameters, {optimisticData, successData, failureData});
Navigation.dismissModal(chatReportID);
Report.notifyNewAction(chatReportID, sessionAccountID);
}
-/**
- * @param {String} transactionID
- * @param {Object} transactionChanges
- */
-function setDraftSplitTransaction(transactionID, transactionChanges = {}) {
+function setDraftSplitTransaction(transactionID: string, transactionChanges: TransactionChanges = {}) {
let draftSplitTransaction = allDraftSplitTransactions[`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`];
if (!draftSplitTransaction) {
draftSplitTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
}
- const updatedTransaction = TransactionUtils.getUpdatedTransaction(draftSplitTransaction, transactionChanges, false, false);
+ const updatedTransaction = draftSplitTransaction ? TransactionUtils.getUpdatedTransaction(draftSplitTransaction, transactionChanges, false, false) : null;
Onyx.merge(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`, updatedTransaction);
}
-/**
- * @param {String} transactionID
- * @param {Number} transactionThreadReportID
- * @param {Object} transactionChanges
- */
-function editRegularMoneyRequest(transactionID, transactionThreadReportID, transactionChanges) {
+function editRegularMoneyRequest(transactionID: string, transactionThreadReportID: string, transactionChanges: TransactionChanges) {
// STEP 1: Get all collections we're updating
- const transactionThread = allReports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`];
+ const transactionThread = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`] ?? null;
const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
- const iouReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThread.parentReportID}`];
- const chatReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${iouReport.chatReportID}`];
+ const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThread?.parentReportID}`] ?? null;
+ const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`] ?? null;
const isFromExpenseReport = ReportUtils.isExpenseReport(iouReport);
// STEP 2: Build new modified expense report action.
const updatedReportAction = ReportUtils.buildOptimisticModifiedExpenseReportAction(transactionThread, transaction, transactionChanges, isFromExpenseReport);
- const updatedTransaction = TransactionUtils.getUpdatedTransaction(transaction, transactionChanges, isFromExpenseReport);
+ const updatedTransaction = transaction ? TransactionUtils.getUpdatedTransaction(transaction, transactionChanges, isFromExpenseReport) : null;
// STEP 3: Compute the IOU total and update the report preview message so LHN amount owed is correct
// Should only update if the transaction matches the currency of the report, else we wait for the update
@@ -2354,20 +2325,22 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans
let updatedMoneyRequestReport = {...iouReport};
const updatedChatReport = {...chatReport};
const diff = TransactionUtils.getAmount(transaction, true) - TransactionUtils.getAmount(updatedTransaction, true);
- if (updatedTransaction.currency === iouReport.currency && updatedTransaction.modifiedAmount && diff !== 0) {
- if (ReportUtils.isExpenseReport(iouReport)) {
+ if (updatedTransaction?.currency === iouReport?.currency && updatedTransaction?.modifiedAmount && diff !== 0) {
+ if (ReportUtils.isExpenseReport(iouReport) && typeof updatedMoneyRequestReport.total === 'number') {
updatedMoneyRequestReport.total += diff;
} else {
- updatedMoneyRequestReport = IOUUtils.updateIOUOwnerAndTotal(iouReport, updatedReportAction.actorAccountID, diff, TransactionUtils.getCurrency(transaction), false);
+ updatedMoneyRequestReport = iouReport
+ ? IOUUtils.updateIOUOwnerAndTotal(iouReport, updatedReportAction.actorAccountID ?? -1, diff, TransactionUtils.getCurrency(transaction), false)
+ : {};
}
updatedMoneyRequestReport.cachedTotal = CurrencyUtils.convertToDisplayString(updatedMoneyRequestReport.total, updatedTransaction.currency);
// Update the last message of the IOU report
const lastMessage = ReportUtils.getIOUReportActionMessage(
- iouReport.reportID,
+ iouReport?.reportID ?? '',
CONST.IOU.REPORT_ACTION_TYPE.CREATE,
- updatedMoneyRequestReport.total,
+ updatedMoneyRequestReport.total ?? 0,
'',
updatedTransaction.currency,
'',
@@ -2377,9 +2350,9 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans
updatedMoneyRequestReport.lastMessageHtml = lastMessage[0].html;
// Update the last message of the chat report
- const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(iouReport);
+ const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(iouReport?.reportID);
const messageText = Localize.translateLocal(hasNonReimbursableTransactions ? 'iou.payerSpentAmount' : 'iou.payerOwesAmount', {
- payer: ReportUtils.getPersonalDetailsForAccountID(updatedMoneyRequestReport.managerID).login || '',
+ payer: ReportUtils.getPersonalDetailsForAccountID(updatedMoneyRequestReport.managerID ?? -1).login ?? '',
amount: CurrencyUtils.convertToDisplayString(updatedMoneyRequestReport.total, updatedMoneyRequestReport.currency),
});
updatedChatReport.lastMessageText = messageText;
@@ -2390,12 +2363,12 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans
// STEP 4: Compose the optimistic data
const currentTime = DateUtils.getDBTime();
- const optimisticData = [
+ const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread?.reportID}`,
value: {
- [updatedReportAction.reportActionID]: updatedReportAction,
+ [updatedReportAction.reportActionID]: updatedReportAction as OnyxTypes.ReportAction,
},
},
{
@@ -2405,12 +2378,12 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans
},
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`,
value: updatedMoneyRequestReport,
},
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.chatReportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`,
value: updatedChatReport,
},
{
@@ -2421,58 +2394,59 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans
lastVisibleActionCreated: currentTime,
},
},
- ...(!isScanning
- ? [
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
- value: {
- [transactionThread.parentReportActionID]: {
- whisperedToAccountIDs: [],
- },
- },
- },
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.parentReportID}`,
- value: {
- [iouReport.parentReportActionID]: {
- whisperedToAccountIDs: [],
- },
- },
- },
- ]
- : []),
];
+ if (!isScanning) {
+ optimisticData.push(
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`,
+ value: {
+ [transactionThread?.parentReportActionID ?? '']: {
+ whisperedToAccountIDs: [],
+ },
+ },
+ },
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.parentReportID}`,
+ value: {
+ [iouReport?.parentReportActionID ?? '']: {
+ whisperedToAccountIDs: [],
+ },
+ },
+ },
+ );
+ }
+
// Update recently used categories if the category is changed
- if (_.has(transactionChanges, 'category')) {
- const optimisticPolicyRecentlyUsedCategories = Policy.buildOptimisticPolicyRecentlyUsedCategories(iouReport.policyID, transactionChanges.category);
- if (!_.isEmpty(optimisticPolicyRecentlyUsedCategories)) {
+ if ('category' in transactionChanges) {
+ const optimisticPolicyRecentlyUsedCategories = Policy.buildOptimisticPolicyRecentlyUsedCategories(iouReport?.policyID, transactionChanges.category);
+ if (optimisticPolicyRecentlyUsedCategories.length) {
optimisticData.push({
onyxMethod: Onyx.METHOD.SET,
- key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${iouReport.policyID}`,
+ key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${iouReport?.policyID}`,
value: optimisticPolicyRecentlyUsedCategories,
});
}
}
// Update recently used categories if the tag is changed
- if (_.has(transactionChanges, 'tag')) {
- const optimisticPolicyRecentlyUsedTags = Policy.buildOptimisticPolicyRecentlyUsedTags(iouReport.policyID, transactionChanges.tag);
- if (!_.isEmpty(optimisticPolicyRecentlyUsedTags)) {
+ if ('tag' in transactionChanges) {
+ const optimisticPolicyRecentlyUsedTags = Policy.buildOptimisticPolicyRecentlyUsedTags(iouReport?.policyID, transactionChanges.tag);
+ if (!isEmptyObject(optimisticPolicyRecentlyUsedTags)) {
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${iouReport.policyID}`,
+ key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${iouReport?.policyID}`,
value: optimisticPolicyRecentlyUsedTags,
});
}
}
- const successData = [
+ const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread?.reportID}`,
value: {
[updatedReportAction.reportActionID]: {pendingAction: null},
},
@@ -2495,15 +2469,15 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans
},
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`,
value: {pendingAction: null},
},
];
- const failureData = [
+ const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread?.reportID}`,
value: {
[updatedReportAction.reportActionID]: {
errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericEditFailureMessage'),
@@ -2515,63 +2489,57 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
value: {
...transaction,
- modifiedCreated: transaction.modifiedCreated ? transaction.modifiedCreated : null,
- modifiedAmount: transaction.modifiedAmount ? transaction.modifiedAmount : null,
- modifiedCurrency: transaction.modifiedCurrency ? transaction.modifiedCurrency : null,
- modifiedMerchant: transaction.modifiedMerchant ? transaction.modifiedMerchant : null,
- modifiedWaypoints: transaction.modifiedWaypoints ? transaction.modifiedWaypoints : null,
+ modifiedCreated: transaction?.modifiedCreated ? transaction.modifiedCreated : null,
+ modifiedAmount: transaction?.modifiedAmount ? transaction.modifiedAmount : null,
+ modifiedCurrency: transaction?.modifiedCurrency ? transaction.modifiedCurrency : null,
+ modifiedMerchant: transaction?.modifiedMerchant ? transaction.modifiedMerchant : null,
+ modifiedWaypoints: transaction?.modifiedWaypoints ? transaction.modifiedWaypoints : null,
pendingFields: null,
},
},
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`,
value: {
...iouReport,
- cachedTotal: iouReport.cachedTotal ? iouReport.cachedTotal : null,
+ cachedTotal: iouReport?.cachedTotal ? iouReport?.cachedTotal : null,
},
},
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.chatReportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`,
value: chatReport,
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`,
value: {
- lastReadTime: transactionThread.lastReadTime,
- lastVisibleActionCreated: transactionThread.lastVisibleActionCreated,
+ lastReadTime: transactionThread?.lastReadTime,
+ lastVisibleActionCreated: transactionThread?.lastVisibleActionCreated,
},
},
];
// STEP 6: Call the API endpoint
- const {created, amount, currency, comment, merchant, category, billable, tag} = ReportUtils.getTransactionDetails(updatedTransaction);
- API.write(
- 'EditMoneyRequest',
- {
- transactionID,
- reportActionID: updatedReportAction.reportActionID,
- created,
- amount,
- currency,
- comment,
- merchant,
- category,
- billable,
- tag,
- },
- {optimisticData, successData, failureData},
- );
+ const {created, amount, currency, comment, merchant, category, billable, tag} = ReportUtils.getTransactionDetails(updatedTransaction) ?? {};
+
+ const parameters: EditMoneyRequestParams = {
+ transactionID,
+ reportActionID: updatedReportAction.reportActionID,
+ created,
+ amount,
+ currency,
+ comment,
+ merchant,
+ category,
+ billable,
+ tag,
+ };
+
+ API.write(WRITE_COMMANDS.EDIT_MONEY_REQUEST, parameters, {optimisticData, successData, failureData});
}
-/**
- * @param {object} transaction
- * @param {String} transactionThreadReportID
- * @param {Object} transactionChanges
- */
-function editMoneyRequest(transaction, transactionThreadReportID, transactionChanges) {
+function editMoneyRequest(transaction: OnyxTypes.Transaction, transactionThreadReportID: string, transactionChanges: TransactionChanges) {
if (TransactionUtils.isDistanceRequest(transaction)) {
updateDistanceRequest(transaction.transactionID, transactionThreadReportID, transactionChanges);
} else {
@@ -2579,46 +2547,35 @@ function editMoneyRequest(transaction, transactionThreadReportID, transactionCha
}
}
-/**
- * Updates the amount and currency fields of a money request
- *
- * @param {String} transactionID
- * @param {String} transactionThreadReportID
- * @param {String} currency
- * @param {Number} amount
- */
-function updateMoneyRequestAmountAndCurrency(transactionID, transactionThreadReportID, currency, amount) {
+/** Updates the amount and currency fields of a money request */
+function updateMoneyRequestAmountAndCurrency(transactionID: string, transactionThreadReportID: string, currency: string, amount: number) {
const transactionChanges = {
amount,
currency,
};
const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true);
- API.write('UpdateMoneyRequestAmountAndCurrency', params, onyxData);
+ API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_AMOUNT_AND_CURRENCY, params, onyxData);
}
-/**
- * @param {String | undefined} transactionID
- * @param {Object} reportAction - the money request reportAction we are deleting
- * @param {Boolean} isSingleTransactionView
- */
-function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView = false) {
+function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false) {
// STEP 1: Get all collections we're updating
- const iouReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${reportAction.originalMessage.IOUReportID}`];
- const chatReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${iouReport.chatReportID}`];
- const reportPreviewAction = ReportActionsUtils.getReportPreviewAction(iouReport.chatReportID, iouReport.reportID);
+ const iouReportID = reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? reportAction.originalMessage.IOUReportID : '';
+ const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`] ?? null;
+ const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`];
+ const reportPreviewAction = ReportActionsUtils.getReportPreviewAction(iouReport?.chatReportID ?? '', iouReport?.reportID ?? '');
const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
const transactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`];
const transactionThreadID = reportAction.childReportID;
let transactionThread = null;
if (transactionThreadID) {
- transactionThread = allReports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`];
+ transactionThread = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`] ?? null;
}
// STEP 2: Decide if we need to:
// 1. Delete the transactionThread - delete if there are no visible comments in the thread
// 2. Update the moneyRequestPreview to show [Deleted request] - update if the transactionThread exists AND it isn't being deleted
- const shouldDeleteTransactionThread = transactionThreadID ? lodashGet(reportAction, 'childVisibleActionCount', 0) === 0 : false;
- const shouldShowDeletedRequestMessage = transactionThreadID && !shouldDeleteTransactionThread;
+ const shouldDeleteTransactionThread = transactionThreadID ? (reportAction?.childVisibleActionCount ?? 0) === 0 : false;
+ const shouldShowDeletedRequestMessage = !!transactionThreadID && !shouldDeleteTransactionThread;
// STEP 3: Update the IOU reportAction and decide if the iouReport should be deleted. We delete the iouReport if there are no visible comments left in the report.
const updatedReportAction = {
@@ -2637,127 +2594,135 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView
originalMessage: {
IOUTransactionID: null,
},
- errors: null,
+ errors: undefined,
},
- };
+ } as OnyxTypes.ReportActions;
- const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(iouReport.reportID, updatedReportAction);
- const iouReportLastMessageText = ReportActionsUtils.getLastVisibleMessage(iouReport.reportID, updatedReportAction).lastMessageText;
+ const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(iouReport?.reportID ?? '', updatedReportAction);
+ const iouReportLastMessageText = ReportActionsUtils.getLastVisibleMessage(iouReport?.reportID ?? '', updatedReportAction).lastMessageText;
const shouldDeleteIOUReport =
iouReportLastMessageText.length === 0 && !ReportActionsUtils.isDeletedParentAction(lastVisibleAction) && (!transactionThreadID || shouldDeleteTransactionThread);
// STEP 4: Update the iouReport and reportPreview with new totals and messages if it wasn't deleted
- let updatedIOUReport = {...iouReport};
- const updatedReportPreviewAction = {...reportPreviewAction};
+ let updatedIOUReport: OnyxTypes.Report | null;
+ const updatedReportPreviewAction: OnyxTypes.ReportAction | EmptyObject = {...reportPreviewAction};
updatedReportPreviewAction.pendingAction = shouldDeleteIOUReport ? CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE : CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE;
- if (ReportUtils.isExpenseReport(iouReport)) {
+ if (iouReport && ReportUtils.isExpenseReport(iouReport)) {
updatedIOUReport = {...iouReport};
- // Because of the Expense reports are stored as negative values, we add the total from the amount
- updatedIOUReport.total += TransactionUtils.getAmount(transaction, true);
+ if (typeof updatedIOUReport.total === 'number') {
+ // Because of the Expense reports are stored as negative values, we add the total from the amount
+ updatedIOUReport.total += TransactionUtils.getAmount(transaction, true);
+ }
} else {
updatedIOUReport = IOUUtils.updateIOUOwnerAndTotal(
iouReport,
- reportAction.actorAccountID,
+ reportAction.actorAccountID ?? -1,
TransactionUtils.getAmount(transaction, false),
TransactionUtils.getCurrency(transaction),
true,
);
}
- updatedIOUReport.lastMessageText = iouReportLastMessageText;
- updatedIOUReport.lastVisibleActionCreated = lodashGet(lastVisibleAction, 'created');
+ if (updatedIOUReport) {
+ updatedIOUReport.lastMessageText = iouReportLastMessageText;
+ updatedIOUReport.lastVisibleActionCreated = lastVisibleAction?.created;
+ }
- const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(iouReport);
+ const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(iouReport?.reportID);
const messageText = Localize.translateLocal(hasNonReimbursableTransactions ? 'iou.payerSpentAmount' : 'iou.payerOwesAmount', {
- payer: ReportUtils.getPersonalDetailsForAccountID(updatedIOUReport.managerID).login || '',
- amount: CurrencyUtils.convertToDisplayString(updatedIOUReport.total, updatedIOUReport.currency),
+ payer: ReportUtils.getPersonalDetailsForAccountID(updatedIOUReport?.managerID ?? -1).login ?? '',
+ amount: CurrencyUtils.convertToDisplayString(updatedIOUReport?.total, updatedIOUReport?.currency),
});
- updatedReportPreviewAction.message[0].text = messageText;
- updatedReportPreviewAction.message[0].html = shouldDeleteIOUReport ? '' : messageText;
- if (reportPreviewAction.childMoneyRequestCount > 0) {
+ if (updatedReportPreviewAction?.message?.[0]) {
+ updatedReportPreviewAction.message[0].text = messageText;
+ updatedReportPreviewAction.message[0].html = shouldDeleteIOUReport ? '' : messageText;
+ }
+
+ if (updatedReportPreviewAction && reportPreviewAction?.childMoneyRequestCount && reportPreviewAction?.childMoneyRequestCount > 0) {
updatedReportPreviewAction.childMoneyRequestCount = reportPreviewAction.childMoneyRequestCount - 1;
}
// STEP 5: Build Onyx data
- const optimisticData = [
+ const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
value: null,
},
- ...(Permissions.canUseViolations(betas)
- ? [
- {
- onyxMethod: Onyx.METHOD.SET,
- key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`,
- value: null,
- },
- ]
- : []),
- ...(shouldDeleteTransactionThread
- ? [
- {
- onyxMethod: Onyx.METHOD.SET,
- key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`,
- value: null,
- },
- {
- onyxMethod: Onyx.METHOD.SET,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadID}`,
- value: null,
- },
- ]
- : []),
+ ];
+
+ if (Permissions.canUseViolations(betas)) {
+ optimisticData.push({
+ onyxMethod: Onyx.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`,
+ value: null,
+ });
+ }
+
+ if (shouldDeleteTransactionThread) {
+ optimisticData.push(
+ {
+ onyxMethod: Onyx.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`,
+ value: null,
+ },
+ {
+ onyxMethod: Onyx.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadID}`,
+ value: null,
+ },
+ );
+ }
+
+ optimisticData.push(
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`,
value: updatedReportAction,
},
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`,
value: updatedIOUReport,
},
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`,
value: {
- [reportPreviewAction.reportActionID]: updatedReportPreviewAction,
+ [reportPreviewAction?.reportActionID ?? '']: updatedReportPreviewAction,
},
},
- ...(!shouldDeleteIOUReport && updatedReportPreviewAction.childMoneyRequestCount === 0
- ? [
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`,
- value: {
- hasOutstandingChildRequest: false,
- },
- },
- ]
- : []),
- ...(shouldDeleteIOUReport
- ? [
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`,
- value: {
- hasOutstandingChildRequest: false,
- iouReportID: null,
- lastMessageText: ReportActionsUtils.getLastVisibleMessage(iouReport.chatReportID, {[reportPreviewAction.reportActionID]: null}).lastMessageText,
- lastVisibleActionCreated: lodashGet(ReportActionsUtils.getLastVisibleAction(iouReport.chatReportID, {[reportPreviewAction.reportActionID]: null}), 'created'),
- },
- },
- ]
- : []),
- ];
+ );
- const successData = [
+ if (!shouldDeleteIOUReport && updatedReportPreviewAction.childMoneyRequestCount === 0) {
+ optimisticData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`,
+ value: {
+ hasOutstandingChildRequest: false,
+ },
+ });
+ }
+
+ if (shouldDeleteIOUReport) {
+ optimisticData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`,
+ value: {
+ hasOutstandingChildRequest: false,
+ iouReportID: null,
+ lastMessageText: ReportActionsUtils.getLastVisibleMessage(iouReport?.chatReportID ?? '', {[reportPreviewAction?.reportActionID ?? '']: null})?.lastMessageText,
+ lastVisibleActionCreated: ReportActionsUtils.getLastVisibleAction(iouReport?.chatReportID ?? '', {[reportPreviewAction?.reportActionID ?? '']: null})?.created,
+ },
+ });
+ }
+
+ const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`,
value: {
[reportAction.reportActionID]: shouldDeleteIOUReport
? null
@@ -2768,9 +2733,9 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView
},
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`,
value: {
- [reportPreviewAction.reportActionID]: shouldDeleteIOUReport
+ [reportPreviewAction?.reportActionID ?? '']: shouldDeleteIOUReport
? null
: {
pendingAction: null,
@@ -2778,44 +2743,44 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView
},
},
},
- ...(shouldDeleteIOUReport
- ? [
- {
- onyxMethod: Onyx.METHOD.SET,
- key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
- value: null,
- },
- ]
- : []),
];
- const failureData = [
+ if (shouldDeleteIOUReport) {
+ successData.push({
+ onyxMethod: Onyx.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`,
+ value: null,
+ });
+ }
+
+ const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
value: transaction,
},
- ...(Permissions.canUseViolations(betas)
- ? [
- {
- onyxMethod: Onyx.METHOD.SET,
- key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`,
- value: transactionViolations,
- },
- ]
- : []),
- ...(shouldDeleteTransactionThread
- ? [
- {
- onyxMethod: Onyx.METHOD.SET,
- key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`,
- value: transactionThread,
- },
- ]
- : []),
+ ];
+
+ if (Permissions.canUseViolations(betas)) {
+ failureData.push({
+ onyxMethod: Onyx.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`,
+ value: transactionViolations,
+ });
+ }
+
+ if (shouldDeleteTransactionThread) {
+ failureData.push({
+ onyxMethod: Onyx.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`,
+ value: transactionThread,
+ });
+ }
+
+ failureData.push(
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`,
value: {
[reportAction.reportActionID]: {
...reportAction,
@@ -2824,78 +2789,82 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView
},
},
},
- {
- onyxMethod: shouldDeleteIOUReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
- value: iouReport,
- },
+ shouldDeleteIOUReport
+ ? {
+ onyxMethod: Onyx.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`,
+ value: iouReport,
+ }
+ : {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`,
+ value: iouReport,
+ },
{
onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`,
value: {
- [reportPreviewAction.reportActionID]: {
+ [reportPreviewAction?.reportActionID ?? '']: {
...reportPreviewAction,
errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericDeleteFailureMessage'),
},
},
},
- ...(shouldDeleteIOUReport
- ? [
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`,
- value: chatReport,
- },
- ]
- : []),
- ...(!shouldDeleteIOUReport && updatedReportPreviewAction.childMoneyRequestCount === 0
- ? [
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`,
- value: {
- hasOutstandingChildRequest: true,
- },
- },
- ]
- : []),
- ];
+ );
+
+ if (chatReport && shouldDeleteIOUReport) {
+ failureData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`,
+ value: chatReport,
+ });
+ }
+
+ if (!shouldDeleteIOUReport && updatedReportPreviewAction.childMoneyRequestCount === 0) {
+ failureData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`,
+ value: {
+ hasOutstandingChildRequest: true,
+ },
+ });
+ }
+
+ const parameters: DeleteMoneyRequestParams = {
+ transactionID,
+ reportActionID: reportAction.reportActionID,
+ };
// STEP 6: Make the API request
- API.write(
- 'DeleteMoneyRequest',
- {
- transactionID,
- reportActionID: reportAction.reportActionID,
- },
- {optimisticData, successData, failureData},
- );
+ API.write(WRITE_COMMANDS.DELETE_MONEY_REQUEST, parameters, {optimisticData, successData, failureData});
// STEP 7: Navigate the user depending on which page they are on and which resources were deleted
- if (isSingleTransactionView && shouldDeleteTransactionThread && !shouldDeleteIOUReport) {
+ if (iouReport && isSingleTransactionView && shouldDeleteTransactionThread && !shouldDeleteIOUReport) {
// Pop the deleted report screen before navigating. This prevents navigating to the Concierge chat due to the missing report.
Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(iouReport.reportID));
return;
}
- if (shouldDeleteIOUReport) {
+ if (iouReport?.chatReportID && shouldDeleteIOUReport) {
// Pop the deleted report screen before navigating. This prevents navigating to the Concierge chat due to the missing report.
Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(iouReport.chatReportID));
}
}
/**
- * @param {Object} report
- * @param {Number} amount
- * @param {String} currency
- * @param {String} comment
- * @param {String} paymentMethodType
- * @param {String} managerID - Account ID of the person sending the money
- * @param {Object} recipient - The user receiving the money
- * @returns {Object}
- */
-function getSendMoneyParams(report, amount, currency, comment, paymentMethodType, managerID, recipient) {
- const recipientEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(recipient.login);
+ * @param managerID - Account ID of the person sending the money
+ * @param recipient - The user receiving the money
+ */
+function getSendMoneyParams(
+ report: OnyxTypes.Report,
+ amount: number,
+ currency: string,
+ comment: string,
+ paymentMethodType: PaymentMethodType,
+ managerID: number,
+ recipient: Participant,
+): SendMoneyParamsData {
+ const recipientEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(recipient.login ?? '');
const recipientAccountID = Number(recipient.accountID);
const newIOUReportDetails = JSON.stringify({
amount,
@@ -2918,7 +2887,7 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType
const optimisticIOUReport = ReportUtils.buildOptimisticIOUReport(recipientAccountID, managerID, amount, chatReport.reportID, currency, true);
const optimisticTransaction = TransactionUtils.buildOptimisticTransaction(amount, currency, optimisticIOUReport.reportID, comment);
- const optimisticTransactionData = {
+ const optimisticTransactionData: OnyxUpdate = {
onyxMethod: Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`,
value: optimisticTransaction,
@@ -2941,36 +2910,49 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType
const reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport, optimisticIOUReport);
- // First, add data that will be used in all cases
- const optimisticChatReportData = {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`,
- value: {
- ...chatReport,
- lastReadTime: DateUtils.getDBTime(),
- lastVisibleActionCreated: reportPreviewAction.created,
- },
- };
- const optimisticIOUReportData = {
+ // Change the method to set for new reports because it doesn't exist yet, is faster,
+ // and we need the data to be available when we navigate to the chat page
+ const optimisticChatReportData: OnyxUpdate = isNewChat
+ ? {
+ onyxMethod: Onyx.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`,
+ value: {
+ ...chatReport,
+ // Set and clear pending fields on the chat report
+ pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD},
+ lastReadTime: DateUtils.getDBTime(),
+ lastVisibleActionCreated: reportPreviewAction.created,
+ },
+ }
+ : {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`,
+ value: {
+ ...chatReport,
+ lastReadTime: DateUtils.getDBTime(),
+ lastVisibleActionCreated: reportPreviewAction.created,
+ },
+ };
+ const optimisticIOUReportData: OnyxUpdate = {
onyxMethod: Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticIOUReport.reportID}`,
value: {
...optimisticIOUReport,
- lastMessageText: optimisticIOUReportAction.message[0].text,
- lastMessageHtml: optimisticIOUReportAction.message[0].html,
+ lastMessageText: optimisticIOUReportAction.message?.[0].text,
+ lastMessageHtml: optimisticIOUReportAction.message?.[0].html,
},
};
- const optimisticIOUReportActionsData = {
+ const optimisticIOUReportActionsData: OnyxUpdate = {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticIOUReport.reportID}`,
value: {
[optimisticIOUReportAction.reportActionID]: {
- ...optimisticIOUReportAction,
+ ...(optimisticIOUReportAction as OnyxTypes.ReportAction),
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
},
},
};
- const optimisticChatReportActionsData = {
+ const optimisticChatReportActionsData: OnyxUpdate = {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`,
value: {
@@ -2978,7 +2960,7 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType
},
};
- const successData = [
+ const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticIOUReport.reportID}`,
@@ -3004,7 +2986,7 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType
},
];
- const failureData = [
+ const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`,
@@ -3014,26 +2996,19 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType
},
];
- let optimisticPersonalDetailListData = {};
+ let optimisticPersonalDetailListData: OnyxUpdate | EmptyObject = {};
// Now, let's add the data we need just when we are creating a new chat report
if (isNewChat) {
- // Change the method to set for new reports because it doesn't exist yet, is faster,
- // and we need the data to be available when we navigate to the chat page
- optimisticChatReportData.onyxMethod = Onyx.METHOD.SET;
- optimisticIOUReportData.onyxMethod = Onyx.METHOD.SET;
-
- // Set and clear pending fields on the chat report
- optimisticChatReportData.value.pendingFields = {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD};
successData.push({
onyxMethod: Onyx.METHOD.MERGE,
- key: optimisticChatReportData.key,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`,
value: {pendingFields: null},
});
failureData.push(
{
onyxMethod: Onyx.METHOD.MERGE,
- key: optimisticChatReportData.key,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`,
value: {
errorFields: {
createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage'),
@@ -3059,14 +3034,18 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType
[recipientAccountID]: {
accountID: recipientAccountID,
avatar: UserUtils.getDefaultAvatarURL(recipient.accountID),
+ // Disabling this line since participant.displayName can be an empty string
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
displayName: recipient.displayName || recipient.login,
login: recipient.login,
},
},
};
- // Add an optimistic created action to the optimistic chat reportActions data
- optimisticChatReportActionsData.value[optimisticCreatedAction.reportActionID] = optimisticCreatedAction;
+ if (optimisticChatReportActionsData.value) {
+ // Add an optimistic created action to the optimistic chat reportActions data
+ optimisticChatReportActionsData.value[optimisticCreatedAction.reportActionID] = optimisticCreatedAction;
+ }
} else {
failureData.push({
onyxMethod: Onyx.METHOD.MERGE,
@@ -3079,8 +3058,8 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType
});
}
- const optimisticData = [optimisticChatReportData, optimisticIOUReportData, optimisticChatReportActionsData, optimisticIOUReportActionsData, optimisticTransactionData];
- if (!_.isEmpty(optimisticPersonalDetailListData)) {
+ const optimisticData: OnyxUpdate[] = [optimisticChatReportData, optimisticIOUReportData, optimisticChatReportActionsData, optimisticIOUReportActionsData, optimisticTransactionData];
+ if (!isEmptyObject(optimisticPersonalDetailListData)) {
optimisticData.push(optimisticPersonalDetailListData);
}
@@ -3092,7 +3071,7 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType
paymentMethodType,
transactionID: optimisticTransaction.transactionID,
newIOUReportDetails,
- createdReportActionID: isNewChat ? optimisticCreatedAction.reportActionID : 0,
+ createdReportActionID: isNewChat ? optimisticCreatedAction.reportActionID : '',
reportPreviewReportActionID: reportPreviewAction.reportActionID,
},
optimisticData,
@@ -3101,18 +3080,11 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType
};
}
-/**
- * @param {Object} chatReport
- * @param {Object} iouReport
- * @param {Object} recipient
- * @param {String} paymentMethodType
- * @returns {Object}
- */
-function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMethodType) {
+function getPayMoneyRequestParams(chatReport: OnyxTypes.Report, iouReport: OnyxTypes.Report, recipient: Participant, paymentMethodType: PaymentMethodType): PayMoneyRequestData {
const optimisticIOUReportAction = ReportUtils.buildOptimisticIOUReportAction(
CONST.IOU.REPORT_ACTION_TYPE.PAY,
- -iouReport.total,
- iouReport.currency,
+ -(iouReport.total ?? 0),
+ iouReport.currency ?? '',
'',
[recipient],
'',
@@ -3129,9 +3101,9 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho
optimisticReportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction, true);
}
- const currentNextStep = lodashGet(allNextSteps, `${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport.reportID}`, null);
+ const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport.reportID}`] ?? null;
- const optimisticData = [
+ const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`,
@@ -3141,8 +3113,8 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho
lastVisibleActionCreated: optimisticIOUReportAction.created,
hasOutstandingChildRequest: false,
iouReportID: null,
- lastMessageText: optimisticIOUReportAction.message[0].text,
- lastMessageHtml: optimisticIOUReportAction.message[0].html,
+ lastMessageText: optimisticIOUReportAction.message?.[0].text,
+ lastMessageHtml: optimisticIOUReportAction.message?.[0].html,
},
},
{
@@ -3150,7 +3122,7 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
value: {
[optimisticIOUReportAction.reportActionID]: {
- ...optimisticIOUReportAction,
+ ...(optimisticIOUReportAction as OnyxTypes.ReportAction),
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
},
},
@@ -3160,8 +3132,8 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho
key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
value: {
...iouReport,
- lastMessageText: optimisticIOUReportAction.message[0].text,
- lastMessageHtml: optimisticIOUReportAction.message[0].html,
+ lastMessageText: optimisticIOUReportAction.message?.[0].text,
+ lastMessageHtml: optimisticIOUReportAction.message?.[0].html,
hasOutstandingChildRequest: false,
statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED,
},
@@ -3169,11 +3141,11 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
- value: {[iouReport.policyID]: paymentMethodType},
+ value: {[iouReport.policyID ?? '']: paymentMethodType},
},
];
- const successData = [
+ const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
@@ -3185,7 +3157,7 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho
},
];
- const failureData = [
+ const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
@@ -3207,7 +3179,7 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho
},
];
- if (!_.isNull(currentNextStep)) {
+ if (currentNextStep) {
optimisticData.push({
onyxMethod: Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport.reportID}`,
@@ -3254,17 +3226,13 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho
}
/**
- * @param {Object} report
- * @param {Number} amount
- * @param {String} currency
- * @param {String} comment
- * @param {String} managerID - Account ID of the person sending the money
- * @param {Object} recipient - The user receiving the money
+ * @param managerID - Account ID of the person sending the money
+ * @param recipient - The user receiving the money
*/
-function sendMoneyElsewhere(report, amount, currency, comment, managerID, recipient) {
+function sendMoneyElsewhere(report: OnyxTypes.Report, amount: number, currency: string, comment: string, managerID: number, recipient: Participant) {
const {params, optimisticData, successData, failureData} = getSendMoneyParams(report, amount, currency, comment, CONST.IOU.PAYMENT_TYPE.ELSEWHERE, managerID, recipient);
- API.write('SendMoneyElsewhere', params, {optimisticData, successData, failureData});
+ API.write(WRITE_COMMANDS.SEND_MONEY_ELSEWHERE, params, {optimisticData, successData, failureData});
resetMoneyRequestInfo();
Navigation.dismissModal(params.chatReportID);
@@ -3272,52 +3240,48 @@ function sendMoneyElsewhere(report, amount, currency, comment, managerID, recipi
}
/**
- * @param {Object} report
- * @param {Number} amount
- * @param {String} currency
- * @param {String} comment
- * @param {String} managerID - Account ID of the person sending the money
- * @param {Object} recipient - The user receiving the money
+ * @param managerID - Account ID of the person sending the money
+ * @param recipient - The user receiving the money
*/
-function sendMoneyWithWallet(report, amount, currency, comment, managerID, recipient) {
+function sendMoneyWithWallet(report: OnyxTypes.Report, amount: number, currency: string, comment: string, managerID: number, recipient: Participant) {
const {params, optimisticData, successData, failureData} = getSendMoneyParams(report, amount, currency, comment, CONST.IOU.PAYMENT_TYPE.EXPENSIFY, managerID, recipient);
- API.write('SendMoneyWithWallet', params, {optimisticData, successData, failureData});
+ API.write(WRITE_COMMANDS.SEND_MONEY_WITH_WALLET, params, {optimisticData, successData, failureData});
resetMoneyRequestInfo();
Navigation.dismissModal(params.chatReportID);
Report.notifyNewAction(params.chatReportID, managerID);
}
-function approveMoneyRequest(expenseReport) {
- const currentNextStep = lodashGet(allNextSteps, `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, null);
+function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject) {
+ const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`] ?? null;
- const optimisticApprovedReportAction = ReportUtils.buildOptimisticApprovedReportAction(expenseReport.total, expenseReport.currency, expenseReport.reportID);
+ const optimisticApprovedReportAction = ReportUtils.buildOptimisticApprovedReportAction(expenseReport.total ?? 0, expenseReport.currency ?? '', expenseReport.reportID);
- const optimisticReportActionsData = {
+ const optimisticReportActionsData: OnyxUpdate = {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`,
value: {
[optimisticApprovedReportAction.reportActionID]: {
- ...optimisticApprovedReportAction,
+ ...(optimisticApprovedReportAction as OnyxTypes.ReportAction),
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
},
},
};
- const optimisticIOUReportData = {
+ const optimisticIOUReportData: OnyxUpdate = {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`,
value: {
...expenseReport,
- lastMessageText: optimisticApprovedReportAction.message[0].text,
- lastMessageHtml: optimisticApprovedReportAction.message[0].html,
+ lastMessageText: optimisticApprovedReportAction.message?.[0].text,
+ lastMessageHtml: optimisticApprovedReportAction.message?.[0].html,
stateNum: CONST.REPORT.STATE_NUM.APPROVED,
statusNum: CONST.REPORT.STATUS_NUM.APPROVED,
},
};
- const optimisticData = [optimisticIOUReportData, optimisticReportActionsData];
+ const optimisticData: OnyxUpdate[] = [optimisticIOUReportData, optimisticReportActionsData];
- const successData = [
+ const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`,
@@ -3329,19 +3293,19 @@ function approveMoneyRequest(expenseReport) {
},
];
- const failureData = [
+ const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`,
value: {
- [expenseReport.reportActionID]: {
+ [expenseReport.reportActionID ?? '']: {
errors: ErrorUtils.getMicroSecondOnyxError('iou.error.other'),
},
},
},
];
- if (!_.isNull(currentNextStep)) {
+ if (currentNextStep) {
optimisticData.push({
onyxMethod: Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`,
@@ -3354,27 +3318,29 @@ function approveMoneyRequest(expenseReport) {
});
}
- API.write('ApproveMoneyRequest', {reportID: expenseReport.reportID, approvedReportActionID: optimisticApprovedReportAction.reportActionID}, {optimisticData, successData, failureData});
+ const parameters: ApproveMoneyRequestParams = {
+ reportID: expenseReport.reportID,
+ approvedReportActionID: optimisticApprovedReportAction.reportActionID,
+ };
+
+ API.write(WRITE_COMMANDS.APPROVE_MONEY_REQUEST, parameters, {optimisticData, successData, failureData});
}
-/**
- * @param {Object} expenseReport
- */
-function submitReport(expenseReport) {
- const currentNextStep = lodashGet(allNextSteps, `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, null);
+function submitReport(expenseReport: OnyxTypes.Report) {
+ const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`] ?? null;
- const optimisticSubmittedReportAction = ReportUtils.buildOptimisticSubmittedReportAction(expenseReport.total, expenseReport.currency, expenseReport.reportID);
+ const optimisticSubmittedReportAction = ReportUtils.buildOptimisticSubmittedReportAction(expenseReport?.total ?? 0, expenseReport.currency ?? '', expenseReport.reportID);
const parentReport = ReportUtils.getReport(expenseReport.parentReportID);
const policy = ReportUtils.getPolicy(expenseReport.policyID);
const isCurrentUserManager = currentUserPersonalDetails.accountID === expenseReport.managerID;
- const optimisticData = [
+ const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`,
value: {
[optimisticSubmittedReportAction.reportActionID]: {
- ...optimisticSubmittedReportAction,
+ ...(optimisticSubmittedReportAction as OnyxTypes.ReportAction),
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
},
},
@@ -3384,30 +3350,28 @@ function submitReport(expenseReport) {
key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`,
value: {
...expenseReport,
- lastMessageText: lodashGet(optimisticSubmittedReportAction, 'message.0.text', ''),
- lastMessageHtml: lodashGet(optimisticSubmittedReportAction, 'message.0.html', ''),
+ lastMessageText: optimisticSubmittedReportAction.message?.[0].text ?? '',
+ lastMessageHtml: optimisticSubmittedReportAction.message?.[0].html ?? '',
stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
},
},
- ...(parentReport.reportID
- ? [
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`,
- value: {
- ...parentReport,
-
- // In case its a manager who force submitted the report, they are the next user who needs to take an action
- hasOutstandingChildRequest: isCurrentUserManager,
- iouReportID: null,
- },
- },
- ]
- : []),
];
- const successData = [
+ if (parentReport?.reportID) {
+ optimisticData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`,
+ value: {
+ ...parentReport,
+ // In case its a manager who force submitted the report, they are the next user who needs to take an action
+ hasOutstandingChildRequest: isCurrentUserManager,
+ iouReportID: null,
+ },
+ });
+ }
+
+ const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`,
@@ -3419,7 +3383,7 @@ function submitReport(expenseReport) {
},
];
- const failureData = [
+ const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`,
@@ -3437,21 +3401,20 @@ function submitReport(expenseReport) {
stateNum: CONST.REPORT.STATE_NUM.OPEN,
},
},
- ...(parentReport.reportID
- ? [
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`,
- value: {
- hasOutstandingChildRequest: parentReport.hasOutstandingChildRequest,
- iouReportID: expenseReport.reportID,
- },
- },
- ]
- : []),
];
- if (!_.isNull(currentNextStep)) {
+ if (parentReport?.reportID) {
+ failureData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`,
+ value: {
+ hasOutstandingChildRequest: parentReport.hasOutstandingChildRequest,
+ iouReportID: expenseReport.reportID,
+ },
+ });
+ }
+
+ if (currentNextStep) {
optimisticData.push({
onyxMethod: Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`,
@@ -3464,40 +3427,32 @@ function submitReport(expenseReport) {
});
}
- API.write(
- 'SubmitReport',
- {
- reportID: expenseReport.reportID,
- managerAccountID: policy.submitsTo || expenseReport.managerID,
- reportActionID: optimisticSubmittedReportAction.reportActionID,
- },
- {optimisticData, successData, failureData},
- );
+ const parameters: SubmitReportParams = {
+ reportID: expenseReport.reportID,
+ managerAccountID: policy.submitsTo ?? expenseReport.managerID,
+ reportActionID: optimisticSubmittedReportAction.reportActionID,
+ };
+
+ API.write(WRITE_COMMANDS.SUBMIT_REPORT, parameters, {optimisticData, successData, failureData});
}
-/**
- * @param {String} paymentType
- * @param {Object} chatReport
- * @param {Object} iouReport
- * @param {String} reimbursementBankAccountState
- */
-function payMoneyRequest(paymentType, chatReport, iouReport) {
+function payMoneyRequest(paymentType: PaymentMethodType, chatReport: OnyxTypes.Report, iouReport: OnyxTypes.Report) {
const recipient = {accountID: iouReport.ownerAccountID};
const {params, optimisticData, successData, failureData} = getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentType);
// For now we need to call the PayMoneyRequestWithWallet API since PayMoneyRequest was not updated to work with
// Expensify Wallets.
- const apiCommand = paymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY ? 'PayMoneyRequestWithWallet' : 'PayMoneyRequest';
+ const apiCommand = paymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY ? WRITE_COMMANDS.PAY_MONEY_REQUEST_WITH_WALLET : WRITE_COMMANDS.PAY_MONEY_REQUEST;
API.write(apiCommand, params, {optimisticData, successData, failureData});
Navigation.dismissModalWithReport(chatReport);
}
-function detachReceipt(transactionID) {
- const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] || {};
- const newTransaction = {...transaction, filename: '', receipt: {}};
+function detachReceipt(transactionID: string) {
+ const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
+ const newTransaction = transaction ? {...transaction, filename: '', receipt: {}} : null;
- const optimisticData = [
+ const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
@@ -3505,7 +3460,7 @@ function detachReceipt(transactionID) {
},
];
- const failureData = [
+ const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
@@ -3513,22 +3468,20 @@ function detachReceipt(transactionID) {
},
];
- API.write('DetachReceipt', {transactionID}, {optimisticData, failureData});
+ const parameters: DetachReceiptParams = {transactionID};
+
+ API.write(WRITE_COMMANDS.DETACH_RECEIPT, parameters, {optimisticData, failureData});
}
-/**
- * @param {String} transactionID
- * @param {Object} file
- * @param {String} source
- */
-function replaceReceipt(transactionID, file, source) {
- const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] || {};
- const oldReceipt = lodashGet(transaction, 'receipt', {});
+function replaceReceipt(transactionID: string, file: File, source: string) {
+ const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
+ const oldReceipt = transaction?.receipt ?? {};
const receiptOptimistic = {
source,
state: CONST.IOU.RECEIPT_STATE.OPEN,
};
- const optimisticData = [
+
+ const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
@@ -3539,95 +3492,73 @@ function replaceReceipt(transactionID, file, source) {
},
];
- const failureData = [
+ const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
value: {
receipt: oldReceipt,
- filename: transaction.filename,
+ filename: transaction?.filename,
errors: getReceiptError(receiptOptimistic, file.name),
},
},
];
- API.write('ReplaceReceipt', {transactionID, receipt: file}, {optimisticData, failureData});
+ const parameters: ReplaceReceiptParams = {
+ transactionID,
+ receipt: file,
+ };
+
+ API.write(WRITE_COMMANDS.REPLACE_RECEIPT, parameters, {optimisticData, failureData});
}
/**
* Finds the participants for an IOU based on the attached report
- * @param {String} transactionID of the transaction to set the participants of
- * @param {Object} report attached to the transaction
+ * @param transactionID of the transaction to set the participants of
+ * @param report attached to the transaction
*/
-function setMoneyRequestParticipantsFromReport(transactionID, report) {
+function setMoneyRequestParticipantsFromReport(transactionID: string, report: OnyxTypes.Report) {
// If the report is iou or expense report, we should get the chat report to set participant for request money
const chatReport = ReportUtils.isMoneyRequestReport(report) ? ReportUtils.getReport(report.chatReportID) : report;
const currentUserAccountID = currentUserPersonalDetails.accountID;
- const participants = ReportUtils.isPolicyExpenseChat(chatReport)
- ? [{reportID: chatReport.reportID, isPolicyExpenseChat: true, selected: true}]
- : _.chain(chatReport.participantAccountIDs)
- .filter((accountID) => currentUserAccountID !== accountID)
- .map((accountID) => ({accountID, selected: true}))
- .value();
+ const participants: Participant[] = ReportUtils.isPolicyExpenseChat(chatReport)
+ ? [{reportID: chatReport?.reportID, isPolicyExpenseChat: true, selected: true}]
+ : (chatReport?.participantAccountIDs ?? []).filter((accountID) => currentUserAccountID !== accountID).map((accountID) => ({accountID, selected: true}));
+
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {participants, participantsAutoAssigned: true});
}
-/**
- * Initialize money request info and navigate to the MoneyRequest page
- * @param {String} iouType
- * @param {String} reportID
- */
-function startMoneyRequest(iouType, reportID = '') {
+/** Initialize money request info and navigate to the MoneyRequest page */
+function startMoneyRequest(iouType: string, reportID = '') {
resetMoneyRequestInfo(`${iouType}${reportID}`);
Navigation.navigate(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID));
}
-/**
- * @param {String} id
- */
-function setMoneyRequestId(id) {
+function setMoneyRequestId(id: string) {
Onyx.merge(ONYXKEYS.IOU, {id});
}
-/**
- * @param {Number} amount
- */
-function setMoneyRequestAmount(amount) {
+function setMoneyRequestAmount(amount: number) {
Onyx.merge(ONYXKEYS.IOU, {amount});
}
-/**
- * @param {String} created
- */
-function setMoneyRequestCreated(created) {
+function setMoneyRequestCreated(created: string) {
Onyx.merge(ONYXKEYS.IOU, {created});
}
-/**
- * @param {String} currency
- */
-function setMoneyRequestCurrency(currency) {
+function setMoneyRequestCurrency(currency: string) {
Onyx.merge(ONYXKEYS.IOU, {currency});
}
-/**
- * @param {String} comment
- */
-function setMoneyRequestDescription(comment) {
+function setMoneyRequestDescription(comment: string) {
Onyx.merge(ONYXKEYS.IOU, {comment: comment.trim()});
}
-/**
- * @param {String} merchant
- */
-function setMoneyRequestMerchant(merchant) {
+function setMoneyRequestMerchant(merchant: string) {
Onyx.merge(ONYXKEYS.IOU, {merchant: merchant.trim()});
}
-/**
- * @param {String} category
- */
-function setMoneyRequestCategory(category) {
+function setMoneyRequestCategory(category: string) {
Onyx.merge(ONYXKEYS.IOU, {category});
}
@@ -3635,45 +3566,19 @@ function resetMoneyRequestCategory() {
Onyx.merge(ONYXKEYS.IOU, {category: ''});
}
-/*
- * @param {String} tag
- */
-function setMoneyRequestTag(tag) {
- Onyx.merge(ONYXKEYS.IOU, {tag});
-}
-
-function resetMoneyRequestTag() {
- Onyx.merge(ONYXKEYS.IOU, {tag: ''});
-}
-
-/**
- * @param {String} transactionID
- * @param {Object} taxRate
- */
-function setMoneyRequestTaxRate(transactionID, taxRate) {
+function setMoneyRequestTaxRate(transactionID: string, taxRate: TaxRate) {
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {taxRate});
}
-/**
- * @param {String} transactionID
- * @param {Number} taxAmount
- */
-function setMoneyRequestTaxAmount(transactionID, taxAmount) {
+function setMoneyRequestTaxAmount(transactionID: string, taxAmount: number) {
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {taxAmount});
}
-/**
- * @param {Boolean} billable
- */
-function setMoneyRequestBillable(billable) {
+function setMoneyRequestBillable(billable: boolean) {
Onyx.merge(ONYXKEYS.IOU, {billable});
}
-/**
- * @param {Object[]} participants
- * @param {Boolean} isSplitRequest
- */
-function setMoneyRequestParticipants(participants, isSplitRequest) {
+function setMoneyRequestParticipants(participants: Participant[], isSplitRequest?: boolean) {
Onyx.merge(ONYXKEYS.IOU, {participants, isSplitRequest});
}
@@ -3686,18 +3591,10 @@ function setUpDistanceTransaction() {
Onyx.merge(ONYXKEYS.IOU, {transactionID});
}
-/**
- * Navigates to the next IOU page based on where the IOU request was started
- *
- * @param {Object} iou
- * @param {String} iouType
- * @param {Object} report
- * @param {String} report.reportID
- * @param {String} path
- */
-function navigateToNextPage(iou, iouType, report, path = '') {
- const moneyRequestID = `${iouType}${report.reportID || ''}`;
- const shouldReset = iou.id !== moneyRequestID && !_.isEmpty(report.reportID);
+/** Navigates to the next IOU page based on where the IOU request was started */
+function navigateToNextPage(iou: OnyxEntry, iouType: string, report?: OnyxTypes.Report, path = '') {
+ const moneyRequestID = `${iouType}${report?.reportID ?? ''}`;
+ const shouldReset = iou?.id !== moneyRequestID && !!report?.reportID;
// If the money request ID in Onyx does not match the ID from params, we want to start a new request
// with the ID from params. We need to clear the participants in case the new request is initiated from FAB.
@@ -3706,27 +3603,23 @@ function navigateToNextPage(iou, iouType, report, path = '') {
}
// If we're adding a receipt, that means the user came from the confirmation page and we need to navigate back to it.
- if (path.slice(1) === ROUTES.MONEY_REQUEST_RECEIPT.getRoute(iouType, report.reportID)) {
- Navigation.navigate(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, report.reportID));
+ if (path.slice(1) === ROUTES.MONEY_REQUEST_RECEIPT.getRoute(iouType, report?.reportID)) {
+ Navigation.navigate(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, report?.reportID));
return;
}
// If a request is initiated on a report, skip the participants selection step and navigate to the confirmation page.
- if (report.reportID) {
+ if (report?.reportID) {
// If the report is iou or expense report, we should get the chat report to set participant for request money
const chatReport = ReportUtils.isMoneyRequestReport(report) ? ReportUtils.getReport(report.chatReportID) : report;
// Reinitialize the participants when the money request ID in Onyx does not match the ID from params
- if (_.isEmpty(iou.participants) || shouldReset) {
+ if (!iou?.participants?.length || shouldReset) {
const currentUserAccountID = currentUserPersonalDetails.accountID;
- const participants = ReportUtils.isPolicyExpenseChat(chatReport)
- ? [{reportID: chatReport.reportID, isPolicyExpenseChat: true, selected: true}]
- : _.chain(chatReport.participantAccountIDs)
- .filter((accountID) => currentUserAccountID !== accountID)
- .map((accountID) => ({accountID, selected: true}))
- .value();
+ const participants: Participant[] = ReportUtils.isPolicyExpenseChat(chatReport)
+ ? [{reportID: chatReport?.reportID, isPolicyExpenseChat: true, selected: true}]
+ : (chatReport?.participantAccountIDs ?? []).filter((accountID) => currentUserAccountID !== accountID).map((accountID) => ({accountID, selected: true}));
setMoneyRequestParticipants(participants);
resetMoneyRequestCategory();
- resetMoneyRequestTag();
}
Navigation.navigate(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, report.reportID));
return;
@@ -3738,28 +3631,23 @@ function navigateToNextPage(iou, iouType, report, path = '') {
* When the money request or split bill creation flow is initialized via FAB, the reportID is not passed as a navigation
* parameter.
* Gets a report id from the first participant of the IOU object stored in Onyx.
- * @param {Object} iou
- * @param {Array} iou.participants
- * @param {Object} route
- * @param {Object} route.params
- * @param {String} [route.params.reportID]
- * @returns {String}
*/
-function getIOUReportID(iou, route) {
- return lodashGet(route, 'params.reportID') || lodashGet(iou, 'participants.0.reportID', '');
+function getIOUReportID(iou?: OnyxTypes.IOU, route?: MoneyRequestRoute): string {
+ // Disabling this line for safeness as nullish coalescing works only if the value is undefined or null
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ return route?.params.reportID || iou?.participants?.[0]?.reportID || '';
}
-/**
- * @param {String} receiptFilename
- * @param {String} receiptPath
- * @param {Function} onSuccess
- * @param {String} requestType
- * @param {String} iouType
- * @param {String} transactionID
- * @param {String} reportID
- */
// eslint-disable-next-line rulesdir/no-negated-variables
-function navigateToStartStepIfScanFileCannotBeRead(receiptFilename, receiptPath, onSuccess, requestType, iouType, transactionID, reportID) {
+function navigateToStartStepIfScanFileCannotBeRead(
+ receiptFilename: string,
+ receiptPath: string,
+ onSuccess: (file: File) => void,
+ requestType: ValueOf,
+ iouType: ValueOf,
+ transactionID: string,
+ reportID: string,
+) {
if (!receiptFilename || !receiptPath) {
return;
}
@@ -3775,12 +3663,8 @@ function navigateToStartStepIfScanFileCannotBeRead(receiptFilename, receiptPath,
FileUtils.readFileAsync(receiptPath, receiptFilename, onSuccess, onFailure);
}
-/**
- * Save the preferred payment method for a policy
- * @param {String} policyID
- * @param {String} paymentMethod
- */
-function savePreferredPaymentMethod(policyID, paymentMethod) {
+/** Save the preferred payment method for a policy */
+function savePreferredPaymentMethod(policyID: string, paymentMethod: PaymentMethodType) {
Onyx.merge(`${ONYXKEYS.NVP_LAST_PAYMENT_METHOD}`, {[policyID]: paymentMethod});
}
@@ -3804,19 +3688,17 @@ export {
resetMoneyRequestCategory,
resetMoneyRequestCategory_temporaryForRefactor,
resetMoneyRequestInfo,
- resetMoneyRequestTag,
- resetMoneyRequestTag_temporaryForRefactor,
clearMoneyRequest,
setMoneyRequestAmount_temporaryForRefactor,
setMoneyRequestBillable_temporaryForRefactor,
setMoneyRequestCategory_temporaryForRefactor,
setMoneyRequestCreated_temporaryForRefactor,
setMoneyRequestCurrency_temporaryForRefactor,
+ setMoneyRequestOriginalCurrency_temporaryForRefactor,
setMoneyRequestDescription_temporaryForRefactor,
setMoneyRequestMerchant_temporaryForRefactor,
setMoneyRequestParticipants_temporaryForRefactor,
setMoneyRequestReceipt,
- setMoneyRequestTag_temporaryForRefactor,
setMoneyRequestAmount,
setMoneyRequestBillable,
setMoneyRequestCategory,
diff --git a/src/libs/actions/PaymentMethods.ts b/src/libs/actions/PaymentMethods.ts
index cbc5778187a1..2199c5768612 100644
--- a/src/libs/actions/PaymentMethods.ts
+++ b/src/libs/actions/PaymentMethods.ts
@@ -3,7 +3,6 @@ import type {MutableRefObject} from 'react';
import type {GestureResponderEvent} from 'react-native';
import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
-import type {TransferMethod} from '@components/KYCWall/types';
import * as API from '@libs/API';
import type {AddPaymentCardParams, DeletePaymentCardParams, MakeDefaultPaymentMethodParams, PaymentCardParams, TransferWalletBalanceParams} from '@libs/API/parameters';
import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
@@ -14,11 +13,12 @@ import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Route} from '@src/ROUTES';
import type {BankAccountList, FundList} from '@src/types/onyx';
+import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
import type PaymentMethod from '@src/types/onyx/PaymentMethod';
import type {FilterMethodPaymentType} from '@src/types/onyx/WalletTransfer';
type KYCWallRef = {
- continueAction?: (event?: GestureResponderEvent | KeyboardEvent, iouPaymentType?: TransferMethod) => void;
+ continueAction?: (event?: GestureResponderEvent | KeyboardEvent, iouPaymentType?: PaymentMethodType) => void;
};
/**
diff --git a/src/libs/actions/PersonalDetails.ts b/src/libs/actions/PersonalDetails.ts
index ac044b0bc25b..26c8937de3aa 100644
--- a/src/libs/actions/PersonalDetails.ts
+++ b/src/libs/actions/PersonalDetails.ts
@@ -1,4 +1,3 @@
-import Str from 'expensify-common/lib/str';
import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import * as API from '@libs/API';
@@ -16,7 +15,6 @@ import type {
import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import type {CustomRNImageManipulatorResult} from '@libs/cropOrRotateImage/types';
import DateUtils from '@libs/DateUtils';
-import * as LocalePhoneNumber from '@libs/LocalePhoneNumber';
import Navigation from '@libs/Navigation/Navigation';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as UserUtils from '@libs/UserUtils';
@@ -27,11 +25,6 @@ import type {DateOfBirthForm, PersonalDetails, PersonalDetailsList, PrivatePerso
import type {SelectedTimezone, Timezone} from '@src/types/onyx/PersonalDetails';
import * as Session from './Session';
-type FirstAndLastName = {
- firstName: string;
- lastName: string;
-};
-
let currentUserEmail = '';
let currentUserAccountID = -1;
Onyx.connect({
@@ -54,69 +47,6 @@ Onyx.connect({
callback: (val) => (privatePersonalDetails = val),
});
-/**
- * Creates a new displayName for a user based on passed personal details or login.
- */
-function createDisplayName(login: string, personalDetails: Pick | OnyxEntry): string {
- // If we have a number like +15857527441@expensify.sms then let's remove @expensify.sms and format it
- // so that the option looks cleaner in our UI.
- const userLogin = LocalePhoneNumber.formatPhoneNumber(login);
-
- if (!personalDetails) {
- return userLogin;
- }
-
- const firstName = personalDetails.firstName ?? '';
- const lastName = personalDetails.lastName ?? '';
- const fullName = `${firstName} ${lastName}`.trim();
-
- // It's possible for fullName to be empty string, so we must use "||" to fallback to userLogin.
- return fullName || userLogin;
-}
-
-/**
- * Gets the first and last name from the user's personal details.
- * If the login is the same as the displayName, then they don't exist,
- * so we return empty strings instead.
- */
-function extractFirstAndLastNameFromAvailableDetails({login, displayName, firstName, lastName}: PersonalDetails): FirstAndLastName {
- // It's possible for firstName to be empty string, so we must use "||" to consider lastName instead.
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- if (firstName || lastName) {
- return {firstName: firstName ?? '', lastName: lastName ?? ''};
- }
- if (login && Str.removeSMSDomain(login) === displayName) {
- return {firstName: '', lastName: ''};
- }
-
- if (displayName) {
- const firstSpaceIndex = displayName.indexOf(' ');
- const lastSpaceIndex = displayName.lastIndexOf(' ');
- if (firstSpaceIndex === -1) {
- return {firstName: displayName, lastName: ''};
- }
-
- return {
- firstName: displayName.substring(0, firstSpaceIndex).trim(),
- lastName: displayName.substring(lastSpaceIndex).trim(),
- };
- }
-
- return {firstName: '', lastName: ''};
-}
-
-/**
- * Convert country names obtained from the backend to their respective ISO codes
- * This is for backward compatibility of stored data before E/App#15507
- */
-function getCountryISO(countryName: string): string {
- if (!countryName || countryName.length === 2) {
- return countryName;
- }
-
- return Object.entries(CONST.ALL_COUNTRIES).find(([, value]) => value === countryName)?.[0] ?? '';
-}
-
function updatePronouns(pronouns: string) {
if (currentUserAccountID) {
const parameters: UpdatePronounsParams = {pronouns};
@@ -152,7 +82,7 @@ function updateDisplayName(firstName: string, lastName: string) {
[currentUserAccountID]: {
firstName,
lastName,
- displayName: createDisplayName(currentUserEmail ?? '', {
+ displayName: PersonalDetailsUtils.createDisplayName(currentUserEmail ?? '', {
firstName,
lastName,
}),
@@ -524,9 +454,6 @@ function getPrivatePersonalDetails(): OnyxEntry {
export {
clearAvatarErrors,
deleteAvatar,
- extractFirstAndLastNameFromAvailableDetails,
- getCountryISO,
- createDisplayName,
getPrivatePersonalDetails,
openPersonalDetailsPage,
openPublicProfilePage,
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index 0c3a8afc1576..866206895d5e 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -38,6 +38,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import type {PersonalDetailsList, Policy, PolicyMember, PolicyTags, RecentlyUsedCategories, RecentlyUsedTags, ReimbursementAccount, Report, ReportAction, Transaction} from '@src/types/onyx';
import type {Errors} from '@src/types/onyx/OnyxCommon';
import type {CustomUnit} from '@src/types/onyx/Policy';
+import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
type AnnounceRoomMembersOnyxData = {
@@ -1161,7 +1162,6 @@ function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', pol
isPolicyExpenseChatEnabled: true,
outputCurrency,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
- areChatRoomsEnabled: true,
customUnits,
makeMeAdmin,
},
@@ -1223,7 +1223,6 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName
isPolicyExpenseChatEnabled: true,
outputCurrency,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
- areChatRoomsEnabled: true,
customUnits,
},
},
@@ -1520,7 +1519,7 @@ function dismissAddedWithPrimaryLoginMessages(policyID: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {primaryLoginsInvited: null});
}
-function buildOptimisticPolicyRecentlyUsedCategories(policyID: string, category: string) {
+function buildOptimisticPolicyRecentlyUsedCategories(policyID?: string, category?: string) {
if (!policyID || !category) {
return [];
}
@@ -1530,7 +1529,7 @@ function buildOptimisticPolicyRecentlyUsedCategories(policyID: string, category:
return lodashUnion([category], policyRecentlyUsedCategories);
}
-function buildOptimisticPolicyRecentlyUsedTags(policyID: string, tag: string): RecentlyUsedTags {
+function buildOptimisticPolicyRecentlyUsedTags(policyID?: string, tag?: string): RecentlyUsedTags {
if (!policyID || !tag) {
return {};
}
@@ -1553,9 +1552,9 @@ function buildOptimisticPolicyRecentlyUsedTags(policyID: string, tag: string): R
*
* @returns policyID of the workspace we have created
*/
-function createWorkspaceFromIOUPayment(iouReport: Report): string | undefined {
+function createWorkspaceFromIOUPayment(iouReport: Report | EmptyObject): string | undefined {
// This flow only works for IOU reports
- if (!ReportUtils.isIOUReport(iouReport)) {
+ if (!ReportUtils.isIOUReportUsingReport(iouReport)) {
return;
}
@@ -1603,7 +1602,6 @@ function createWorkspaceFromIOUPayment(iouReport: Report): string | undefined {
// Setting the currency to USD as we can only add the VBBA for this policy currency right now
outputCurrency: CONST.CURRENCY.USD,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
- areChatRoomsEnabled: true,
customUnits,
};
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index 4bff826ceb3a..5b4fb8160894 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -38,7 +38,7 @@ import type {
UpdateReportNotificationPreferenceParams,
UpdateReportPrivateNoteParams,
UpdateReportWriteCapabilityParams,
- UpdateWelcomeMessageParams,
+ UpdateRoomDescriptionParams,
} from '@libs/API/parameters';
import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import * as CollectionUtils from '@libs/CollectionUtils';
@@ -1614,34 +1614,41 @@ function updateReportField(reportID: string, reportField: PolicyReportField, pre
API.write(WRITE_COMMANDS.SET_REPORT_FIELD, parameters, {optimisticData, failureData, successData});
}
-function updateWelcomeMessage(reportID: string, previousValue: string, newValue: string) {
+function updateDescription(reportID: string, previousValue: string, newValue: string) {
// No change needed, navigate back
if (previousValue === newValue) {
- Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(reportID));
+ Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID));
return;
}
- const parsedWelcomeMessage = ReportUtils.getParsedComment(newValue);
+ const parsedDescription = ReportUtils.getParsedComment(newValue);
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
- value: {welcomeMessage: parsedWelcomeMessage},
+ value: {description: parsedDescription, pendingFields: {description: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}},
},
];
const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
- value: {welcomeMessage: previousValue},
+ value: {description: previousValue, pendingFields: {description: null}},
+ },
+ ];
+ const successData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
+ value: {pendingFields: {description: null}},
},
];
- const parameters: UpdateWelcomeMessageParams = {reportID, welcomeMessage: parsedWelcomeMessage};
+ const parameters: UpdateRoomDescriptionParams = {reportID, description: parsedDescription};
- API.write(WRITE_COMMANDS.UPDATE_WELCOME_MESSAGE, parameters, {optimisticData, failureData});
- Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(reportID));
+ API.write(WRITE_COMMANDS.UPDATE_ROOM_DESCRIPTION, parameters, {optimisticData, failureData, successData});
+ Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID));
}
function updateWriteCapabilityAndNavigate(report: Report, newValue: WriteCapability) {
@@ -1774,7 +1781,7 @@ function addPolicyReport(policyReport: ReportUtils.OptimisticChatReport) {
reportID: policyReport.reportID,
createdReportActionID: createdReportAction.reportActionID,
writeCapability: policyReport.writeCapability,
- welcomeMessage: policyReport.welcomeMessage,
+ description: policyReport.description,
};
API.write(WRITE_COMMANDS.ADD_WORKSPACE_ROOM, parameters, {optimisticData, successData, failureData});
@@ -2821,7 +2828,7 @@ export {
addComment,
addAttachment,
reconnect,
- updateWelcomeMessage,
+ updateDescription,
updateWriteCapabilityAndNavigate,
updateNotificationPreference,
subscribeToReportTypingEvents,
diff --git a/src/libs/actions/TeachersUnite.ts b/src/libs/actions/TeachersUnite.ts
index 055d1f2b53a2..ab48609e2d53 100644
--- a/src/libs/actions/TeachersUnite.ts
+++ b/src/libs/actions/TeachersUnite.ts
@@ -91,7 +91,6 @@ function addSchoolPrincipal(firstName: string, partnerUserID: string, lastName:
value: {
id: policyID,
isPolicyExpenseChatEnabled: true,
- areChatRoomsEnabled: true,
type: CONST.POLICY.TYPE.CORPORATE,
name: policyName,
role: CONST.POLICY.ROLE.USER,
diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts
index 93739ce68e47..09cc6e49e6cc 100644
--- a/src/libs/actions/User.ts
+++ b/src/libs/actions/User.ts
@@ -24,6 +24,7 @@ import * as ErrorUtils from '@libs/ErrorUtils';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import * as SequentialQueue from '@libs/Network/SequentialQueue';
+import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as Pusher from '@libs/Pusher/pusher';
import PusherUtils from '@libs/PusherUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
@@ -40,7 +41,6 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import * as Link from './Link';
import * as OnyxUpdates from './OnyxUpdates';
-import * as PersonalDetails from './PersonalDetails';
import * as Report from './Report';
import * as Session from './Session';
@@ -705,7 +705,7 @@ function setContactMethodAsDefault(newDefaultContactMethod: string) {
value: {
[currentUserAccountID]: {
login: newDefaultContactMethod,
- displayName: PersonalDetails.createDisplayName(newDefaultContactMethod, myPersonalDetails),
+ displayName: PersonalDetailsUtils.createDisplayName(newDefaultContactMethod, myPersonalDetails),
},
},
},
diff --git a/src/libs/updateMultilineInputRange/types.ts b/src/libs/updateMultilineInputRange/types.ts
index ce8f553c51f8..3646c64e4845 100644
--- a/src/libs/updateMultilineInputRange/types.ts
+++ b/src/libs/updateMultilineInputRange/types.ts
@@ -1,5 +1,6 @@
import type {TextInput} from 'react-native';
+import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types';
-type UpdateMultilineInputRange = (input: HTMLInputElement | TextInput | null, shouldAutoFocus?: boolean) => void;
+type UpdateMultilineInputRange = (input: HTMLInputElement | BaseTextInputRef | TextInput | null, shouldAutoFocus?: boolean) => void;
export default UpdateMultilineInputRange;
diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.js b/src/pages/EnablePayments/AdditionalDetailsStep.js
index 5ca51e208b49..cf769bf58fd3 100644
--- a/src/pages/EnablePayments/AdditionalDetailsStep.js
+++ b/src/pages/EnablePayments/AdditionalDetailsStep.js
@@ -16,10 +16,10 @@ import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultPro
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
+import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import {parsePhoneNumber} from '@libs/PhoneNumber';
import * as ValidationUtils from '@libs/ValidationUtils';
import AddressForm from '@pages/ReimbursementAccount/AddressForm';
-import * as PersonalDetails from '@userActions/PersonalDetails';
import * as Wallet from '@userActions/Wallet';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -194,7 +194,7 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP
label={translate(fieldNameTranslationKeys.legalFirstName)}
accessibilityLabel={translate(fieldNameTranslationKeys.legalFirstName)}
role={CONST.ROLE.PRESENTATION}
- defaultValue={PersonalDetails.extractFirstAndLastNameFromAvailableDetails(currentUserPersonalDetails).firstName}
+ defaultValue={PersonalDetailsUtils.extractFirstAndLastNameFromAvailableDetails(currentUserPersonalDetails).firstName}
shouldSaveDraft
/>
- Navigation.navigate(ROUTES.PROFILE_AVATAR.getRoute(String(accountID)))}
+ accessibilityLabel={props.translate('common.profile')}
+ accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON}
>
- {({show}) => (
-
-
-
-
-
- )}
-
+
+
+
+
{Boolean(displayName) && (
{contentHeader}
{contentBody}
diff --git a/src/pages/ReportDescriptionPage.tsx b/src/pages/ReportDescriptionPage.tsx
new file mode 100644
index 000000000000..3ccabf30c1b7
--- /dev/null
+++ b/src/pages/ReportDescriptionPage.tsx
@@ -0,0 +1,35 @@
+import type {RouteProp} from '@react-navigation/native';
+import React from 'react';
+import type {OnyxCollection} from 'react-native-onyx';
+import * as ReportUtils from '@libs/ReportUtils';
+import type * as OnyxTypes from '@src/types/onyx';
+import withReportOrNotFound from './home/report/withReportOrNotFound';
+import RoomDescriptionPage from './RoomDescriptionPage';
+import TaskDescriptionPage from './tasks/TaskDescriptionPage';
+
+type ReportDescriptionPageProps = {
+ /** The report currently being looked at */
+ report: OnyxTypes.Report;
+
+ /** Policy for the current report */
+ policies: OnyxCollection;
+
+ /** Route params */
+ route: RouteProp<{params: {reportID: string}}>;
+};
+
+function ReportDescriptionPage(props: ReportDescriptionPageProps) {
+ const isTask = ReportUtils.isTaskReport(props.report);
+
+ if (isTask) {
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ return ;
+ }
+
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ return ;
+}
+
+ReportDescriptionPage.displayName = 'ReportDescriptionPage';
+
+export default withReportOrNotFound()(ReportDescriptionPage);
diff --git a/src/pages/ReportDetailsPage.js b/src/pages/ReportDetailsPage.js
index 513ccbbe307c..99e1cea8280a 100644
--- a/src/pages/ReportDetailsPage.js
+++ b/src/pages/ReportDetailsPage.js
@@ -8,7 +8,9 @@ import DisplayNames from '@components/DisplayNames';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Expensicons from '@components/Icon/Expensicons';
import MenuItem from '@components/MenuItem';
+import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import MultipleAvatars from '@components/MultipleAvatars';
+import OfflineWithFeedback from '@components/OfflineWithFeedback';
import {withNetwork} from '@components/OnyxProvider';
import ParentNavigationSubtitle from '@components/ParentNavigationSubtitle';
import participantPropTypes from '@components/participantPropTypes';
@@ -69,6 +71,8 @@ function ReportDetailsPage(props) {
const isUserCreatedPolicyRoom = useMemo(() => ReportUtils.isUserCreatedPolicyRoom(props.report), [props.report]);
const isArchivedRoom = useMemo(() => ReportUtils.isArchivedRoom(props.report), [props.report]);
const isMoneyRequestReport = useMemo(() => ReportUtils.isMoneyRequestReport(props.report), [props.report]);
+ const canEditReportDescription = useMemo(() => ReportUtils.canEditReportDescription(props.report, policy), [props.report, policy]);
+ const shouldShowReportDescription = isChatRoom && (canEditReportDescription || !_.isEmpty(props.report.description));
// eslint-disable-next-line react-hooks/exhaustive-deps -- policy is a dependency because `getChatRoomSubtitle` calls `getPolicyName` which in turn retrieves the value from the `policy` value stored in Onyx
const chatRoomSubtitle = useMemo(() => ReportUtils.getChatRoomSubtitle(props.report), [props.report, policy]);
@@ -238,6 +242,19 @@ function ReportDetailsPage(props) {
)}
+ {shouldShowReportDescription && (
+
+ Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(props.report.reportID))}
+ />
+
+ )}
{_.map(menuItems, (item) => {
const brickRoadIndicator =
ReportUtils.hasReportNameError(props.report) && item.key === CONST.REPORT_DETAILS_MENU_ITEM.SETTINGS ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : '';
diff --git a/src/pages/ReportWelcomeMessagePage.tsx b/src/pages/ReportWelcomeMessagePage.tsx
deleted file mode 100644
index 53f3e7fcadda..000000000000
--- a/src/pages/ReportWelcomeMessagePage.tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-import {useFocusEffect} from '@react-navigation/native';
-import type {StackScreenProps} from '@react-navigation/stack';
-import ExpensiMark from 'expensify-common/lib/ExpensiMark';
-import React, {useCallback, useRef, useState} from 'react';
-import {View} from 'react-native';
-import type {OnyxEntry} from 'react-native-onyx';
-import {withOnyx} from 'react-native-onyx';
-import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
-import FormProvider from '@components/Form/FormProvider';
-import InputWrapper from '@components/Form/InputWrapper';
-import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import type {AnimatedTextInputRef} from '@components/RNTextInput';
-import ScreenWrapper from '@components/ScreenWrapper';
-import Text from '@components/Text';
-import TextInput from '@components/TextInput';
-import useLocalize from '@hooks/useLocalize';
-import useThemeStyles from '@hooks/useThemeStyles';
-import Navigation from '@libs/Navigation/Navigation';
-import * as ReportUtils from '@libs/ReportUtils';
-import updateMultilineInputRange from '@libs/updateMultilineInputRange';
-import type {ReportWelcomeMessageNavigatorParamList} from '@navigation/types';
-import * as Report from '@userActions/Report';
-import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
-import ROUTES from '@src/ROUTES';
-import type SCREENS from '@src/SCREENS';
-import type {Policy} from '@src/types/onyx';
-import type {WithReportOrNotFoundProps} from './home/report/withReportOrNotFound';
-import withReportOrNotFound from './home/report/withReportOrNotFound';
-
-type ReportWelcomeMessagePageOnyxProps = {
- /** The policy object for the current route */
- policy: OnyxEntry;
-};
-
-type ReportWelcomeMessagePageProps = ReportWelcomeMessagePageOnyxProps &
- WithReportOrNotFoundProps &
- StackScreenProps;
-
-function ReportWelcomeMessagePage({report, policy}: ReportWelcomeMessagePageProps) {
- const styles = useThemeStyles();
- const {translate} = useLocalize();
-
- const parser = new ExpensiMark();
- const [welcomeMessage, setWelcomeMessage] = useState(() => parser.htmlToMarkdown(report?.welcomeMessage ?? ''));
- const welcomeMessageInputRef = useRef(null);
- const focusTimeoutRef = useRef(null);
-
- const handleWelcomeMessageChange = useCallback((value: string) => {
- setWelcomeMessage(value);
- }, []);
-
- const submitForm = useCallback(() => {
- Report.updateWelcomeMessage(report?.reportID ?? '', report?.welcomeMessage ?? '', welcomeMessage.trim());
- }, [report?.reportID, report?.welcomeMessage, welcomeMessage]);
-
- useFocusEffect(
- useCallback(() => {
- focusTimeoutRef.current = setTimeout(() => {
- if (welcomeMessageInputRef.current) {
- welcomeMessageInputRef.current.focus();
- }
- return () => {
- if (!focusTimeoutRef.current) {
- return;
- }
- clearTimeout(focusTimeoutRef.current);
- };
- }, CONST.ANIMATED_TRANSITION);
- }, []),
- );
-
- return (
-
-
- Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? ''))}
- />
-
- {translate('welcomeMessagePage.explainerText')}
-
- {
- if (!element) {
- return;
- }
- welcomeMessageInputRef.current = element;
- updateMultilineInputRange(welcomeMessageInputRef.current);
- }}
- value={welcomeMessage}
- onChangeText={handleWelcomeMessageChange}
- autoCapitalize="none"
- containerStyles={[styles.autoGrowHeightMultilineInput]}
- />
-
-
-
-
- );
-}
-
-ReportWelcomeMessagePage.displayName = 'ReportWelcomeMessagePage';
-
-export default withReportOrNotFound()(
- withOnyx({
- policy: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`,
- },
- })(ReportWelcomeMessagePage),
-);
diff --git a/src/pages/RoomDescriptionPage.tsx b/src/pages/RoomDescriptionPage.tsx
new file mode 100644
index 000000000000..bc46587814e3
--- /dev/null
+++ b/src/pages/RoomDescriptionPage.tsx
@@ -0,0 +1,113 @@
+import {useFocusEffect} from '@react-navigation/native';
+import ExpensiMark from 'expensify-common/lib/ExpensiMark';
+import React, {useCallback, useRef, useState} from 'react';
+import {View} from 'react-native';
+import type {OnyxCollection} from 'react-native-onyx';
+import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
+import FormProvider from '@components/Form/FormProvider';
+import InputWrapper from '@components/Form/InputWrapper';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import ScreenWrapper from '@components/ScreenWrapper';
+import Text from '@components/Text';
+import TextInput from '@components/TextInput';
+import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import Navigation from '@libs/Navigation/Navigation';
+import * as ReportUtils from '@libs/ReportUtils';
+import updateMultilineInputRange from '@libs/updateMultilineInputRange';
+import * as Report from '@userActions/Report';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type * as OnyxTypes from '@src/types/onyx';
+
+type RoomDescriptionPageProps = {
+ /** Policy for the current report */
+ policies: OnyxCollection;
+
+ /** The report currently being looked at */
+ report: OnyxTypes.Report;
+};
+
+function RoomDescriptionPage({report, policies}: RoomDescriptionPageProps) {
+ const styles = useThemeStyles();
+ const parser = new ExpensiMark();
+ const [description, setDescription] = useState(() => parser.htmlToMarkdown(report?.description ?? ''));
+ const reportDescriptionInputRef = useRef(null);
+ const focusTimeoutRef = useRef | null>(null);
+ const {translate} = useLocalize();
+ const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`];
+
+ const handleReportDescriptionChange = useCallback((value: string) => {
+ setDescription(value);
+ }, []);
+
+ const submitForm = useCallback(() => {
+ Report.updateDescription(report.reportID, report?.description ?? '', description.trim());
+ }, [report.reportID, report.description, description]);
+
+ useFocusEffect(
+ useCallback(() => {
+ focusTimeoutRef.current = setTimeout(() => {
+ reportDescriptionInputRef.current?.focus();
+ return () => {
+ if (!focusTimeoutRef.current) {
+ return;
+ }
+ clearTimeout(focusTimeoutRef.current);
+ };
+ }, CONST.ANIMATED_TRANSITION);
+ }, []),
+ );
+
+ return (
+
+
+ Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID))}
+ />
+
+ {translate('reportDescriptionPage.explainerText')}
+
+ {
+ if (!el) {
+ return;
+ }
+ reportDescriptionInputRef.current = el;
+ updateMultilineInputRange(el);
+ }}
+ value={description}
+ onChangeText={handleReportDescriptionChange}
+ autoCapitalize="none"
+ containerStyles={[styles.autoGrowHeightMultilineInput]}
+ />
+
+
+
+
+ );
+}
+
+RoomDescriptionPage.displayName = 'RoomDescriptionPage';
+
+export default RoomDescriptionPage;
diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js
index d0d2a23bb563..d8eef6f447ae 100755
--- a/src/pages/SearchPage.js
+++ b/src/pages/SearchPage.js
@@ -174,6 +174,7 @@ function SearchPage({betas, personalDetails, reports, isSearchingForReports, nav
testID={SearchPage.displayName}
onEntryTransitionEnd={updateOptions}
navigation={navigation}
+ shouldEnableMaxHeight
>
{({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => (
<>
diff --git a/src/pages/ShareCodePage.js b/src/pages/ShareCodePage.js
deleted file mode 100644
index 3f0ef6ca138e..000000000000
--- a/src/pages/ShareCodePage.js
+++ /dev/null
@@ -1,144 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import {ScrollView, View} from 'react-native';
-import _ from 'underscore';
-import expensifyLogo from '@assets/images/expensify-logo-round-transparent.png';
-import ContextMenuItem from '@components/ContextMenuItem';
-import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import * as Expensicons from '@components/Icon/Expensicons';
-import MenuItem from '@components/MenuItem';
-import QRShareWithDownload from '@components/QRShare/QRShareWithDownload';
-import ScreenWrapper from '@components/ScreenWrapper';
-import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails';
-import withEnvironment from '@components/withEnvironment';
-import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
-import withThemeStyles, {withThemeStylesPropTypes} from '@components/withThemeStyles';
-import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions';
-import Clipboard from '@libs/Clipboard';
-import compose from '@libs/compose';
-import getPlatform from '@libs/getPlatform';
-import Navigation from '@libs/Navigation/Navigation';
-import * as ReportUtils from '@libs/ReportUtils';
-import * as Url from '@libs/Url';
-import * as UserUtils from '@libs/UserUtils';
-import CONST from '@src/CONST';
-import ROUTES from '@src/ROUTES';
-import reportPropTypes from './reportPropTypes';
-
-const propTypes = {
- /** The report currently being looked at */
- report: reportPropTypes,
-
- /** The string value representing the URL of the current environment */
- environmentURL: PropTypes.string.isRequired,
-
- ...withLocalizePropTypes,
- ...withCurrentUserPersonalDetailsPropTypes,
- ...withThemeStylesPropTypes,
- ...windowDimensionsPropTypes,
-};
-
-const defaultProps = {
- report: undefined,
- ...withCurrentUserPersonalDetailsDefaultProps,
-};
-
-// eslint-disable-next-line react/prefer-stateless-function
-class ShareCodePage extends React.Component {
- qrCodeRef = React.createRef();
-
- /**
- * @param {Boolean} isReport
- * @return {String|string|*}
- */
- getSubtitle(isReport) {
- if (ReportUtils.isExpenseReport(this.props.report)) {
- return ReportUtils.getPolicyName(this.props.report);
- }
- if (ReportUtils.isMoneyRequestReport(this.props.report)) {
- // generate subtitle from participants
- return _.map(ReportUtils.getVisibleMemberIDs(this.props.report), (accountID) => ReportUtils.getDisplayNameForParticipant(accountID)).join(' & ');
- }
-
- if (isReport) {
- return ReportUtils.getParentNavigationSubtitle(this.props.report).workspaceName || ReportUtils.getChatRoomSubtitle(this.props.report);
- }
-
- return this.props.formatPhoneNumber(this.props.session.email);
- }
-
- render() {
- const isReport = this.props.report != null && this.props.report.reportID != null;
- const title = isReport ? ReportUtils.getReportName(this.props.report) : this.props.currentUserPersonalDetails.displayName;
- const subtitle = this.getSubtitle(isReport);
- const urlWithTrailingSlash = Url.addTrailingForwardSlash(this.props.environmentURL);
- const url = isReport
- ? `${urlWithTrailingSlash}${ROUTES.REPORT_WITH_ID.getRoute(this.props.report.reportID)}`
- : `${urlWithTrailingSlash}${ROUTES.PROFILE.getRoute(this.props.session.accountID)}`;
-
- const platform = getPlatform();
- const isNative = platform === CONST.PLATFORM.IOS || platform === CONST.PLATFORM.ANDROID;
-
- return (
-
- Navigation.goBack(isReport ? ROUTES.REPORT_WITH_ID_DETAILS.getRoute(this.props.report.reportID) : ROUTES.SETTINGS)}
- shouldShowBackButton={isReport || this.props.isSmallScreenWidth}
- />
-
-
-
-
-
-
-
- Clipboard.setString(url)}
- shouldLimitWidth={false}
- />
-
- {isNative && (
- this.qrCodeRef.current?.download()}
- />
- )}
-
- Navigation.navigate(ROUTES.REFERRAL_DETAILS_MODAL.getRoute(CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SHARE_CODE))}
- />
-
-
-
- );
- }
-}
-
-ShareCodePage.propTypes = propTypes;
-ShareCodePage.defaultProps = defaultProps;
-ShareCodePage.displayName = 'ShareCodePage';
-
-export default compose(withEnvironment, withLocalize, withCurrentUserPersonalDetails, withThemeStyles, withWindowDimensions)(ShareCodePage);
diff --git a/src/pages/ShareCodePage.tsx b/src/pages/ShareCodePage.tsx
new file mode 100644
index 000000000000..831f0eb8f1d8
--- /dev/null
+++ b/src/pages/ShareCodePage.tsx
@@ -0,0 +1,129 @@
+import React, {useRef} from 'react';
+import {ScrollView, View} from 'react-native';
+import type {ImageSourcePropType} from 'react-native';
+import type {OnyxEntry} from 'react-native-onyx';
+import expensifyLogo from '@assets/images/expensify-logo-round-transparent.png';
+import ContextMenuItem from '@components/ContextMenuItem';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import * as Expensicons from '@components/Icon/Expensicons';
+import MenuItem from '@components/MenuItem';
+import QRShareWithDownload from '@components/QRShare/QRShareWithDownload';
+import type QRShareWithDownloadHandle from '@components/QRShare/QRShareWithDownload/types';
+import ScreenWrapper from '@components/ScreenWrapper';
+import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
+import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails';
+import useEnvironment from '@hooks/useEnvironment';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import useWindowDimensions from '@hooks/useWindowDimensions';
+import Clipboard from '@libs/Clipboard';
+import getPlatform from '@libs/getPlatform';
+import Navigation from '@libs/Navigation/Navigation';
+import * as ReportUtils from '@libs/ReportUtils';
+import * as Url from '@libs/Url';
+import * as UserUtils from '@libs/UserUtils';
+import CONST from '@src/CONST';
+import ROUTES from '@src/ROUTES';
+import type {Report, Session} from '@src/types/onyx';
+
+type ShareCodePageOnyxProps = WithCurrentUserPersonalDetailsProps & {
+ /** Session info for the currently logged in user. */
+ session: OnyxEntry;
+
+ /** The report currently being looked at */
+ report?: OnyxEntry;
+};
+
+type ShareCodePageProps = ShareCodePageOnyxProps;
+
+function ShareCodePage({report, session, currentUserPersonalDetails}: ShareCodePageProps) {
+ const themeStyles = useThemeStyles();
+ const {translate} = useLocalize();
+ const {environmentURL} = useEnvironment();
+ const qrCodeRef = useRef(null);
+ const {isSmallScreenWidth} = useWindowDimensions();
+
+ const isReport = !!report?.reportID;
+
+ const getSubtitle = () => {
+ if (isReport) {
+ if (ReportUtils.isExpenseReport(report)) {
+ return ReportUtils.getPolicyName(report);
+ }
+ if (ReportUtils.isMoneyRequestReport(report)) {
+ // generate subtitle from participants
+ return ReportUtils.getVisibleMemberIDs(report)
+ .map((accountID) => ReportUtils.getDisplayNameForParticipant(accountID))
+ .join(' & ');
+ }
+
+ return ReportUtils.getParentNavigationSubtitle(report).workspaceName ?? ReportUtils.getChatRoomSubtitle(report);
+ }
+
+ return session?.email;
+ };
+
+ const title = isReport ? ReportUtils.getReportName(report) : currentUserPersonalDetails.displayName ?? '';
+ const subtitle = getSubtitle();
+ const urlWithTrailingSlash = Url.addTrailingForwardSlash(environmentURL);
+ const url = isReport ? `${urlWithTrailingSlash}${ROUTES.REPORT_WITH_ID.getRoute(report.reportID)}` : `${urlWithTrailingSlash}${ROUTES.PROFILE.getRoute(session?.accountID ?? '')}`;
+ const platform = getPlatform();
+ const isNative = platform === CONST.PLATFORM.IOS || platform === CONST.PLATFORM.ANDROID;
+
+ return (
+
+ Navigation.goBack(isReport ? ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID) : ROUTES.SETTINGS)}
+ shouldShowBackButton={isReport || isSmallScreenWidth}
+ />
+
+
+
+
+
+
+ Clipboard.setString(url)}
+ shouldLimitWidth={false}
+ />
+
+ {isNative && (
+
+ )}
+
+ Navigation.navigate(ROUTES.REFERRAL_DETAILS_MODAL.getRoute(CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SHARE_CODE))}
+ />
+
+
+
+ );
+}
+
+ShareCodePage.displayName = 'ShareCodePage';
+
+export default withCurrentUserPersonalDetails(ShareCodePage);
diff --git a/src/pages/TeachersUnite/IntroSchoolPrincipalPage.tsx b/src/pages/TeachersUnite/IntroSchoolPrincipalPage.tsx
index 997d9baa4f59..d5900330e4ba 100644
--- a/src/pages/TeachersUnite/IntroSchoolPrincipalPage.tsx
+++ b/src/pages/TeachersUnite/IntroSchoolPrincipalPage.tsx
@@ -22,7 +22,6 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {LoginList} from '@src/types/onyx';
-import type {Errors} from '@src/types/onyx/OnyxCommon';
type IntroSchoolPrincipalPageOnyxProps = {
loginList: OnyxEntry;
@@ -48,7 +47,7 @@ function IntroSchoolPrincipalPage(props: IntroSchoolPrincipalPageProps) {
*/
const validate = useCallback(
(values: OnyxFormValuesFields) => {
- const errors: Errors = {};
+ const errors: Partial, string>> = {};
if (!values.firstName || !ValidationUtils.isValidPersonName(values.firstName)) {
ErrorUtils.addErrorMessage(errors, 'firstName', 'bankAccount.error.firstName');
diff --git a/src/pages/TeachersUnite/SaveTheWorldPage.tsx b/src/pages/TeachersUnite/SaveTheWorldPage.tsx
index 03df52050ce5..5a6ce4e8f3e9 100644
--- a/src/pages/TeachersUnite/SaveTheWorldPage.tsx
+++ b/src/pages/TeachersUnite/SaveTheWorldPage.tsx
@@ -23,6 +23,7 @@ function SaveTheWorldPage() {
backgroundColor={theme.PAGE_THEMES[SCREENS.SAVE_THE_WORLD.ROOT].backgroundColor}
onBackButtonPress={Navigation.goBack}
illustration={LottieAnimations.SaveTheWorld}
+ testID={SaveTheWorldPage.displayName}
>
{translate('teachersUnitePage.teachersUnite')}
diff --git a/src/pages/ValidateLoginPage/index.tsx b/src/pages/ValidateLoginPage/index.tsx
index 7e79216a129d..5415c359aab0 100644
--- a/src/pages/ValidateLoginPage/index.tsx
+++ b/src/pages/ValidateLoginPage/index.tsx
@@ -3,6 +3,7 @@ import {withOnyx} from 'react-native-onyx';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import Navigation from '@libs/Navigation/Navigation';
import * as Session from '@userActions/Session';
+import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {ValidateLoginPageOnyxNativeProps, ValidateLoginPageProps} from './types';
@@ -13,16 +14,29 @@ function ValidateLoginPage({
session,
}: ValidateLoginPageProps) {
useEffect(() => {
- if (session?.authToken) {
- // If already signed in, do not show the validate code if not on web,
- // because we don't want to block the user with the interstitial page.
- Navigation.goBack();
- } else {
- Session.signInWithValidateCodeAndNavigate(Number(accountID), validateCode);
- }
+ // Wait till navigation becomes available
+ Navigation.isNavigationReady().then(() => {
+ if (session?.authToken) {
+ // If already signed in, do not show the validate code if not on web,
+ // because we don't want to block the user with the interstitial page.
+ Navigation.goBack();
+ } else {
+ Session.signInWithValidateCodeAndNavigate(Number(accountID), validateCode);
+ }
+ });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
+ useEffect(() => {
+ if (session?.autoAuthState !== CONST.AUTO_AUTH_STATE.FAILED) {
+ return;
+ }
+ // Go back to initial route if validation fails
+ Navigation.isNavigationReady().then(() => {
+ Navigation.goBack();
+ });
+ }, [session?.autoAuthState]);
+
return ;
}
diff --git a/src/pages/ValidateLoginPage/index.website.tsx b/src/pages/ValidateLoginPage/index.website.tsx
index 08f0a64d1a0d..6253535e9789 100644
--- a/src/pages/ValidateLoginPage/index.website.tsx
+++ b/src/pages/ValidateLoginPage/index.website.tsx
@@ -27,7 +27,9 @@ function ValidateLoginPage({
useEffect(() => {
if (!login && isSignedIn && (autoAuthState === CONST.AUTO_AUTH_STATE.SIGNING_IN || autoAuthState === CONST.AUTO_AUTH_STATE.JUST_SIGNED_IN)) {
// The user clicked the option to sign in the current tab
- Navigation.navigate();
+ Navigation.isNavigationReady().then(() => {
+ Navigation.goBack();
+ });
return;
}
Session.initAutoAuthState(autoAuthState);
@@ -47,7 +49,9 @@ function ValidateLoginPage({
}
// The user clicked the option to sign in the current tab
- Navigation.navigate();
+ Navigation.isNavigationReady().then(() => {
+ Navigation.goBack();
+ });
}, [login, cachedAccountID, is2FARequired]);
return (
diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js
index e3185eea2731..7b299a18a030 100644
--- a/src/pages/home/HeaderView.js
+++ b/src/pages/home/HeaderView.js
@@ -27,6 +27,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import {getGroupChatName} from '@libs/GroupChatUtils';
import * as HeaderUtils from '@libs/HeaderUtils';
+import Navigation from '@libs/Navigation/Navigation';
import reportWithoutHasDraftSelector from '@libs/OnyxSelectors/reportWithoutHasDraftSelector';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
@@ -38,6 +39,7 @@ import * as Session from '@userActions/Session';
import * as Task from '@userActions/Task';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
const propTypes = {
/** Toggles the navigationMenu open and closed */
@@ -112,6 +114,8 @@ function HeaderView(props) {
const isPolicyMember = useMemo(() => !_.isEmpty(props.policy), [props.policy]);
const canLeaveRoom = ReportUtils.canLeaveRoom(props.report, isPolicyMember);
const isArchivedRoom = ReportUtils.isArchivedRoom(props.report);
+ const reportDescription = ReportUtils.getReportDescriptionText(props.report);
+ const policyName = ReportUtils.getPolicyName(props.report);
// We hide the button when we are chatting with an automated Expensify account since it's not possible to contact
// these users via alternative means. It is possible to request a call with Concierge so we leave the option for them.
@@ -176,6 +180,18 @@ function HeaderView(props) {
/>
);
+ const renderAdditionalText = () => {
+ if (_.isEmpty(policyName) || _.isEmpty(reportDescription) || !_.isEmpty(parentNavigationSubtitleData)) {
+ return null;
+ }
+ return (
+ <>
+ {translate('threads.in')}
+ {policyName}
+ >
+ );
+ };
+
threeDotMenuItems.push(HeaderUtils.getPinMenuItem(props.report));
if (isConcierge && props.guideCalendarLink) {
@@ -274,6 +290,7 @@ function HeaderView(props) {
numberOfLines={1}
textStyles={[styles.headerText, styles.pre]}
shouldUseFullTitle={isChatRoom || isPolicyExpenseChat || isChatThread || isTaskReport}
+ renderAdditionalText={renderAdditionalText}
/>
{!_.isEmpty(parentNavigationSubtitleData) && (
)}
- {!_.isEmpty(subtitle) && (
+ {!_.isEmpty(subtitle) && _.isEmpty(reportDescription) && (
)}
+ {!_.isEmpty(reportDescription) && _.isEmpty(parentNavigationSubtitleData) && (
+ {
+ if (ReportUtils.canEditReportDescription(props.report, props.policy)) {
+ Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(props.reportID));
+ return;
+ }
+ Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(props.reportID));
+ }}
+ style={[styles.alignSelfStart, styles.mw100]}
+ accessibilityLabel={translate('reportDescriptionPage.roomDescription')}
+ >
+
+ {reportDescription}
+
+
+ )}
{brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR && (
diff --git a/src/pages/home/report/AnimatedEmptyStateBackground.tsx b/src/pages/home/report/AnimatedEmptyStateBackground.tsx
index 7e259b7473cf..c2ae6ddeb54c 100644
--- a/src/pages/home/report/AnimatedEmptyStateBackground.tsx
+++ b/src/pages/home/report/AnimatedEmptyStateBackground.tsx
@@ -1,9 +1,8 @@
import React from 'react';
-import Animated, {SensorType, useAnimatedSensor, useAnimatedStyle, useSharedValue, withSpring} from 'react-native-reanimated';
+import Animated, {clamp, SensorType, useAnimatedSensor, useAnimatedStyle, useReducedMotion, useSharedValue, withSpring} from 'react-native-reanimated';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeIllustrations from '@hooks/useThemeIllustrations';
import useWindowDimensions from '@hooks/useWindowDimensions';
-import * as NumberUtils from '@libs/NumberUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
@@ -22,10 +21,11 @@ function AnimatedEmptyStateBackground() {
const animatedSensor = useAnimatedSensor(SensorType.GYROSCOPE);
const xOffset = useSharedValue(0);
const yOffset = useSharedValue(0);
+ const isReducedMotionEnabled = useReducedMotion();
// Apply data to create style object
const animatedStyles = useAnimatedStyle(() => {
- if (!isSmallScreenWidth) {
+ if (!isSmallScreenWidth || isReducedMotionEnabled) {
return {};
}
/*
@@ -34,12 +34,12 @@ function AnimatedEmptyStateBackground() {
*/
const {x, y} = animatedSensor.sensor.value;
// The x vs y here seems wrong but is the way to make it feel right to the user
- xOffset.value = NumberUtils.clampWorklet(xOffset.value + y, -IMAGE_OFFSET_X, IMAGE_OFFSET_X);
- yOffset.value = NumberUtils.clampWorklet(yOffset.value - x, -IMAGE_OFFSET_Y, IMAGE_OFFSET_Y);
+ xOffset.value = clamp(xOffset.value + y * CONST.ANIMATION_GYROSCOPE_VALUE, -IMAGE_OFFSET_X, IMAGE_OFFSET_X);
+ yOffset.value = clamp(yOffset.value - x * CONST.ANIMATION_GYROSCOPE_VALUE, -IMAGE_OFFSET_Y, IMAGE_OFFSET_Y);
return {
transform: [{translateX: withSpring(-IMAGE_OFFSET_X - xOffset.value)}, {translateY: withSpring(yOffset.value)}],
};
- }, []);
+ }, [isReducedMotionEnabled]);
return (
{},
},
- {
- isAnonymousAction: false,
- textTranslateKey: 'reportActionContextMenu.leaveThread',
- icon: Expensicons.BellSlash,
- shouldShow: (type, reportAction, isArchivedRoom, betas, menuTarget, isChronosReport, reportID) => {
- const childReportNotificationPreference = ReportUtils.getChildReportNotificationPreference(reportAction);
- const isDeletedAction = ReportActionsUtils.isDeletedAction(reportAction);
- const shouldDisplayThreadReplies = ReportUtils.shouldDisplayThreadReplies(reportAction, reportID);
- const subscribed = childReportNotificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
- if (type !== CONST.CONTEXT_MENU_TYPES.REPORT_ACTION) {
- return false;
- }
- const isCommentAction = reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && !ReportUtils.isThreadFirstChat(reportAction, reportID);
- const isReportPreviewAction = reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW;
- const isIOUAction = reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && !ReportActionsUtils.isSplitBillAction(reportAction);
- return subscribed && (isCommentAction || isReportPreviewAction || isIOUAction) && (!isDeletedAction || shouldDisplayThreadReplies);
- },
- onPress: (closePopover, {reportAction, reportID}) => {
- const childReportNotificationPreference = ReportUtils.getChildReportNotificationPreference(reportAction);
- if (closePopover) {
- hideContextMenu(false, () => {
- ReportActionComposeFocusManager.focus();
- Report.toggleSubscribeToChildReport(reportAction?.childReportID ?? '0', reportAction, reportID, childReportNotificationPreference);
- });
- return;
- }
-
- ReportActionComposeFocusManager.focus();
- Report.toggleSubscribeToChildReport(reportAction?.childReportID ?? '0', reportAction, reportID, childReportNotificationPreference);
- },
- getDescription: () => {},
- },
{
isAnonymousAction: true,
textTranslateKey: 'reportActionContextMenu.copyURLToClipboard',
diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx
index 272548214ece..9c8c6a8b37e7 100644
--- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx
+++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx
@@ -250,7 +250,7 @@ function PopoverReportActionContextMenu(_props: never, ref: ForwardedRef (onComfirmDeleteModal.current = runAndResetCallback(onComfirmDeleteModal.current));
const reportAction = reportActionRef.current;
if (ReportActionsUtils.isMoneyRequestAction(reportAction) && reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) {
- IOU.deleteMoneyRequest(reportAction?.originalMessage?.IOUTransactionID, reportAction);
+ IOU.deleteMoneyRequest(reportAction?.originalMessage?.IOUTransactionID ?? '', reportAction);
} else if (reportAction) {
Report.deleteReportComment(reportIDRef.current, reportAction);
}
diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js
index 18f065018f1c..f960b987427b 100644
--- a/src/pages/home/report/ReportActionItem.js
+++ b/src/pages/home/report/ReportActionItem.js
@@ -627,7 +627,7 @@ function ReportActionItem(props) {
if (ReportUtils.isTaskReport(props.report)) {
if (ReportUtils.isCanceledTaskReport(props.report, parentReportAction)) {
return (
- <>
+
- >
+
);
}
return (
- <>
+
- >
+
);
}
if (ReportUtils.isExpenseReport(props.report) || ReportUtils.isIOUReport(props.report)) {
diff --git a/src/pages/home/report/ReportActionItemCreated.tsx b/src/pages/home/report/ReportActionItemCreated.tsx
index f8345ca7d2d0..afe97b2b95c1 100644
--- a/src/pages/home/report/ReportActionItemCreated.tsx
+++ b/src/pages/home/report/ReportActionItemCreated.tsx
@@ -116,6 +116,7 @@ export default withOnyx ;
-}
-
-ReportDetailsShareCodePage.propTypes = propTypes;
-ReportDetailsShareCodePage.defaultProps = defaultProps;
-
-export default withReportOrNotFound()(ReportDetailsShareCodePage);
diff --git a/src/pages/home/report/ReportDetailsShareCodePage.tsx b/src/pages/home/report/ReportDetailsShareCodePage.tsx
new file mode 100644
index 000000000000..28b1d5cd71d7
--- /dev/null
+++ b/src/pages/home/report/ReportDetailsShareCodePage.tsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import ShareCodePage from '@pages/ShareCodePage';
+import type {WithReportOrNotFoundProps} from './withReportOrNotFound';
+import withReportOrNotFound from './withReportOrNotFound';
+
+type ReportDetailsShareCodePageProps = WithReportOrNotFoundProps;
+
+function ReportDetailsShareCodePage({report}: ReportDetailsShareCodePageProps) {
+ return ;
+}
+
+export default withReportOrNotFound()(ReportDetailsShareCodePage);
diff --git a/src/pages/home/report/withReportOrNotFound.tsx b/src/pages/home/report/withReportOrNotFound.tsx
index 7214e6b6f3ea..94000f8a6bd8 100644
--- a/src/pages/home/report/withReportOrNotFound.tsx
+++ b/src/pages/home/report/withReportOrNotFound.tsx
@@ -16,16 +16,22 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject';
type WithReportOrNotFoundOnyxProps = {
/** The report currently being looked at */
report: OnyxEntry;
+
/** The policies which the user has access to */
policies: OnyxCollection;
+
/** Beta features list */
betas: OnyxEntry;
+
/** Indicated whether the report data is loading */
isLoadingReportData: OnyxEntry;
};
type WithReportOrNotFoundProps = WithReportOrNotFoundOnyxProps & {
route: RouteProp<{params: {reportID: string}}>;
+
+ /** The report currently being looked at */
+ report: OnyxTypes.Report;
};
export default function (
diff --git a/src/pages/home/sidebar/PressableAvatarWithIndicator.js b/src/pages/home/sidebar/PressableAvatarWithIndicator.js
index d72aef2ef824..63c5936e957b 100644
--- a/src/pages/home/sidebar/PressableAvatarWithIndicator.js
+++ b/src/pages/home/sidebar/PressableAvatarWithIndicator.js
@@ -58,7 +58,7 @@ function PressableAvatarWithIndicator({isCreateMenuOpen, currentUserPersonalDeta
diff --git a/src/pages/iou/MoneyRequestTagPage.js b/src/pages/iou/MoneyRequestTagPage.js
deleted file mode 100644
index 60e40d665580..000000000000
--- a/src/pages/iou/MoneyRequestTagPage.js
+++ /dev/null
@@ -1,127 +0,0 @@
-import lodashGet from 'lodash/get';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
-import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import ScreenWrapper from '@components/ScreenWrapper';
-import TagPicker from '@components/TagPicker';
-import tagPropTypes from '@components/tagPropTypes';
-import Text from '@components/Text';
-import useLocalize from '@hooks/useLocalize';
-import useThemeStyles from '@hooks/useThemeStyles';
-import compose from '@libs/compose';
-import Navigation from '@libs/Navigation/Navigation';
-import * as PolicyUtils from '@libs/PolicyUtils';
-import reportPropTypes from '@pages/reportPropTypes';
-import * as IOU from '@userActions/IOU';
-import ONYXKEYS from '@src/ONYXKEYS';
-import ROUTES from '@src/ROUTES';
-import {iouDefaultProps, iouPropTypes} from './propTypes';
-
-const propTypes = {
- /** Navigation route context info provided by react navigation */
- route: PropTypes.shape({
- /** Route specific parameters used on this screen via route :iouType/new/tag/:reportID? */
- params: PropTypes.shape({
- /** The type of IOU report, i.e. bill, request, send */
- iouType: PropTypes.string,
-
- /** The report ID of the IOU */
- reportID: PropTypes.string,
- }),
- }).isRequired,
-
- /* Onyx props */
- /** The report currently being used */
- report: reportPropTypes,
-
- /** Collection of tags attached to a policy */
- policyTags: tagPropTypes,
-
- /** Holds data related to Money Request view state, rather than the underlying Money Request data. */
- iou: iouPropTypes,
-};
-
-const defaultProps = {
- report: {},
- policyTags: {},
- iou: iouDefaultProps,
-};
-
-function MoneyRequestTagPage({route, report, policyTags, iou}) {
- const styles = useThemeStyles();
- const {translate} = useLocalize();
-
- const iouType = lodashGet(route, 'params.iouType', '');
-
- // Fetches the first tag list of the policy
- const tagListKey = _.first(_.keys(policyTags));
- const policyTagListName = PolicyUtils.getTagListName(policyTags) || translate('common.tag');
-
- const navigateBack = () => {
- Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, report.reportID));
- };
-
- const updateTag = (selectedTag) => {
- if (selectedTag.searchText === iou.tag) {
- IOU.resetMoneyRequestTag();
- } else {
- IOU.setMoneyRequestTag(selectedTag.searchText);
- }
- navigateBack();
- };
-
- return (
-
- {({insets}) => (
- <>
-
- {translate('iou.tagSelection', {tagName: policyTagListName})}
-
- >
- )}
-
- );
-}
-
-MoneyRequestTagPage.displayName = 'MoneyRequestTagPage';
-MoneyRequestTagPage.propTypes = propTypes;
-MoneyRequestTagPage.defaultProps = defaultProps;
-
-export default compose(
- withOnyx({
- iou: {
- key: ONYXKEYS.IOU,
- },
- }),
- // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file
- withOnyx({
- report: {
- key: ({route, iou}) => {
- const reportID = IOU.getIOUReportID(iou, route);
-
- return `${ONYXKEYS.COLLECTION.REPORT}${reportID}`;
- },
- },
- }),
- // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file
- withOnyx({
- policyTags: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`,
- },
- }),
-)(MoneyRequestTagPage);
diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js
index 99335b062f52..fe1f425a6d62 100644
--- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js
+++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js
@@ -243,7 +243,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({
const headerMessage = useMemo(
() =>
OptionsListUtils.getHeaderMessage(
- _.get(newChatOptions, 'personalDetails.length', 0) + _.get(newChatOptions, 'recentReports.length', 0) !== 0,
+ _.get(newChatOptions, 'personalDetails', []).length + _.get(newChatOptions, 'recentReports', []).length !== 0,
Boolean(newChatOptions.userToInvite),
searchTerm.trim(),
maxParticipantsReached,
diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.js
index 84e0ac8533c5..f91e7cea533b 100644
--- a/src/pages/iou/request/step/IOURequestStepAmount.js
+++ b/src/pages/iou/request/step/IOURequestStepAmount.js
@@ -1,6 +1,6 @@
import {useFocusEffect} from '@react-navigation/native';
import PropTypes from 'prop-types';
-import React, {useCallback, useRef} from 'react';
+import React, {useCallback, useEffect, useRef} from 'react';
import {withOnyx} from 'react-native-onyx';
import taxPropTypes from '@components/taxPropTypes';
import transactionPropTypes from '@components/transactionPropTypes';
@@ -59,18 +59,19 @@ const getTaxAmount = (transaction, defaultTaxValue, amount) => {
function IOURequestStepAmount({
report,
route: {
- params: {iouType, reportID, transactionID, backTo, currency: selectedCurrency},
+ params: {iouType, reportID, transactionID, backTo},
},
transaction,
- transaction: {currency: originalCurrency},
+ transaction: {currency},
policyTaxRates,
policy,
}) {
const {translate} = useLocalize();
const textInput = useRef(null);
const focusTimeoutRef = useRef(null);
+ const isSaveButtonPressed = useRef(false);
+ const originalCurrency = useRef(null);
const iouRequestType = getRequestType(transaction);
- const currency = selectedCurrency || originalCurrency;
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report));
const isTaxTrackingEnabled = isPolicyExpenseChat && policy.isTaxTrackingEnabled;
@@ -87,6 +88,22 @@ function IOURequestStepAmount({
}, []),
);
+ useEffect(() => {
+ if (transaction.originalCurrency) {
+ originalCurrency.current = transaction.originalCurrency;
+ } else {
+ originalCurrency.current = currency;
+ IOU.setMoneyRequestOriginalCurrency_temporaryForRefactor(transactionID, currency);
+ }
+ return () => {
+ if (isSaveButtonPressed.current) {
+ return;
+ }
+ IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, originalCurrency.current, true);
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
const navigateBack = () => {
Navigation.goBack(backTo || ROUTES.HOME);
};
@@ -99,6 +116,7 @@ function IOURequestStepAmount({
* @param {Number} amount
*/
const navigateToNextPage = ({amount}) => {
+ isSaveButtonPressed.current = true;
const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(amount));
if ((iouRequestType === CONST.IOU.REQUEST_TYPE.MANUAL || backTo) && isTaxTrackingEnabled) {
@@ -107,7 +125,7 @@ function IOURequestStepAmount({
IOU.setMoneyRequestTaxAmount(transaction.transactionID, taxAmountInSmallestCurrencyUnits);
}
- IOU.setMoneyRequestAmount_temporaryForRefactor(transactionID, amountInSmallestCurrencyUnits, currency || CONST.CURRENCY.USD);
+ IOU.setMoneyRequestAmount_temporaryForRefactor(transactionID, amountInSmallestCurrencyUnits, currency || CONST.CURRENCY.USD, true);
if (backTo) {
Navigation.goBack(backTo);
diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js
index 0f1c2b27ad2e..2efba59e0acc 100644
--- a/src/pages/iou/request/step/IOURequestStepConfirmation.js
+++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js
@@ -5,6 +5,7 @@ import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import categoryPropTypes from '@components/categoryPropTypes';
+import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Expensicons from '@components/Icon/Expensicons';
import MoneyRequestConfirmationList from '@components/MoneyTemporaryForRefactorRequestConfirmationList';
@@ -103,6 +104,15 @@ function IOURequestStepConfirmation({
);
const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]);
+ useEffect(() => {
+ if (!transaction || !transaction.originalCurrency) {
+ return;
+ }
+ // If user somehow lands on this page without the currency reset, then reset it here.
+ IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, transaction.originalCurrency, true);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
useEffect(() => {
const policyExpenseChat = _.find(participants, (participant) => participant.isPolicyExpenseChat);
if (policyExpenseChat) {
@@ -321,6 +331,10 @@ function IOURequestStepConfirmation({
IOU.setMoneyRequestBillable_temporaryForRefactor(transactionID, billable);
};
+ // This loading indicator is shown because the transaction originalCurrency is being updated later than the component mounts.
+ // To prevent the component from rendering with the wrong currency, we show a loading indicator until the correct currency is set.
+ const isLoading = !!(transaction && transaction.originalCurrency);
+
return (
-
+ {isLoading ? (
+
+ ) : (
+
+ )}
)}
diff --git a/src/pages/iou/request/step/IOURequestStepCurrency.js b/src/pages/iou/request/step/IOURequestStepCurrency.js
index b4281de4d16e..06af0ecf3ca4 100644
--- a/src/pages/iou/request/step/IOURequestStepCurrency.js
+++ b/src/pages/iou/request/step/IOURequestStepCurrency.js
@@ -59,18 +59,14 @@ function IOURequestStepCurrency({
const [searchValue, setSearchValue] = useState('');
const optionsSelectorRef = useRef();
- const navigateBack = (selectedCurrency = undefined) => {
+ const navigateBack = () => {
// If the currency selection was done from the confirmation step (eg. + > request money > manual > confirm > amount > currency)
// then the user needs taken back to the confirmation page instead of the initial amount page. This is because the route params
// are only able to handle one backTo param at a time and the user needs to go back to the amount page before going back
// to the confirmation page
if (pageIndex === 'confirm') {
const routeToAmountPageWithConfirmationAsBackTo = getUrlWithBackToParam(backTo, `/${ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(iouType, transactionID, reportID)}`);
- if (selectedCurrency) {
- Navigation.navigate(`${routeToAmountPageWithConfirmationAsBackTo}¤cy=${selectedCurrency}`);
- } else {
- Navigation.goBack(routeToAmountPageWithConfirmationAsBackTo);
- }
+ Navigation.goBack(routeToAmountPageWithConfirmationAsBackTo);
return;
}
Navigation.goBack(backTo || ROUTES.HOME);
@@ -82,10 +78,8 @@ function IOURequestStepCurrency({
*/
const confirmCurrencySelection = (option) => {
Keyboard.dismiss();
- if (pageIndex !== 'confirm') {
- IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, option.currencyCode);
- }
- navigateBack(option.currencyCode);
+ IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, option.currencyCode);
+ navigateBack();
};
const {sections, headerMessage, initiallyFocusedOptionKey} = useMemo(() => {
diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js
index 0d1177e231c4..5f1b22cab128 100644
--- a/src/pages/iou/request/step/IOURequestStepParticipants.js
+++ b/src/pages/iou/request/step/IOURequestStepParticipants.js
@@ -70,7 +70,7 @@ function IOURequestStepParticipants({
const goToNextStep = useCallback(() => {
const nextStepIOUType = numberOfParticipants.current === 1 ? iouType : CONST.IOU.TYPE.SPLIT;
- IOU.resetMoneyRequestTag_temporaryForRefactor(transactionID);
+ IOU.setMoneyRequestTag(transactionID, '');
IOU.resetMoneyRequestCategory_temporaryForRefactor(transactionID);
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(nextStepIOUType, transactionID, selectedReportID.current || reportID));
}, [iouType, transactionID, reportID]);
diff --git a/src/pages/iou/request/step/IOURequestStepTag.js b/src/pages/iou/request/step/IOURequestStepTag.js
index 7e2ccbe1a9dd..1297b98a0814 100644
--- a/src/pages/iou/request/step/IOURequestStepTag.js
+++ b/src/pages/iou/request/step/IOURequestStepTag.js
@@ -12,6 +12,7 @@ import Navigation from '@libs/Navigation/Navigation';
import * as PolicyUtils from '@libs/PolicyUtils';
import reportPropTypes from '@pages/reportPropTypes';
import * as IOU from '@userActions/IOU';
+import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes';
@@ -44,7 +45,7 @@ function IOURequestStepTag({
policyTags,
report,
route: {
- params: {transactionID, backTo},
+ params: {action, transactionID, backTo, iouType},
},
transaction: {tag},
}) {
@@ -54,6 +55,8 @@ function IOURequestStepTag({
// Fetches the first tag list of the policy
const tagListKey = _.first(_.keys(policyTags));
const policyTagListName = PolicyUtils.getTagListName(policyTags) || translate('common.tag');
+ const isEditing = action === CONST.IOU.ACTION.EDIT;
+ const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT;
const navigateBack = () => {
Navigation.goBack(backTo || ROUTES.HOME);
@@ -64,11 +67,19 @@ function IOURequestStepTag({
* @param {String} selectedTag.searchText
*/
const updateTag = (selectedTag) => {
- if (selectedTag.searchText === tag) {
- IOU.resetMoneyRequestTag_temporaryForRefactor(transactionID);
- } else {
- IOU.setMoneyRequestTag_temporaryForRefactor(transactionID, selectedTag.searchText);
+ const isSelectedTag = selectedTag.searchText === tag;
+ const updatedTag = !isSelectedTag ? selectedTag.searchText : '';
+ if (isSplitBill) {
+ IOU.setDraftSplitTransaction(transactionID, {tag: selectedTag.searchText});
+ navigateBack();
+ return;
}
+ if (isEditing) {
+ IOU.updateMoneyRequestTag(transactionID, report.reportID, updatedTag);
+ Navigation.dismissModal();
+ return;
+ }
+ IOU.setMoneyRequestTag(transactionID, updatedTag);
navigateBack();
};
@@ -79,13 +90,18 @@ function IOURequestStepTag({
shouldShowWrapper
testID={IOURequestStepTag.displayName}
>
- {translate('iou.tagSelection', {tagName: policyTagListName})}
-
+ {({insets}) => (
+ <>
+ {translate('iou.tagSelection', {tagName: policyTagListName})}
+
+ >
+ )}
);
}
diff --git a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js
index a9f3195bfdfd..d1352f5b1f5d 100644
--- a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js
+++ b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js
@@ -1,5 +1,5 @@
import {useFocusEffect} from '@react-navigation/native';
-import React, {useCallback, useRef} from 'react';
+import React, {useCallback, useEffect, useRef} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
@@ -53,10 +53,10 @@ const getTaxAmount = (transaction, defaultTaxValue) => {
function IOURequestStepTaxAmountPage({
route: {
- params: {iouType, reportID, transactionID, backTo, currency: selectedCurrency},
+ params: {iouType, reportID, transactionID, backTo},
},
transaction,
- transaction: {currency: originalCurrency},
+ transaction: {currency},
report,
policyTaxRates,
}) {
@@ -65,10 +65,27 @@ function IOURequestStepTaxAmountPage({
const textInput = useRef(null);
const isEditing = Navigation.getActiveRoute().includes('taxAmount');
- const currency = selectedCurrency || originalCurrency;
-
const focusTimeoutRef = useRef(null);
+ const isSaveButtonPressed = useRef(false);
+ const originalCurrency = useRef(null);
+
+ useEffect(() => {
+ if (transaction.originalCurrency) {
+ originalCurrency.current = transaction.originalCurrency;
+ } else {
+ originalCurrency.current = currency;
+ IOU.setMoneyRequestOriginalCurrency_temporaryForRefactor(transactionID, currency);
+ }
+ return () => {
+ if (isSaveButtonPressed.current) {
+ return;
+ }
+ IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, originalCurrency.current, true);
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
useFocusEffect(
useCallback(() => {
focusTimeoutRef.current = setTimeout(() => textInput.current && textInput.current.focus(), CONST.ANIMATED_TRANSITION);
@@ -93,10 +110,11 @@ function IOURequestStepTaxAmountPage({
};
const updateTaxAmount = (currentAmount) => {
+ isSaveButtonPressed.current = true;
const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(currentAmount.amount));
IOU.setMoneyRequestTaxAmount(transactionID, amountInSmallestCurrencyUnits);
- IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, currency || CONST.CURRENCY.USD);
+ IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, currency || CONST.CURRENCY.USD, true);
if (backTo) {
Navigation.goBack(backTo);
diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js
index 216154be9cd4..ea57d88579ae 100644
--- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js
+++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js
@@ -88,7 +88,6 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route, transaction}) {
const navigateToConfirmationStep = (moneyRequestType) => {
IOU.setMoneyRequestId(moneyRequestType);
IOU.resetMoneyRequestCategory();
- IOU.resetMoneyRequestTag();
Navigation.navigate(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(moneyRequestType, reportID));
};
diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
index 3cf39d98426f..944bad76c96b 100755
--- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
+++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
@@ -259,13 +259,13 @@ function MoneyRequestParticipantsSelector({
const headerMessage = useMemo(
() =>
OptionsListUtils.getHeaderMessage(
- newChatOptions.personalDetails.length + newChatOptions.recentReports.length !== 0,
+ _.get(newChatOptions, 'personalDetails', []).length + _.get(newChatOptions, 'recentReports', []).length !== 0,
Boolean(newChatOptions.userToInvite),
searchTerm.trim(),
maxParticipantsReached,
_.some(participants, (participant) => participant.searchText.toLowerCase().includes(searchTerm.trim().toLowerCase())),
),
- [maxParticipantsReached, newChatOptions.personalDetails.length, newChatOptions.recentReports.length, newChatOptions.userToInvite, participants, searchTerm],
+ [maxParticipantsReached, newChatOptions, participants, searchTerm],
);
// Right now you can't split a request with a workspace and other additional participants
diff --git a/src/pages/settings/AboutPage/AboutPage.tsx b/src/pages/settings/AboutPage/AboutPage.tsx
index f19c9eb2d534..93948cf0565e 100644
--- a/src/pages/settings/AboutPage/AboutPage.tsx
+++ b/src/pages/settings/AboutPage/AboutPage.tsx
@@ -132,6 +132,7 @@ function AboutPage() {
backgroundColor={theme.PAGE_THEMES[SCREENS.SETTINGS.ABOUT].backgroundColor}
overlayContent={overlayContent}
shouldShowOfflineIndicatorInWideScreen
+ testID={AboutPage.displayName}
>
{translate('footer.aboutExpensify')}
diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js
index dfa4262549fc..6f547564e7a9 100755
--- a/src/pages/settings/InitialSettingsPage.js
+++ b/src/pages/settings/InitialSettingsPage.js
@@ -171,7 +171,7 @@ function InitialSettingsPage(props) {
action: () => {
Link.openOldDotLink(CONST.OLDDOT_URLS.INBOX);
},
- link: Link.buildOldDotURL(CONST.OLDDOT_URLS.INBOX),
+ link: () => Link.buildOldDotURL(CONST.OLDDOT_URLS.INBOX),
},
{
translationKey: 'initialSettingsPage.signOut',
@@ -225,6 +225,15 @@ function InitialSettingsPage(props) {
* @returns {String|undefined} the user's wallet balance
*/
const getWalletBalance = (isPaymentItem) => (isPaymentItem ? CurrencyUtils.convertToDisplayString(props.userWallet.currentBalance) : undefined);
+
+ const openPopover = (link, event) => {
+ if (typeof link === 'function') {
+ link().then((url) => ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, event, url, popoverAnchor.current));
+ } else if (link) {
+ ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, event, link, popoverAnchor.current);
+ }
+ };
+
return (
{translate(menuItemsData.sectionTranslationKey)}
@@ -259,9 +268,7 @@ function InitialSettingsPage(props) {
ref={popoverAnchor}
hoverAndPressStyle={styles.hoveredComponentBG}
shouldBlockSelection={Boolean(item.link)}
- onSecondaryInteraction={
- !_.isEmpty(item.link) ? (e) => ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, e, item.link, popoverAnchor.current) : undefined
- }
+ onSecondaryInteraction={item.link ? (event) => openPopover(item.link, event) : undefined}
focused={activeRoute && item.routeName && activeRoute.toLowerCase().replaceAll('_', '') === item.routeName.toLowerCase().replaceAll('/', '')}
isPaneMenu
/>
@@ -313,6 +320,7 @@ function InitialSettingsPage(props) {
errors={lodashGet(props.currentUserPersonalDetails, 'errorFields.avatar', null)}
errorRowStyles={[styles.mt6]}
onErrorClose={PersonalDetails.clearAvatarErrors}
+ onViewPhotoPress={() => Navigation.navigate(ROUTES.PROFILE_AVATAR.getRoute(accountID))}
previewSource={UserUtils.getFullSizeAvatar(avatarURL, accountID)}
originalFileName={currentUserDetails.originalFileName}
headerTitle={props.translate('profilePage.profileAvatar')}
@@ -347,6 +355,7 @@ function InitialSettingsPage(props) {
onBackButtonPress={() => Navigation.closeFullScreen()}
backgroundColor={theme.PAGE_THEMES[SCREENS.SETTINGS.ROOT].backgroundColor}
childrenContainerStyles={[styles.m0, styles.p0]}
+ testID={InitialSettingsPage.displayName}
>
{accountMenuItems}
diff --git a/src/pages/settings/Preferences/PreferencesPage.js b/src/pages/settings/Preferences/PreferencesPage.js
index da0d87afe932..f95daf8ad7d2 100755
--- a/src/pages/settings/Preferences/PreferencesPage.js
+++ b/src/pages/settings/Preferences/PreferencesPage.js
@@ -55,6 +55,7 @@ function PreferencesPage(props) {
illustration={LottieAnimations.PreferencesDJ}
shouldShowBackButton={isSmallScreenWidth}
shouldShowOfflineIndicatorInWideScreen
+ testID={PreferencesPage.displayName}
>
Navigation.goBack(ROUTES)}
illustration={LottieAnimations.ExpensifyLounge}
+ testID={LoungeAccessPage.displayName}
>
pref !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN),
- (preference) => ({
- value: preference,
- text: props.translate(`notificationPreferencesPage.notificationPreferences.${preference}`),
- keyForList: preference,
- isSelected: preference === props.report.notificationPreference,
- }),
- );
-
- return (
-
-
- ReportUtils.goBackToDetailsPage(props.report)}
- />
-
- Report.updateNotificationPreference(props.report.reportID, props.report.notificationPreference, option.value, true, undefined, undefined, props.report)
- }
- initiallyFocusedOptionKey={_.find(notificationPreferenceOptions, (locale) => locale.isSelected).keyForList}
- />
-
-
- );
-}
-
-NotificationPreferencePage.displayName = 'NotificationPreferencePage';
-NotificationPreferencePage.propTypes = propTypes;
-
-export default compose(withLocalize, withReportOrNotFound())(NotificationPreferencePage);
diff --git a/src/pages/settings/Report/NotificationPreferencePage.tsx b/src/pages/settings/Report/NotificationPreferencePage.tsx
new file mode 100644
index 000000000000..05f3483f7ce8
--- /dev/null
+++ b/src/pages/settings/Report/NotificationPreferencePage.tsx
@@ -0,0 +1,54 @@
+import type {StackScreenProps} from '@react-navigation/stack';
+import React from 'react';
+import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import ScreenWrapper from '@components/ScreenWrapper';
+import SelectionList from '@components/SelectionList';
+import useLocalize from '@hooks/useLocalize';
+import * as ReportUtils from '@libs/ReportUtils';
+import type {ReportSettingsNavigatorParamList} from '@navigation/types';
+import withReportOrNotFound from '@pages/home/report/withReportOrNotFound';
+import type {WithReportOrNotFoundProps} from '@pages/home/report/withReportOrNotFound';
+import * as ReportActions from '@userActions/Report';
+import CONST from '@src/CONST';
+import type SCREENS from '@src/SCREENS';
+
+type NotificationPreferencePageProps = WithReportOrNotFoundProps & StackScreenProps;
+
+function NotificationPreferencePage({report}: NotificationPreferencePageProps) {
+ const {translate} = useLocalize();
+ const shouldDisableNotificationPreferences = ReportUtils.isArchivedRoom(report);
+ const notificationPreferenceOptions = Object.values(CONST.REPORT.NOTIFICATION_PREFERENCE)
+ .filter((pref) => pref !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN)
+ .map((preference) => ({
+ value: preference,
+ text: translate(`notificationPreferencesPage.notificationPreferences.${preference}`),
+ keyForList: preference,
+ isSelected: preference === report?.notificationPreference,
+ }));
+
+ return (
+
+
+ ReportUtils.goBackToDetailsPage(report)}
+ />
+
+ report && ReportActions.updateNotificationPreference(report.reportID, report.notificationPreference, option.value, true, undefined, undefined, report)
+ }
+ initiallyFocusedOptionKey={notificationPreferenceOptions.find((locale) => locale.isSelected)?.keyForList}
+ />
+
+
+ );
+}
+
+NotificationPreferencePage.displayName = 'NotificationPreferencePage';
+
+export default withReportOrNotFound()(NotificationPreferencePage);
diff --git a/src/pages/settings/Report/ReportSettingsPage.js b/src/pages/settings/Report/ReportSettingsPage.tsx
similarity index 68%
rename from src/pages/settings/Report/ReportSettingsPage.js
rename to src/pages/settings/Report/ReportSettingsPage.tsx
index c7cfd9c7850d..613dcd460e26 100644
--- a/src/pages/settings/Report/ReportSettingsPage.js
+++ b/src/pages/settings/Report/ReportSettingsPage.tsx
@@ -1,85 +1,52 @@
-import lodashGet from 'lodash/get';
-import PropTypes from 'prop-types';
+import type {StackScreenProps} from '@react-navigation/stack';
import React, {useMemo} from 'react';
import {ScrollView, View} from 'react-native';
-import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import DisplayNames from '@components/DisplayNames';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import * as Expensicons from '@components/Icon/Expensicons';
-import MenuItem from '@components/MenuItem';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import ScreenWrapper from '@components/ScreenWrapper';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
-import compose from '@libs/compose';
import {getGroupChatName} from '@libs/GroupChatUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
+import type {ReportSettingsNavigatorParamList} from '@navigation/types';
import withReportOrNotFound from '@pages/home/report/withReportOrNotFound';
-import reportPropTypes from '@pages/reportPropTypes';
-import * as Report from '@userActions/Report';
+import type {WithReportOrNotFoundProps} from '@pages/home/report/withReportOrNotFound';
+import * as ReportActions from '@userActions/Report';
import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
-const propTypes = {
- /** Route params */
- route: PropTypes.shape({
- params: PropTypes.shape({
- /** Report ID passed via route r/:reportID/settings */
- reportID: PropTypes.string,
- }),
- }).isRequired,
+type ReportSettingsPageProps = WithReportOrNotFoundProps & StackScreenProps;
- /* Onyx Props */
-
- /** The active report */
- report: reportPropTypes.isRequired,
-
- /** The policies which the user has access to and which the report could be tied to */
- policies: PropTypes.shape({
- /** The policy name */
- name: PropTypes.string,
-
- /** ID of the policy */
- id: PropTypes.string,
- }),
-};
-
-const defaultProps = {
- policies: {},
-};
-
-function ReportSettingsPage(props) {
+function ReportSettingsPage({report, policies}: ReportSettingsPageProps) {
+ const reportID = report?.reportID ?? '';
const styles = useThemeStyles();
- const {report, policies} = props;
const {translate} = useLocalize();
// The workspace the report is on, null if the user isn't a member of the workspace
- const linkedWorkspace = useMemo(() => _.find(policies, (policy) => policy && policy.id === report.policyID), [policies, report.policyID]);
+ const linkedWorkspace = useMemo(() => Object.values(policies ?? {}).find((policy) => policy && policy.id === report?.policyID) ?? null, [policies, report?.policyID]);
const shouldDisableRename = useMemo(() => ReportUtils.shouldDisableRename(report, linkedWorkspace), [report, linkedWorkspace]);
const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
- // We only want policy owners and admins to be able to modify the welcome message, but not in thread chat
- const shouldDisableWelcomeMessage = ReportUtils.shouldDisableWelcomeMessage(report, linkedWorkspace);
-
- const shouldDisableSettings = _.isEmpty(report) || ReportUtils.isArchivedRoom(report);
+ const shouldDisableSettings = isEmptyObject(report) || ReportUtils.isArchivedRoom(report);
const shouldShowRoomName = !ReportUtils.isPolicyExpenseChat(report) && !ReportUtils.isChatThread(report);
const notificationPreference =
- report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN
+ report?.notificationPreference && report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN
? translate(`notificationPreferencesPage.notificationPreferences.${report.notificationPreference}`)
: '';
- const writeCapability = ReportUtils.isAdminRoom(report) ? CONST.REPORT.WRITE_CAPABILITIES.ADMINS : report.writeCapability || CONST.REPORT.WRITE_CAPABILITIES.ALL;
+ const writeCapability = ReportUtils.isAdminRoom(report) ? CONST.REPORT.WRITE_CAPABILITIES.ADMINS : report?.writeCapability ?? CONST.REPORT.WRITE_CAPABILITIES.ALL;
const writeCapabilityText = translate(`writeCapabilityPage.writeCapability.${writeCapability}`);
const shouldAllowWriteCapabilityEditing = useMemo(() => ReportUtils.canEditWriteCapability(report, linkedWorkspace), [report, linkedWorkspace]);
- const shouldShowNotificationPref = !isMoneyRequestReport && report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
+ const shouldShowNotificationPref = !isMoneyRequestReport && report?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
const roomNameLabel = translate(isMoneyRequestReport ? 'workspace.editor.nameInputLabel' : 'newRoomPage.roomName');
- const reportName = ReportUtils.isGroupChat(props.report) ? getGroupChatName(props.report) : ReportUtils.getReportName(props.report);
+ const reportName = ReportUtils.isGroupChat(report) ? getGroupChatName(report) : ReportUtils.getReportName(report);
const shouldShowWriteCapability = !isMoneyRequestReport;
@@ -88,7 +55,7 @@ function ReportSettingsPage(props) {
Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID))}
+ onBackButtonPress={() => Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID))}
/>
{shouldShowNotificationPref && (
@@ -96,15 +63,15 @@ function ReportSettingsPage(props) {
shouldShowRightIcon
title={notificationPreference}
description={translate('notificationPreferencesPage.label')}
- onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_NOTIFICATION_PREFERENCES.getRoute(report.reportID))}
+ onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_NOTIFICATION_PREFERENCES.getRoute(reportID))}
/>
)}
{shouldShowRoomName && (
Report.clearPolicyRoomNameErrors(report.reportID)}
+ onClose={() => ReportActions.clearPolicyRoomNameErrors(reportID)}
>
{shouldDisableRename ? (
@@ -115,7 +82,7 @@ function ReportSettingsPage(props) {
{roomNameLabel}
Navigation.navigate(ROUTES.REPORT_SETTINGS_ROOM_NAME.getRoute(report.reportID))}
+ onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_ROOM_NAME.getRoute(reportID))}
/>
)}
@@ -138,7 +105,7 @@ function ReportSettingsPage(props) {
shouldShowRightIcon
title={writeCapabilityText}
description={translate('writeCapabilityPage.label')}
- onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_WRITE_CAPABILITY.getRoute(report.reportID))}
+ onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_WRITE_CAPABILITY.getRoute(reportID))}
/>
) : (
@@ -157,7 +124,7 @@ function ReportSettingsPage(props) {
))}
- {Boolean(linkedWorkspace) && (
+ {linkedWorkspace !== null && (
)}
- {Boolean(report.visibility) && (
+ {report?.visibility !== undefined && (
)}
- {!shouldDisableWelcomeMessage && (
- Navigation.navigate(ROUTES.REPORT_WELCOME_MESSAGE.getRoute(report.reportID))}
- shouldShowRightIcon
- />
- )}
);
}
-ReportSettingsPage.propTypes = propTypes;
-ReportSettingsPage.defaultProps = defaultProps;
ReportSettingsPage.displayName = 'ReportSettingsPage';
-export default compose(
- withReportOrNotFound(),
- withOnyx({
- policies: {
- key: ONYXKEYS.COLLECTION.POLICY,
- },
- }),
-)(ReportSettingsPage);
+
+export default withReportOrNotFound()(ReportSettingsPage);
diff --git a/src/pages/settings/Report/RoomNamePage.js b/src/pages/settings/Report/RoomNamePage.tsx
similarity index 71%
rename from src/pages/settings/Report/RoomNamePage.js
rename to src/pages/settings/Report/RoomNamePage.tsx
index 5f64faca50fc..30226bc6f502 100644
--- a/src/pages/settings/Report/RoomNamePage.js
+++ b/src/pages/settings/Report/RoomNamePage.tsx
@@ -1,59 +1,55 @@
import {useIsFocused} from '@react-navigation/native';
-import PropTypes from 'prop-types';
+import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback, useRef} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
+import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
+import type {OnyxFormValuesFields} from '@components/Form/types';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import type {AnimatedTextInputRef} from '@components/RNTextInput';
import RoomNameInput from '@components/RoomNameInput';
import ScreenWrapper from '@components/ScreenWrapper';
-import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
+import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
-import compose from '@libs/compose';
import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
import * as ValidationUtils from '@libs/ValidationUtils';
+import type {ReportSettingsNavigatorParamList} from '@navigation/types';
import withReportOrNotFound from '@pages/home/report/withReportOrNotFound';
-import reportPropTypes from '@pages/reportPropTypes';
-import * as Report from '@userActions/Report';
+import type {WithReportOrNotFoundProps} from '@pages/home/report/withReportOrNotFound';
+import * as ReportActions from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
+import type {Policy, Report} from '@src/types/onyx';
-const propTypes = {
- ...withLocalizePropTypes,
-
- /** The room report for which the name is being edited */
- report: reportPropTypes.isRequired,
-
+type RoomNamePageOnyxProps = {
/** All reports shared with the user */
- reports: PropTypes.objectOf(reportPropTypes),
+ reports: OnyxCollection;
/** Policy of the report for which the name is being edited */
- policy: PropTypes.shape({
- role: PropTypes.string,
- owner: PropTypes.string,
- }),
-};
-const defaultProps = {
- reports: {},
- policy: {},
+ policy: OnyxEntry;
};
-function RoomNamePage({policy, report, reports, translate}) {
+type RoomNamePageProps = RoomNamePageOnyxProps & WithReportOrNotFoundProps & StackScreenProps;
+
+function RoomNamePage({report, policy, reports}: RoomNamePageProps) {
const styles = useThemeStyles();
- const roomNameInputRef = useRef(null);
+ const roomNameInputRef = useRef(null);
const isFocused = useIsFocused();
+ const {translate} = useLocalize();
const validate = useCallback(
- (values) => {
+ (values: OnyxFormValuesFields) => {
const errors = {};
// We should skip validation hence we return an empty errors and we skip Form submission on the onSubmit method
- if (values.roomName === report.reportName) {
+ if (values.roomName === report?.reportName) {
return errors;
}
@@ -66,7 +62,7 @@ function RoomNamePage({policy, report, reports, translate}) {
} else if (ValidationUtils.isReservedRoomName(values.roomName)) {
// Certain names are reserved for default rooms and should not be used for policy rooms.
ErrorUtils.addErrorMessage(errors, 'roomName', ['newRoomPage.roomNameReservedError', {reservedName: values.roomName}]);
- } else if (ValidationUtils.isExistingRoomName(values.roomName, reports, report.policyID)) {
+ } else if (ValidationUtils.isExistingRoomName(values.roomName, reports, report?.policyID ?? '')) {
// The room name can't be set to one that already exists on the policy
ErrorUtils.addErrorMessage(errors, 'roomName', 'newRoomPage.roomAlreadyExistsError');
}
@@ -78,19 +74,19 @@ function RoomNamePage({policy, report, reports, translate}) {
return (
roomNameInputRef.current && roomNameInputRef.current.focus()}
+ onEntryTransitionEnd={() => roomNameInputRef.current?.focus()}
includeSafeAreaPaddingBottom={false}
testID={RoomNamePage.displayName}
>
Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report.reportID))}
+ onBackButtonPress={() => Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? ''))}
/>
Report.updatePolicyRoomNameAndNavigate(report, values.roomName)}
+ onSubmit={(values) => report && ReportActions.updatePolicyRoomNameAndNavigate(report, values.roomName)}
validate={validate}
submitButtonText={translate('common.save')}
enabledWhenOffline
@@ -100,7 +96,7 @@ function RoomNamePage({policy, report, reports, translate}) {
InputComponent={RoomNameInput}
ref={roomNameInputRef}
inputID="roomName"
- defaultValue={report.reportName}
+ defaultValue={report?.reportName}
isFocused={isFocused}
/>
@@ -110,19 +106,15 @@ function RoomNamePage({policy, report, reports, translate}) {
);
}
-RoomNamePage.propTypes = propTypes;
-RoomNamePage.defaultProps = defaultProps;
RoomNamePage.displayName = 'RoomNamePage';
-export default compose(
- withLocalize,
- withReportOrNotFound(),
- withOnyx({
+export default withReportOrNotFound()(
+ withOnyx({
reports: {
key: ONYXKEYS.COLLECTION.REPORT,
},
policy: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`,
+ key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`,
},
- }),
-)(RoomNamePage);
+ })(RoomNamePage),
+);
diff --git a/src/pages/settings/Report/WriteCapabilityPage.js b/src/pages/settings/Report/WriteCapabilityPage.js
deleted file mode 100644
index fc587b028f7d..000000000000
--- a/src/pages/settings/Report/WriteCapabilityPage.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import React from 'react';
-import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
-import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
-import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import ScreenWrapper from '@components/ScreenWrapper';
-import SelectionList from '@components/SelectionList';
-import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
-import compose from '@libs/compose';
-import Navigation from '@libs/Navigation/Navigation';
-import * as ReportUtils from '@libs/ReportUtils';
-import withReportOrNotFound from '@pages/home/report/withReportOrNotFound';
-import reportPropTypes from '@pages/reportPropTypes';
-import {policyDefaultProps, policyPropTypes} from '@pages/workspace/withPolicy';
-import * as Report from '@userActions/Report';
-import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
-import ROUTES from '@src/ROUTES';
-
-const propTypes = {
- ...withLocalizePropTypes,
- ...policyPropTypes,
-
- /** The report for which we are setting write capability */
- report: reportPropTypes.isRequired,
-};
-
-const defaultProps = {
- ...policyDefaultProps,
-};
-
-function WriteCapabilityPage(props) {
- const writeCapabilityOptions = _.map(CONST.REPORT.WRITE_CAPABILITIES, (value) => ({
- value,
- text: props.translate(`writeCapabilityPage.writeCapability.${value}`),
- keyForList: value,
- isSelected: value === (props.report.writeCapability || CONST.REPORT.WRITE_CAPABILITIES.ALL),
- }));
-
- const isAbleToEdit = ReportUtils.canEditWriteCapability(props.report, props.policy);
-
- return (
-
-
- Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(props.report.reportID))}
- />
- Report.updateWriteCapabilityAndNavigate(props.report, option.value)}
- initiallyFocusedOptionKey={_.find(writeCapabilityOptions, (locale) => locale.isSelected).keyForList}
- />
-
-
- );
-}
-
-WriteCapabilityPage.displayName = 'WriteCapabilityPage';
-WriteCapabilityPage.propTypes = propTypes;
-WriteCapabilityPage.defaultProps = defaultProps;
-
-export default compose(
- withLocalize,
- withReportOrNotFound(),
- withOnyx({
- policy: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`,
- },
- }),
-)(WriteCapabilityPage);
diff --git a/src/pages/settings/Report/WriteCapabilityPage.tsx b/src/pages/settings/Report/WriteCapabilityPage.tsx
new file mode 100644
index 000000000000..5f5fe73e5199
--- /dev/null
+++ b/src/pages/settings/Report/WriteCapabilityPage.tsx
@@ -0,0 +1,71 @@
+import type {StackScreenProps} from '@react-navigation/stack';
+import React from 'react';
+import {withOnyx} from 'react-native-onyx';
+import type {OnyxEntry} from 'react-native-onyx';
+import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import ScreenWrapper from '@components/ScreenWrapper';
+import SelectionList from '@components/SelectionList';
+import useLocalize from '@hooks/useLocalize';
+import Navigation from '@libs/Navigation/Navigation';
+import * as ReportUtils from '@libs/ReportUtils';
+import type {ReportSettingsNavigatorParamList} from '@navigation/types';
+import withReportOrNotFound from '@pages/home/report/withReportOrNotFound';
+import type {WithReportOrNotFoundProps} from '@pages/home/report/withReportOrNotFound';
+import * as ReportActions from '@userActions/Report';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
+import type {Policy} from '@src/types/onyx';
+
+type WriteCapabilityPageOnyxProps = {
+ /** The policy object for the current route */
+ policy: OnyxEntry;
+};
+
+type WriteCapabilityPageProps = WriteCapabilityPageOnyxProps &
+ WithReportOrNotFoundProps &
+ StackScreenProps;
+
+function WriteCapabilityPage({report, policy}: WriteCapabilityPageProps) {
+ const {translate} = useLocalize();
+ const writeCapabilityOptions = Object.values(CONST.REPORT.WRITE_CAPABILITIES).map((value) => ({
+ value,
+ text: translate(`writeCapabilityPage.writeCapability.${value}`),
+ keyForList: value,
+ isSelected: value === (report?.writeCapability ?? CONST.REPORT.WRITE_CAPABILITIES.ALL),
+ }));
+
+ const isAbleToEdit = ReportUtils.canEditWriteCapability(report, policy);
+
+ return (
+
+
+ Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? ''))}
+ />
+ report && ReportActions.updateWriteCapabilityAndNavigate(report, option.value)}
+ initiallyFocusedOptionKey={writeCapabilityOptions.find((locale) => locale.isSelected)?.keyForList}
+ />
+
+
+ );
+}
+
+WriteCapabilityPage.displayName = 'WriteCapabilityPage';
+
+export default withReportOrNotFound()(
+ withOnyx({
+ policy: {
+ key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`,
+ },
+ })(WriteCapabilityPage),
+);
diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx
index 040794422bb8..05c879669e78 100644
--- a/src/pages/settings/Security/SecuritySettingsPage.tsx
+++ b/src/pages/settings/Security/SecuritySettingsPage.tsx
@@ -53,6 +53,7 @@ function SecuritySettingsPage() {
illustration={LottieAnimations.Safe}
backgroundColor={theme.PAGE_THEMES[SCREENS.SETTINGS.SECURITY].backgroundColor}
shouldShowOfflineIndicatorInWideScreen
+ testID={SecuritySettingsPage.displayName}
>
diff --git a/src/pages/settings/Wallet/ActivatePhysicalCardPage.js b/src/pages/settings/Wallet/ActivatePhysicalCardPage.js
index bfc8583998b6..947252649cc4 100644
--- a/src/pages/settings/Wallet/ActivatePhysicalCardPage.js
+++ b/src/pages/settings/Wallet/ActivatePhysicalCardPage.js
@@ -138,6 +138,7 @@ function ActivatePhysicalCardPage({
illustration={LottieAnimations.Magician}
scrollViewContainerStyles={[styles.mnh100]}
childrenContainerStyles={[styles.flex1]}
+ testID={ActivatePhysicalCardPage.displayName}
>
{translate('activateCardPage.pleaseEnterLastFour')}
diff --git a/src/pages/settings/Wallet/WalletEmptyState.js b/src/pages/settings/Wallet/WalletEmptyState.js
index c26e2947c0ef..c76d8a7e21fd 100644
--- a/src/pages/settings/Wallet/WalletEmptyState.js
+++ b/src/pages/settings/Wallet/WalletEmptyState.js
@@ -46,6 +46,7 @@ function WalletEmptyState({onAddPaymentMethod}) {
shouldShowBackButton={isSmallScreenWidth}
shouldShowOfflineIndicatorInWideScreen
style={isSmallScreenWidth ? styles.workspaceSectionMobile : styles.workspaceSection}
+ testID={WalletEmptyState.displayName}
>
{
- if (firstBlurred.current || !Visibility.isVisible() || !Visibility.hasFocus()) {
- return;
- }
- firstBlurred.current = true;
- validate(login);
- }}
+ onBlur={
+ // As we have only two signin buttons (Apple/Google) other than the text input,
+ // for natives onBlur is called only when the buttons are pressed and we don't need
+ // to validate in those case as the user has opted for other signin flow.
+ willBlurTextInputOnTapOutside
+ ? () =>
+ // This delay is to avoid the validate being called before google iframe is rendered to
+ // avoid error message appearing after pressing google signin button.
+ setTimeout(() => {
+ if (firstBlurred.current || !Visibility.isVisible() || !Visibility.hasFocus()) {
+ return;
+ }
+ firstBlurred.current = true;
+ validate(login);
+ }, 500)
+ : undefined
+ }
onChangeText={onTextInput}
onSubmitEditing={validateAndSubmitForm}
autoCapitalize="none"
@@ -333,10 +346,10 @@ function LoginForm(props) {
- e.preventDefault()}>
+
- e.preventDefault()}>
+
diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js
index b616b519ff32..1c6981b9936a 100644
--- a/src/pages/workspace/WorkspaceNewRoomPage.js
+++ b/src/pages/workspace/WorkspaceNewRoomPage.js
@@ -106,11 +106,14 @@ function WorkspaceNewRoomPage(props) {
const workspaceOptions = useMemo(
() =>
- _.map(PolicyUtils.getActivePolicies(props.policies), (policy) => ({
- label: policy.name,
- key: policy.id,
- value: policy.id,
- })),
+ _.map(
+ _.filter(PolicyUtils.getActivePolicies(props.policies), (policy) => policy.type !== CONST.POLICY.TYPE.PERSONAL),
+ (policy) => ({
+ label: policy.name,
+ key: policy.id,
+ value: policy.id,
+ }),
+ ),
[props.policies],
);
const [policyID, setPolicyID] = useState(() => {
@@ -133,7 +136,7 @@ function WorkspaceNewRoomPage(props) {
*/
const submit = (values) => {
const participants = [props.session.accountID];
- const parsedWelcomeMessage = ReportUtils.getParsedComment(values.welcomeMessage);
+ const parsedDescription = ReportUtils.getParsedComment(values.reportDescription);
const policyReport = ReportUtils.buildOptimisticChatReport(
participants,
values.roomName,
@@ -147,7 +150,7 @@ function WorkspaceNewRoomPage(props) {
CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS,
'',
'',
- parsedWelcomeMessage,
+ parsedDescription,
);
setNewRoomReportID(policyReport.reportID);
Report.addPolicyReport(policyReport);
@@ -289,6 +292,7 @@ function WorkspaceNewRoomPage(props) {
validate={validate}
onSubmit={submit}
enabledWhenOffline
+ disablePressOnEnter={false}
>
diff --git a/src/styles/index.ts b/src/styles/index.ts
index 8d28e80b490f..98abb94b6f30 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -415,6 +415,10 @@ const styles = (theme: ThemeColors) =>
fontWeight: FontUtils.fontWeight.bold,
},
+ fontWeightNormal: {
+ fontWeight: FontUtils.fontWeight.normal,
+ },
+
textHeadline: {
...headlineFont,
...whiteSpace.preWrap,
@@ -1815,6 +1819,12 @@ const styles = (theme: ThemeColors) =>
...wordBreak.breakWord,
},
+ renderHTML: {
+ maxWidth: '100%',
+ ...whiteSpace.preWrap,
+ ...wordBreak.breakWord,
+ },
+
chatItemComposeWithFirstRow: {
minHeight: 90,
},
diff --git a/src/styles/utils/FontUtils/index.ts b/src/styles/utils/FontUtils/index.ts
index ac07fdbf026e..b93655fdf63d 100644
--- a/src/styles/utils/FontUtils/index.ts
+++ b/src/styles/utils/FontUtils/index.ts
@@ -11,6 +11,7 @@ const FontUtils = {
},
fontWeight: {
bold: fontWeightBold,
+ normal: '400',
},
};
diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts
index 010714f4208f..a45b7cdbcb34 100644
--- a/src/styles/utils/index.ts
+++ b/src/styles/utils/index.ts
@@ -699,8 +699,8 @@ function getHorizontalStackedOverlayAvatarStyle(oneAvatarSize: AvatarSize, oneAv
/**
* Gets the correct size for the empty state background image based on screen dimensions
*/
-function getReportWelcomeBackgroundImageStyle(isSmallScreenWidth: boolean, isMoneyReport = false): ImageStyle {
- const emptyStateBackground = isMoneyReport ? CONST.EMPTY_STATE_BACKGROUND.MONEY_REPORT : CONST.EMPTY_STATE_BACKGROUND;
+function getReportWelcomeBackgroundImageStyle(isSmallScreenWidth: boolean, isMoneyOrTaskReport = false): ImageStyle {
+ const emptyStateBackground = isMoneyOrTaskReport ? CONST.EMPTY_STATE_BACKGROUND.MONEY_OR_TASK_REPORT : CONST.EMPTY_STATE_BACKGROUND;
if (isSmallScreenWidth) {
return {
@@ -720,8 +720,8 @@ function getReportWelcomeBackgroundImageStyle(isSmallScreenWidth: boolean, isMon
/**
* Gets the correct top margin size for the chat welcome message based on screen dimensions
*/
-function getReportWelcomeTopMarginStyle(isSmallScreenWidth: boolean, isMoneyReport = false): ViewStyle {
- const emptyStateBackground = isMoneyReport ? CONST.EMPTY_STATE_BACKGROUND.MONEY_REPORT : CONST.EMPTY_STATE_BACKGROUND;
+function getReportWelcomeTopMarginStyle(isSmallScreenWidth: boolean, isMoneyOrTaskReport = false): ViewStyle {
+ const emptyStateBackground = isMoneyOrTaskReport ? CONST.EMPTY_STATE_BACKGROUND.MONEY_OR_TASK_REPORT : CONST.EMPTY_STATE_BACKGROUND;
if (isSmallScreenWidth) {
return {
marginTop: emptyStateBackground.SMALL_SCREEN.VIEW_HEIGHT,
@@ -754,8 +754,8 @@ function getLineHeightStyle(lineHeight: number): TextStyle {
/**
* Gets the correct size for the empty state container based on screen dimensions
*/
-function getReportWelcomeContainerStyle(isSmallScreenWidth: boolean, isMoneyReport = false): ViewStyle {
- const emptyStateBackground = isMoneyReport ? CONST.EMPTY_STATE_BACKGROUND.MONEY_REPORT : CONST.EMPTY_STATE_BACKGROUND;
+function getReportWelcomeContainerStyle(isSmallScreenWidth: boolean, isMoneyOrTaskReport = false): ViewStyle {
+ const emptyStateBackground = isMoneyOrTaskReport ? CONST.EMPTY_STATE_BACKGROUND.MONEY_OR_TASK_REPORT : CONST.EMPTY_STATE_BACKGROUND;
if (isSmallScreenWidth) {
return {
minHeight: emptyStateBackground.SMALL_SCREEN.CONTAINER_MINHEIGHT,
diff --git a/src/types/onyx/Form.ts b/src/types/onyx/Form.ts
index 48f386afcbb0..6938e17163d2 100644
--- a/src/types/onyx/Form.ts
+++ b/src/types/onyx/Form.ts
@@ -33,7 +33,7 @@ type DisplayNameForm = Form<{
type NewRoomForm = Form<{
roomName?: string;
- welcomeMessage?: string;
+ reportDescription?: string;
policyID?: string;
writeCapability?: string;
visibility?: string;
@@ -68,6 +68,10 @@ type CloseAccountForm = Form<{
phoneOrEmail: string;
}>;
+type RoomNameForm = Form<{
+ roomName: string;
+}>;
+
export default Form;
export type {
@@ -84,4 +88,5 @@ export type {
WorkspaceSettingsForm,
ReportFieldEditForm,
CloseAccountForm,
+ RoomNameForm,
};
diff --git a/src/types/onyx/IOU.ts b/src/types/onyx/IOU.ts
index e4a195dc291e..b04011978d73 100644
--- a/src/types/onyx/IOU.ts
+++ b/src/types/onyx/IOU.ts
@@ -1,29 +1,49 @@
+import type {ValueOf} from 'type-fest';
+import type CONST from '@src/CONST';
import type {Icon} from './OnyxCommon';
type Participant = {
- accountID: number;
- login: string;
+ accountID?: number;
+ login?: string;
+ displayName?: string;
isPolicyExpenseChat?: boolean;
isOwnPolicyExpenseChat?: boolean;
- selected?: boolean;
+ chatType?: ValueOf;
reportID?: string;
+ policyID?: string;
+ selected?: boolean;
searchText?: string;
- alternateText: string;
- firstName: string;
- icons: Icon[];
- keyForList: string;
- lastName: string;
- phoneNumber: string;
- text: string;
+ alternateText?: string;
+ firstName?: string;
+ icons?: Icon[];
+ keyForList?: string;
+ lastName?: string;
+ phoneNumber?: string;
+ text?: string;
isSelected?: boolean;
};
+type Split = {
+ email?: string;
+ amount?: number;
+ accountID?: number;
+ chatReportID?: string;
+ iouReportID?: string;
+ reportActionID?: string;
+ transactionID?: string;
+ policyID?: string;
+ createdChatReportActionID?: string;
+ createdIOUReportActionID?: string;
+ reportPreviewReportActionID?: string;
+};
+
type IOU = {
id: string;
amount?: number;
/** Selected Currency Code of the current IOU */
currency?: string;
comment?: string;
+ category?: string;
merchant?: string;
created?: string;
receiptPath?: string;
@@ -31,7 +51,9 @@ type IOU = {
transactionID?: string;
participants?: Participant[];
tag?: string;
+ billable?: boolean;
+ isSplitRequest?: boolean;
};
export default IOU;
-export type {Participant};
+export type {Participant, Split};
diff --git a/src/types/onyx/LastPaymentMethod.ts b/src/types/onyx/LastPaymentMethod.ts
new file mode 100644
index 000000000000..677a23fa9586
--- /dev/null
+++ b/src/types/onyx/LastPaymentMethod.ts
@@ -0,0 +1,3 @@
+type LastPaymentMethod = Record;
+
+export default LastPaymentMethod;
diff --git a/src/types/onyx/OnyxCommon.ts b/src/types/onyx/OnyxCommon.ts
index 2dafe16b5388..2bb6eca8c3f0 100644
--- a/src/types/onyx/OnyxCommon.ts
+++ b/src/types/onyx/OnyxCommon.ts
@@ -8,7 +8,7 @@ type PendingFields = Record = Record;
-type Errors = Record;
+type Errors = Record;
type AvatarType = typeof CONST.ICON_TYPE_AVATAR | typeof CONST.ICON_TYPE_WORKSPACE;
diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts
index 2ca9e67dfe53..459ff5524034 100644
--- a/src/types/onyx/OriginalMessage.ts
+++ b/src/types/onyx/OriginalMessage.ts
@@ -2,7 +2,7 @@ import type {ValueOf} from 'type-fest';
import type CONST from '@src/CONST';
import type DeepValueOf from '@src/types/utils/DeepValueOf';
-type PaymentMethodType = DeepValueOf;
+type PaymentMethodType = DeepValueOf;
type ActionName = DeepValueOf;
type OriginalMessageActionName =
diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts
index f55b3b797bf0..784cd546a961 100644
--- a/src/types/onyx/Policy.ts
+++ b/src/types/onyx/Policy.ts
@@ -76,9 +76,6 @@ type Policy = {
/** The custom units data for this policy */
customUnits?: Record;
- /** Whether chat rooms can be created and used on this policy. Enabled manually by CQ/JS snippet. Always true for free policies. */
- areChatRoomsEnabled: boolean;
-
/** Whether policy expense chats can be created and used on this policy. Enabled manually by CQ/JS snippet. Always true for free policies. */
isPolicyExpenseChatEnabled: boolean;
diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts
index d26bdd4f282e..fbd61a9c5365 100644
--- a/src/types/onyx/Report.ts
+++ b/src/types/onyx/Report.ts
@@ -88,6 +88,9 @@ type Report = {
/** ID of the report */
reportID: string;
+ /** ID of the report action */
+ reportActionID?: string;
+
/** ID of the chat report */
chatReportID?: string;
@@ -121,7 +124,6 @@ type Report = {
lastVisibleActionLastModified?: string;
displayName?: string;
lastMessageHtml?: string;
- welcomeMessage?: string;
lastActorAccountID?: number;
ownerAccountID?: number;
ownerEmail?: string;
diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts
index d2f0afad5b7a..3ec98fd43178 100644
--- a/src/types/onyx/ReportAction.ts
+++ b/src/types/onyx/ReportAction.ts
@@ -143,6 +143,9 @@ type ReportActionBase = {
/** Type of child report */
childType?: string;
+ /** The user's ID */
+ accountID?: number;
+
childOldestFourEmails?: string;
childOldestFourAccountIDs?: string;
childCommenterCount?: number;
@@ -178,7 +181,7 @@ type ReportActionBase = {
delegateAccountID?: number;
/** Server side errors keyed by microtime */
- errors?: OnyxCommon.Errors;
+ errors?: OnyxCommon.Errors | OnyxCommon.ErrorFields;
/** Whether the report action is attachment */
isAttachment?: boolean;
diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts
index fe7ca7436a81..b559346a48de 100644
--- a/src/types/onyx/Transaction.ts
+++ b/src/types/onyx/Transaction.ts
@@ -1,5 +1,6 @@
import type {ValueOf} from 'type-fest';
import type CONST from '@src/CONST';
+import type {Participant, Split} from './IOU';
import type * as OnyxCommon from './OnyxCommon';
import type RecentWaypoint from './RecentWaypoint';
@@ -18,6 +19,7 @@ type Waypoint = {
};
type WaypointCollection = Record;
+
type Comment = {
comment?: string;
waypoints?: WaypointCollection;
@@ -26,6 +28,7 @@ type Comment = {
customUnit?: Record;
source?: string;
originalTransactionID?: string;
+ splits?: Split[];
};
type GeometryType = 'LineString';
@@ -35,10 +38,14 @@ type Geometry = {
type?: GeometryType;
};
+type ReceiptSource = string | number;
+
type Receipt = {
receiptID?: number;
path?: string;
- source?: string;
+ name?: string;
+ source?: ReceiptSource;
+ filename?: string;
state?: ValueOf;
};
@@ -55,52 +62,150 @@ type ReceiptError = {error?: string; source: string; filename: string};
type ReceiptErrors = Record;
+type TaxRateData = {
+ name: string;
+ value: string;
+ code?: string;
+};
+
+type TaxRate = {
+ text: string;
+ keyForList: string;
+ searchText: string;
+ tooltipText: string;
+ isDisabled?: boolean;
+ data?: TaxRateData;
+};
+
type Transaction = {
+ /** The original transaction amount */
amount: number;
- billable: boolean;
- category: string;
+
+ /** Whether the request is billable */
+ billable?: boolean;
+
+ /** The category name */
+ category?: string;
+
+ /** The comment object on the transaction */
comment: Comment;
+
+ /** Date that the request was created */
created: string;
+
+ /** The original currency of the transaction */
currency: string;
+
+ /** Any additional error message to show */
errors?: OnyxCommon.Errors | ReceiptErrors;
+
+ /** Server side errors keyed by microtime */
errorFields?: OnyxCommon.ErrorFields<'route'>;
- // The name of the file used for a receipt (formerly receiptFilename)
+
+ /** The name of the file used for a receipt (formerly receiptFilename) */
filename?: string;
- // Used during the creation flow before the transaction is saved to the server
+
+ /** Used during the creation flow before the transaction is saved to the server */
iouRequestType?: ValueOf;
+
+ /** The original merchant name */
merchant: string;
+
+ /** The edited transaction amount */
modifiedAmount?: number;
+
+ /** The edited transaction date */
modifiedCreated?: string;
+
+ /** The edited currency of the transaction */
modifiedCurrency?: string;
+
+ /** The edited merchant name */
modifiedMerchant?: string;
+
+ /** The edited waypoints for the distance request */
modifiedWaypoints?: WaypointCollection;
- // Used during the creation flow before the transaction is saved to the server and helps dictate where the user is navigated to when pressing the back button on the confirmation step
+
+ /**
+ * Used during the creation flow before the transaction is saved to the server and helps dictate where
+ * the user is navigated to when pressing the back button on the confirmation step
+ */
participantsAutoAssigned?: boolean;
- pendingAction: OnyxCommon.PendingAction;
+
+ /** Selected participants */
+ participants?: Participant[];
+
+ /** The type of action that's pending */
+ pendingAction?: OnyxCommon.PendingAction;
+
+ /** The receipt object associated with the transaction */
receipt?: Receipt;
+
+ /** The iouReportID associated with the transaction */
reportID: string;
+
+ /** Existing routes */
routes?: Routes;
+
+ /** The transaction id */
transactionID: string;
- tag: string;
+
+ /** The transaction tag */
+ tag?: string;
+
+ /** Whether the transaction was created globally */
+ isFromGlobalCreate?: boolean;
+
+ /** The transaction tax rate */
+ taxRate?: TaxRate;
+
+ /** Tax amount */
+ taxAmount?: number;
+
+ /** Pending fields for the transaction */
pendingFields?: Partial<{[K in TransactionPendingFieldsKey]: ValueOf}>;
/** Card Transactions */
+ /** The parent transaction id */
parentTransactionID?: string;
+
+ /** Whether the expense is reimbursable or not */
reimbursable?: boolean;
+
/** The CC for this transaction */
cardID?: number;
+
/** If the transaction is pending or posted */
status?: ValueOf;
+
/** If an EReceipt should be generated for this transaction */
hasEReceipt?: boolean;
+
/** The MCC Group for this transaction */
mccGroup?: ValueOf;
+
+ /** Modified MCC Group */
modifiedMCCGroup?: ValueOf;
+
/** If the transaction was made in a foreign currency, we send the original amount and currency */
originalAmount?: number;
+
+ /** The original currency of the transaction */
originalCurrency?: string;
+
+ /** Indicates transaction loading */
+ isLoading?: boolean;
+};
+
+type AdditionalTransactionChanges = {
+ comment?: string;
+ waypoints?: WaypointCollection;
+ oldAmount?: number;
+ oldCurrency?: string;
};
+type TransactionChanges = Partial & AdditionalTransactionChanges;
+
export default Transaction;
-export type {WaypointCollection, Comment, Receipt, Waypoint, ReceiptError, ReceiptErrors, TransactionPendingFieldsKey};
+export type {WaypointCollection, Comment, Receipt, Waypoint, ReceiptError, ReceiptErrors, TransactionPendingFieldsKey, TransactionChanges, TaxRate, ReceiptSource};
diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts
index d0ac2ce395fa..4728c8872e75 100644
--- a/src/types/onyx/index.ts
+++ b/src/types/onyx/index.ts
@@ -19,6 +19,7 @@ import type {
NewRoomForm,
PrivateNotesForm,
ReportFieldEditForm,
+ RoomNameForm,
WorkspaceSettingsForm,
} from './Form';
import type Form from './Form';
@@ -27,6 +28,7 @@ import type {FundList} from './Fund';
import type Fund from './Fund';
import type IntroSelected from './IntroSelected';
import type IOU from './IOU';
+import type LastPaymentMethod from './LastPaymentMethod';
import type Locale from './Locale';
import type {LoginList} from './Login';
import type Login from './Login';
@@ -160,9 +162,11 @@ export type {
PolicyReportField,
PolicyReportFields,
RecentlyUsedReportFields,
+ LastPaymentMethod,
NewRoomForm,
IKnowATeacherForm,
IntroSchoolPrincipalForm,
PrivateNotesForm,
ReportFieldEditForm,
+ RoomNameForm,
};
diff --git a/src/types/utils/AnchorAlignment.ts b/src/types/utils/AnchorAlignment.ts
new file mode 100644
index 000000000000..899e3d9e277b
--- /dev/null
+++ b/src/types/utils/AnchorAlignment.ts
@@ -0,0 +1,12 @@
+import type {ValueOf} from 'type-fest';
+import type CONST from '@src/CONST';
+
+type AnchorAlignment = {
+ /** The horizontal anchor alignment of the popover */
+ horizontal: ValueOf;
+
+ /** The vertical anchor alignment of the popover */
+ vertical: ValueOf;
+};
+
+export default AnchorAlignment;
diff --git a/tests/ui/UnreadIndicatorsTest.js b/tests/ui/UnreadIndicatorsTest.js
index e4d4d877f66b..6a57218fab23 100644
--- a/tests/ui/UnreadIndicatorsTest.js
+++ b/tests/ui/UnreadIndicatorsTest.js
@@ -43,6 +43,7 @@ jest.mock('react-native/Libraries/LogBox/LogBox', () => ({
jest.mock('react-native-reanimated', () => ({
...jest.requireActual('react-native-reanimated/mock'),
createAnimatedPropAdapter: jest.fn,
+ useReducedMotion: jest.fn,
}));
/**
@@ -438,11 +439,11 @@ describe('Unread Indicators', () => {
expect(displayNameTexts).toHaveLength(2);
const firstReportOption = displayNameTexts[0];
expect(lodashGet(firstReportOption, ['props', 'style', 'fontWeight'])).toBe(FontUtils.fontWeight.bold);
- expect(lodashGet(firstReportOption, ['props', 'children'])).toBe('C User');
+ expect(lodashGet(firstReportOption, ['props', 'children', 0])).toBe('C User');
const secondReportOption = displayNameTexts[1];
expect(lodashGet(secondReportOption, ['props', 'style', 'fontWeight'])).toBe(FontUtils.fontWeight.bold);
- expect(lodashGet(secondReportOption, ['props', 'children'])).toBe('B User');
+ expect(lodashGet(secondReportOption, ['props', 'children', 0])).toBe('B User');
// Tap the new report option and navigate back to the sidebar again via the back button
return navigateToSidebarOption(0);
@@ -455,9 +456,9 @@ describe('Unread Indicators', () => {
const displayNameTexts = screen.queryAllByLabelText(hintText);
expect(displayNameTexts).toHaveLength(2);
expect(lodashGet(displayNameTexts[0], ['props', 'style', 'fontWeight'])).toBe(undefined);
- expect(lodashGet(displayNameTexts[0], ['props', 'children'])).toBe('C User');
+ expect(lodashGet(displayNameTexts[0], ['props', 'children', 0])).toBe('C User');
expect(lodashGet(displayNameTexts[1], ['props', 'style', 'fontWeight'])).toBe(FontUtils.fontWeight.bold);
- expect(lodashGet(displayNameTexts[1], ['props', 'children'])).toBe('B User');
+ expect(lodashGet(displayNameTexts[1], ['props', 'children', 0])).toBe('B User');
}));
xit('Manually marking a chat message as unread shows the new line indicator and updates the LHN', () =>
@@ -489,7 +490,7 @@ describe('Unread Indicators', () => {
const displayNameTexts = screen.queryAllByLabelText(hintText);
expect(displayNameTexts).toHaveLength(1);
expect(lodashGet(displayNameTexts[0], ['props', 'style', 'fontWeight'])).toBe(FontUtils.fontWeight.bold);
- expect(lodashGet(displayNameTexts[0], ['props', 'children'])).toBe('B User');
+ expect(lodashGet(displayNameTexts[0], ['props', 'children', 0])).toBe('B User');
// Navigate to the report again and back to the sidebar
return navigateToSidebarOption(0);
@@ -501,7 +502,7 @@ describe('Unread Indicators', () => {
const displayNameTexts = screen.queryAllByLabelText(hintText);
expect(displayNameTexts).toHaveLength(1);
expect(lodashGet(displayNameTexts[0], ['props', 'style', 'fontWeight'])).toBe(undefined);
- expect(lodashGet(displayNameTexts[0], ['props', 'children'])).toBe('B User');
+ expect(lodashGet(displayNameTexts[0], ['props', 'children', 0])).toBe('B User');
// Navigate to the report again and verify the new line indicator is missing
return navigateToSidebarOption(0);
diff --git a/tests/unit/SidebarFilterTest.js b/tests/unit/SidebarFilterTest.js
index 55840ad24b6a..148710cb2d25 100644
--- a/tests/unit/SidebarFilterTest.js
+++ b/tests/unit/SidebarFilterTest.js
@@ -339,7 +339,7 @@ xdescribe('Sidebar', () => {
const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
expect(screen.queryAllByAccessibilityHint(navigatesToChatHintText)).toHaveLength(1);
expect(displayNames).toHaveLength(1);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Three, Four');
} else {
// Both reports visible
const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
@@ -380,8 +380,8 @@ xdescribe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(2);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('One, Two');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('One, Two');
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('Three, Four');
})
// When report3 becomes unread
@@ -450,8 +450,8 @@ xdescribe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(2);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Three, Four');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('One, Two');
})
);
});
@@ -663,7 +663,7 @@ xdescribe('Sidebar', () => {
const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
expect(screen.queryAllByAccessibilityHint(navigatesToChatHintText)).toHaveLength(1);
expect(displayNames).toHaveLength(1);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Three, Four');
} else {
// Both reports visible
const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
diff --git a/tests/unit/SidebarOrderTest.js b/tests/unit/SidebarOrderTest.js
index ed0f210d1da6..7ae8c4e1e9b3 100644
--- a/tests/unit/SidebarOrderTest.js
+++ b/tests/unit/SidebarOrderTest.js
@@ -140,9 +140,9 @@ describe('Sidebar', () => {
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Three, Four');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('One, Two');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Five, Six');
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [2, 'props', 'children', 0])).toBe('One, Two');
})
);
});
@@ -189,9 +189,9 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('One, Two'); // this has `hasDraft` flag enabled so it will be on top
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Five, Six');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('One, Two'); // this has `hasDraft` flag enabled so it will be on top
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('Five, Six');
+ expect(lodashGet(displayNames, [2, 'props', 'children', 0])).toBe('Three, Four');
})
);
});
@@ -236,9 +236,9 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('One, Two');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Five, Six');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('One, Two');
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('Five, Six');
+ expect(lodashGet(displayNames, [2, 'props', 'children', 0])).toBe('Three, Four');
})
);
});
@@ -286,10 +286,10 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(4);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe(taskReportName);
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Five, Six');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Three, Four');
- expect(lodashGet(displayNames, [3, 'props', 'children'])).toBe('One, Two');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe(taskReportName);
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('Five, Six');
+ expect(lodashGet(displayNames, [2, 'props', 'children', 0])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [3, 'props', 'children', 0])).toBe('One, Two');
})
);
});
@@ -346,10 +346,10 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(4);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Email Two owes $100.00');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Five, Six');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Three, Four');
- expect(lodashGet(displayNames, [3, 'props', 'children'])).toBe('One, Two');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Email Two owes $100.00');
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('Five, Six');
+ expect(lodashGet(displayNames, [2, 'props', 'children', 0])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [3, 'props', 'children', 0])).toBe('One, Two');
})
);
});
@@ -410,10 +410,10 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(4);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Workspace owes $100.00');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Email Five');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Three, Four');
- expect(lodashGet(displayNames, [3, 'props', 'children'])).toBe('One, Two');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Workspace owes $100.00');
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('Email Five');
+ expect(lodashGet(displayNames, [2, 'props', 'children', 0])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [3, 'props', 'children', 0])).toBe('One, Two');
})
);
});
@@ -465,9 +465,9 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Three, Four');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Five, Six');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('One, Two');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('Five, Six');
+ expect(lodashGet(displayNames, [2, 'props', 'children', 0])).toBe('One, Two');
})
);
});
@@ -607,9 +607,9 @@ describe('Sidebar', () => {
expect(displayNames).toHaveLength(3);
expect(screen.queryAllByTestId('Pin Icon')).toHaveLength(1);
expect(screen.queryAllByTestId('Pencil Icon')).toHaveLength(1);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Email Two owes $100.00');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Email Two owes $100.00');
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('One, Two');
+ expect(lodashGet(displayNames, [2, 'props', 'children', 0])).toBe('Three, Four');
})
);
});
@@ -653,9 +653,9 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Five, Six');
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('One, Two');
+ expect(lodashGet(displayNames, [2, 'props', 'children', 0])).toBe('Three, Four');
})
// When a new report is added
@@ -666,10 +666,10 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(4);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Seven, Eight');
- expect(lodashGet(displayNames, [3, 'props', 'children'])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Five, Six');
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('One, Two');
+ expect(lodashGet(displayNames, [2, 'props', 'children', 0])).toBe('Seven, Eight');
+ expect(lodashGet(displayNames, [3, 'props', 'children', 0])).toBe('Three, Four');
})
);
});
@@ -713,9 +713,9 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Five, Six');
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('One, Two');
+ expect(lodashGet(displayNames, [2, 'props', 'children', 0])).toBe('Three, Four');
})
// When a new report is added
@@ -726,10 +726,10 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(4);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Seven, Eight');
- expect(lodashGet(displayNames, [3, 'props', 'children'])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Five, Six');
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('One, Two');
+ expect(lodashGet(displayNames, [2, 'props', 'children', 0])).toBe('Seven, Eight');
+ expect(lodashGet(displayNames, [3, 'props', 'children', 0])).toBe('Three, Four');
})
);
});
@@ -773,9 +773,9 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Three, Four');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Report (archived)');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Five, Six');
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [2, 'props', 'children', 0])).toBe('Report (archived)');
})
);
});
@@ -810,9 +810,9 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Five, Six');
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('One, Two');
+ expect(lodashGet(displayNames, [2, 'props', 'children', 0])).toBe('Three, Four');
})
// When a new report is added
@@ -823,10 +823,10 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(4);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Seven, Eight');
- expect(lodashGet(displayNames, [3, 'props', 'children'])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Five, Six');
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('One, Two');
+ expect(lodashGet(displayNames, [2, 'props', 'children', 0])).toBe('Seven, Eight');
+ expect(lodashGet(displayNames, [3, 'props', 'children', 0])).toBe('Three, Four');
})
);
});
@@ -865,9 +865,9 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Three, Four');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Report (archived)');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Five, Six');
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [2, 'props', 'children', 0])).toBe('Report (archived)');
})
);
});
@@ -1000,11 +1000,11 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(5);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Email Five owes $100.00');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Email Four owes $1,000.00');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Email Six owes $100.00');
- expect(lodashGet(displayNames, [3, 'props', 'children'])).toBe('Email Three owes $100.00');
- expect(lodashGet(displayNames, [4, 'props', 'children'])).toBe('Email Two owes $100.00');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Email Five owes $100.00');
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('Email Four owes $1,000.00');
+ expect(lodashGet(displayNames, [2, 'props', 'children', 0])).toBe('Email Six owes $100.00');
+ expect(lodashGet(displayNames, [3, 'props', 'children', 0])).toBe('Email Three owes $100.00');
+ expect(lodashGet(displayNames, [4, 'props', 'children', 0])).toBe('Email Two owes $100.00');
})
);
});
@@ -1050,9 +1050,9 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Five, Six');
+ expect(lodashGet(displayNames, [1, 'props', 'children', 0])).toBe('One, Two');
+ expect(lodashGet(displayNames, [2, 'props', 'children', 0])).toBe('Three, Four');
})
);
});
diff --git a/tests/unit/SidebarTest.js b/tests/unit/SidebarTest.js
index dba1365626ba..6a813ef1fa8c 100644
--- a/tests/unit/SidebarTest.js
+++ b/tests/unit/SidebarTest.js
@@ -83,7 +83,7 @@ describe('Sidebar', () => {
.then(() => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Report (archived)');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Report (archived)');
const hintMessagePreviewText = Localize.translateLocal('accessibilityHints.lastChatMessagePreview');
const messagePreviewTexts = screen.queryAllByLabelText(hintMessagePreviewText);
@@ -127,7 +127,7 @@ describe('Sidebar', () => {
.then(() => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Report (archived)');
+ expect(lodashGet(displayNames, [0, 'props', 'children', 0])).toBe('Report (archived)');
const hintMessagePreviewText = Localize.translateLocal('accessibilityHints.lastChatMessagePreview');
const messagePreviewTexts = screen.queryAllByLabelText(hintMessagePreviewText);
diff --git a/tests/utils/LHNTestUtils.js b/tests/utils/LHNTestUtils.js
index 04246c1c438a..3e40063dd040 100644
--- a/tests/utils/LHNTestUtils.js
+++ b/tests/utils/LHNTestUtils.js
@@ -252,7 +252,6 @@ function getFakePolicy(id = 1, name = 'Workspace-Test-001') {
avatar: '',
employeeList: [],
isPolicyExpenseChatEnabled: true,
- areChatRoomsEnabled: true,
lastModified: 1697323926777105,
autoReporting: true,
autoReportingFrequency: 'immediate',
diff --git a/tests/utils/collections/policies.ts b/tests/utils/collections/policies.ts
index 4223c7e41941..7ecf152122d3 100644
--- a/tests/utils/collections/policies.ts
+++ b/tests/utils/collections/policies.ts
@@ -7,7 +7,6 @@ export default function createRandomPolicy(index: number): Policy {
id: index.toString(),
name: randWord(),
type: rand(Object.values(CONST.POLICY.TYPE)),
- areChatRoomsEnabled: randBoolean(),
autoReporting: randBoolean(),
isPolicyExpenseChatEnabled: randBoolean(),
autoReportingFrequency: rand(Object.values(CONST.POLICY.AUTO_REPORTING_FREQUENCIES)),
diff --git a/web/index.html b/web/index.html
index f0cc320926a7..7b2fbe018111 100644
--- a/web/index.html
+++ b/web/index.html
@@ -121,16 +121,16 @@
<% if (htmlWebpackPlugin.options.isWeb) { %>
- <% if (htmlWebpackPlugin.options.isStaging) { %>
+ <% if (htmlWebpackPlugin.options.isProduction) { %>
-
+
<% } %>
<% } %>
- <% if (htmlWebpackPlugin.options.isWeb && htmlWebpackPlugin.options.isStaging) { %>
+ <% if (htmlWebpackPlugin.options.isWeb && htmlWebpackPlugin.options.isProduction) { %>