diff --git a/.eslintrc.js b/.eslintrc.js
index c2198da60c52..56a5236a7899 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -94,7 +94,6 @@ module.exports = {
rules: {
'prefer-regex-literals': 'off',
'rulesdir/no-multiple-onyx-in-file': 'off',
- 'rulesdir/onyx-props-must-have-default': 'off',
'react-native-a11y/has-accessibility-hint': ['off'],
'react/jsx-no-constructed-context-values': 'error',
'react-native-a11y/has-valid-accessibility-descriptors': [
diff --git a/.github/scripts/verifyRedirect.sh b/.github/scripts/verifyRedirect.sh
new file mode 100644
index 000000000000..737d9bffacf9
--- /dev/null
+++ b/.github/scripts/verifyRedirect.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# HelpDot - Verifies that redirects.csv does not have any duplicates
+# Duplicate sourceURLs break redirection on cloudflare pages
+
+declare -r REDIRECTS_FILE="docs/redirects.csv"
+
+duplicates=$(awk -F, 'a[$1]++{print $1}' $REDIRECTS_FILE)
+
+if [[ -z "$duplicates" ]]; then
+ exit 0
+fi
+
+echo "duplicate redirects are not allowed: $duplicates"
+exit 1
diff --git a/.github/workflows/deployExpensifyHelp.yml b/.github/workflows/deployExpensifyHelp.yml
index d4577e112d59..699bd379fb77 100644
--- a/.github/workflows/deployExpensifyHelp.yml
+++ b/.github/workflows/deployExpensifyHelp.yml
@@ -36,6 +36,9 @@ jobs:
- name: Create docs routes file
run: ./.github/scripts/createDocsRoutes.sh
+
+ - name: Check duplicates in redirect.csv
+ run: ./.github/scripts/verifyRedirect.sh
- name: Build with Jekyll
uses: actions/jekyll-build-pages@0143c158f4fa0c5dcd99499a5d00859d79f70b0e
diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml
index d97ea2b86269..bb850e6eda10 100644
--- a/.github/workflows/platformDeploy.yml
+++ b/.github/workflows/platformDeploy.yml
@@ -87,11 +87,12 @@ jobs:
MYAPP_UPLOAD_STORE_PASSWORD: ${{ secrets.MYAPP_UPLOAD_STORE_PASSWORD }}
MYAPP_UPLOAD_KEY_PASSWORD: ${{ secrets.MYAPP_UPLOAD_KEY_PASSWORD }}
- - name: Run Fastlane production
- if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }}
- run: bundle exec fastlane android production
- env:
- VERSION: ${{ env.VERSION_CODE }}
+ # Note: Android production deploys are temporarily disabled until https://github.com/Expensify/App/issues/40108 is resolved
+ # - name: Run Fastlane production
+ # if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }}
+ # run: bundle exec fastlane android production
+ # env:
+ # VERSION: ${{ env.VERSION_CODE }}
- name: Archive Android sourcemaps
uses: actions/upload-artifact@v3
@@ -158,7 +159,7 @@ jobs:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
GCP_GEOLOCATION_API_KEY: $${{ secrets.GCP_GEOLOCATION_API_KEY_PRODUCTION }}
-
+
- name: Build staging desktop app
if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }}
diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml
index 156b9764bcca..f20939f9df0a 100644
--- a/.github/workflows/typecheck.yml
+++ b/.github/workflows/typecheck.yml
@@ -30,7 +30,7 @@ jobs:
# - git diff is used to see the files that were added on this branch
# - gh pr view is used to list files touched by this PR. Git diff may give false positives if the branch isn't up-to-date with main
# - wc counts the words in the result of the intersection
- count_new_js=$(comm -1 -2 <(git diff --name-only --diff-filter=A origin/main HEAD -- 'src/*.js') <(gh pr view ${{ github.event.pull_request.number }} --json files | jq -r '.files | map(.path) | .[]') | wc -l)
+ count_new_js=$(comm -1 -2 <(git diff --name-only --diff-filter=A origin/main HEAD -- 'src/*.js' '__mocks__/*.js' '.storybook/*.js' 'assets/*.js' 'config/*.js' 'desktop/*.js' 'jest/*.js' 'scripts/*.js' 'tests/*.js' 'web/*.js' 'workflow_tests/*.js' '.github/libs/*.js' '.github/scripts/*.js') <(gh pr view ${{ github.event.pull_request.number }} --json files | jq -r '.files | map(.path) | .[]') | wc -l)
if [ "$count_new_js" -gt "0" ]; then
echo "ERROR: Found new JavaScript files in the project; use TypeScript instead."
exit 1
diff --git a/.well-known/apple-app-site-association b/.well-known/apple-app-site-association
index b3adf0f59b9c..a2c7365f7de8 100644
--- a/.well-known/apple-app-site-association
+++ b/.well-known/apple-app-site-association
@@ -32,10 +32,6 @@
"/": "/iou/*",
"comment": "I Owe You reports"
},
- {
- "/": "/request/*",
- "comment": "Money request"
- },
{
"/": "/enable-payments/*",
"comment": "Payments setup"
@@ -54,11 +50,11 @@
},
{
"/": "/split/*",
- "comment": "Split Bill"
+ "comment": "Split Expense"
},
{
"/": "/request/*",
- "comment": "Request Money"
+ "comment": "Submit Expense"
},
{
"/": "/new/*",
@@ -82,7 +78,7 @@
},
{
"/": "/send/*",
- "comment": "Send money"
+ "comment": "Pay someone"
},
{
"/": "/money2020/*",
diff --git a/android/app/build.gradle b/android/app/build.gradle
index da340184e7c8..0db4b032ec9d 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 1001046210
- versionName "1.4.62-10"
+ versionCode 1001046300
+ versionName "1.4.63-0"
// Supported language variants must be declared here to avoid from being removed during the compilation.
// This also helps us to not include unnecessary language variants in the APK.
resConfigs "en", "es"
diff --git a/docs/articles/expensify-classic/integrations/HR-integrations/Zenefits.md b/docs/articles/expensify-classic/integrations/HR-integrations/Zenefits.md
index e94d915e4dfa..07421553aeb2 100644
--- a/docs/articles/expensify-classic/integrations/HR-integrations/Zenefits.md
+++ b/docs/articles/expensify-classic/integrations/HR-integrations/Zenefits.md
@@ -17,7 +17,7 @@ Expensify's direct integration with Zenefits will automatically:
- Every employee record in Zenefits must have a work email address since we use this as the unique identifier in Expensify.
- Zenefits will add all your employees to one Expensify workspace. If your company uses multiple Expensify workspaces, you'll be given the option to choose which workspace to connect to when you're setting up the integration.
-## To connect your Expensify workspace to Gusto:
+## To connect your Expensify workspace to Zenefits:
1. Navigate to **Settings > Workspaces > Group > _[Workspace Name]_ > Connections**
2. Scroll down to HR Integrations, click the **Connect to Zenefits** radio button, then click **Sync with Zenefits**
diff --git a/docs/redirects.csv b/docs/redirects.csv
index 51c8c7515e10..af595ecc5f83 100644
--- a/docs/redirects.csv
+++ b/docs/redirects.csv
@@ -152,7 +152,6 @@ https://help.expensify.com/articles/expensify-classic/manage-employees-and-repor
https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Invite-Members,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Invite-Members
https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Removing-Members,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Removing-Members
https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles,https://help.expensify.com/expensify-classic/hubs/copilots-and-delegates/
-https://help.expensify.com/articles/expensify-classic/reports/Currency,https://help.expensify.com/articles/expensify-classic/workspaces/Currency
https://help.expensify.com/articles/expensify-classic/send-payments/Reimbursing-Reports,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Reimbursing-Reports
https://help.expensify.com/articles/expensify-classic/workspace-and-domain-settings/SAML-SSO,https://help.expensify.com/articles/expensify-classic/settings/Enable-two-factor-authentication
https://help.expensify.com/articles/expensify-classic/workspaces/Budgets,https://help.expensify.com/articles/expensify-classic/workspaces/Set-budgets
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 4dea8203b477..ddcc64604581 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.4.62
+ 1.4.63
CFBundleSignature
????
CFBundleURLTypes
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.4.62.10
+ 1.4.63.0
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 0d1e81ade440..a57ffb9500c5 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 1.4.62
+ 1.4.63
CFBundleSignature
????
CFBundleVersion
- 1.4.62.10
+ 1.4.63.0
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index a2dfb017df48..3c8e8c1fb63f 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -11,9 +11,9 @@
CFBundleName
$(PRODUCT_NAME)
CFBundleShortVersionString
- 1.4.62
+ 1.4.63
CFBundleVersion
- 1.4.62.10
+ 1.4.63.0
NSExtension
NSExtensionPointIdentifier
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 1ebfc6bb1b62..f564bfd931e4 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1835,7 +1835,7 @@ PODS:
- RNGoogleSignin (10.0.1):
- GoogleSignIn (~> 7.0)
- React-Core
- - RNLiveMarkdown (0.1.47):
+ - RNLiveMarkdown (0.1.62):
- glog
- hermes-engine
- RCT-Folly (= 2022.05.16.00)
@@ -1853,9 +1853,9 @@ PODS:
- React-utils
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- - RNLiveMarkdown/common (= 0.1.47)
+ - RNLiveMarkdown/common (= 0.1.62)
- Yoga
- - RNLiveMarkdown/common (0.1.47):
+ - RNLiveMarkdown/common (0.1.62):
- glog
- hermes-engine
- RCT-Folly (= 2022.05.16.00)
@@ -2564,7 +2564,7 @@ SPEC CHECKSUMS:
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
RNGestureHandler: 1190c218cdaaf029ee1437076a3fbbc3297d89fb
RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0
- RNLiveMarkdown: f172c7199283dc9d21bccf7e21ea10741fd19e1d
+ RNLiveMarkdown: 47dfb50244f9ba1caefbc0efc6404ba41bf6620a
RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81
rnmapbox-maps: 3e273e0e867a079ec33df9ee33bb0482434b897d
RNPermissions: 8990fc2c10da3640938e6db1647cb6416095b729
@@ -2581,7 +2581,7 @@ SPEC CHECKSUMS:
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2
VisionCamera: 3033e0dd5272d46e97bcb406adea4ae0e6907abf
- Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70
+ Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312
PODFILE CHECKSUM: a25a81f2b50270f0c0bd0aff2e2ebe4d0b4ec06d
diff --git a/package-lock.json b/package-lock.json
index 203e062de680..920fefc8242b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,19 +1,19 @@
{
"name": "new.expensify",
- "version": "1.4.62-10",
+ "version": "1.4.63-0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.4.62-10",
+ "version": "1.4.63-0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@babel/plugin-proposal-private-methods": "^7.18.6",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@dotlottie/react-player": "^1.6.3",
- "@expensify/react-native-live-markdown": "0.1.47",
+ "@expensify/react-native-live-markdown": "0.1.62",
"@expo/metro-runtime": "~3.1.1",
"@formatjs/intl-datetimeformat": "^6.10.0",
"@formatjs/intl-listformat": "^7.2.2",
@@ -57,7 +57,7 @@
"date-fns-tz": "^2.0.0",
"dom-serializer": "^0.2.2",
"domhandler": "^4.3.0",
- "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#13de5b0606662df33fa1392ad82cc11daadff52e",
+ "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#c0f7f3b6558fbeda0527c80d68460d418afef219",
"expo": "^50.0.3",
"expo-av": "~13.10.4",
"expo-image": "1.11.0",
@@ -207,7 +207,7 @@
"electron-builder": "24.13.2",
"eslint": "^7.6.0",
"eslint-config-airbnb-typescript": "^17.1.0",
- "eslint-config-expensify": "^2.0.44",
+ "eslint-config-expensify": "^2.0.47",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jest": "^24.1.0",
@@ -3570,9 +3570,9 @@
}
},
"node_modules/@expensify/react-native-live-markdown": {
- "version": "0.1.47",
- "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.47.tgz",
- "integrity": "sha512-zUfwgg6qq47MnGuynamDpdHSlBYwVKFV4Zc/2wlVzFcBndQOjOyFu04Ns8YDB4Gl80LyGvfAuBT/sU+kvmMU6g==",
+ "version": "0.1.62",
+ "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.62.tgz",
+ "integrity": "sha512-o70/tFIGZJ1U8U8aqTQu1HAZed6nt5LYWk74mrceRxQHOqsKhZgn2q5EuEy8EMIcnCGKjwxuDyZJbuRexgHx/A==",
"engines": {
"node": ">= 18.0.0"
},
@@ -16462,10 +16462,8 @@
},
"node_modules/classnames": {
"version": "2.5.0",
- "license": "MIT",
- "workspaces": [
- "benchmarks"
- ]
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.0.tgz",
+ "integrity": "sha512-FQuRlyKinxrb5gwJlfVASbSrDlikDJ07426TrfPsdGLvtochowmkbnSFdQGJ2aoXrSetq5KqGV9emvWpy+91xA=="
},
"node_modules/clean-css": {
"version": "5.3.2",
@@ -16551,7 +16549,8 @@
},
"node_modules/clipboard": {
"version": "2.0.11",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz",
+ "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
"dependencies": {
"good-listener": "^1.2.2",
"select": "^1.1.2",
@@ -18058,7 +18057,8 @@
},
"node_modules/delegate": {
"version": "3.2.0",
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
+ "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
},
"node_modules/delegates": {
"version": "1.0.0",
@@ -19250,9 +19250,10 @@
}
},
"node_modules/eslint-config-expensify": {
- "version": "2.0.44",
+ "version": "2.0.48",
+ "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.48.tgz",
+ "integrity": "sha512-PFegJ9Wfsiu5tgevhjA1toCxsZ8Etfk6pIjtXAnwpmVj7q4CtB3QDRusJoUDyJ3HrZr8AsFKViz7CU/CBTfwOw==",
"dev": true,
- "license": "ISC",
"dependencies": {
"@lwc/eslint-plugin-lwc": "^1.7.2",
"babel-eslint": "^10.1.0",
@@ -20212,8 +20213,8 @@
},
"node_modules/expensify-common": {
"version": "1.0.0",
- "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#13de5b0606662df33fa1392ad82cc11daadff52e",
- "integrity": "sha512-/NAZoAXqeqFWHvC61dueqq9VjRugF69urUtDdDhsfvu1sQE2PCnBoM7a+ACoAEWRYrnP82cyHHhdSA8e7fPuAg==",
+ "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#c0f7f3b6558fbeda0527c80d68460d418afef219",
+ "integrity": "sha512-zz0/y0apISP1orxXEQOgn+Uod45O4wVypwwtaqcDPV4dH1tC3i4L98NoLSZvLn7Y17EcceSkfN6QCEsscgFTDQ==",
"license": "MIT",
"dependencies": {
"classnames": "2.5.0",
@@ -20266,6 +20267,8 @@
},
"node_modules/expensify-common/node_modules/ua-parser-js": {
"version": "1.0.37",
+ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz",
+ "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==",
"funding": [
{
"type": "opencollective",
@@ -20280,7 +20283,6 @@
"url": "https://github.com/sponsors/faisalman"
}
],
- "license": "MIT",
"engines": {
"node": "*"
}
@@ -21728,7 +21730,8 @@
},
"node_modules/good-listener": {
"version": "1.2.2",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
+ "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
"dependencies": {
"delegate": "^3.1.2"
}
@@ -22779,7 +22782,8 @@
},
"node_modules/immediate": {
"version": "3.0.6",
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
},
"node_modules/import-fresh": {
"version": "3.3.0",
@@ -26838,7 +26842,8 @@
},
"node_modules/lie": {
"version": "3.1.1",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
+ "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
"dependencies": {
"immediate": "~3.0.5"
}
@@ -26981,7 +26986,8 @@
},
"node_modules/localforage": {
"version": "1.10.0",
- "license": "Apache-2.0",
+ "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
+ "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
"dependencies": {
"lie": "3.1.1"
}
@@ -33311,7 +33317,8 @@
},
"node_modules/select": {
"version": "1.1.2",
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
+ "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA=="
},
"node_modules/select-hose": {
"version": "2.0.0",
diff --git a/package.json b/package.json
index 43a3ed8cae6a..20d066eabebe 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.4.62-10",
+ "version": "1.4.63-0",
"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.",
@@ -64,7 +64,7 @@
"@babel/plugin-proposal-private-methods": "^7.18.6",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@dotlottie/react-player": "^1.6.3",
- "@expensify/react-native-live-markdown": "0.1.47",
+ "@expensify/react-native-live-markdown": "0.1.62",
"@expo/metro-runtime": "~3.1.1",
"@formatjs/intl-datetimeformat": "^6.10.0",
"@formatjs/intl-listformat": "^7.2.2",
@@ -108,7 +108,7 @@
"date-fns-tz": "^2.0.0",
"dom-serializer": "^0.2.2",
"domhandler": "^4.3.0",
- "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#13de5b0606662df33fa1392ad82cc11daadff52e",
+ "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#c0f7f3b6558fbeda0527c80d68460d418afef219",
"expo": "^50.0.3",
"expo-av": "~13.10.4",
"expo-image": "1.11.0",
@@ -258,7 +258,7 @@
"electron-builder": "24.13.2",
"eslint": "^7.6.0",
"eslint-config-airbnb-typescript": "^17.1.0",
- "eslint-config-expensify": "^2.0.44",
+ "eslint-config-expensify": "^2.0.47",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jest": "^24.1.0",
diff --git a/src/CONFIG.ts b/src/CONFIG.ts
index 76ea18d37d5f..9ed4242d7604 100644
--- a/src/CONFIG.ts
+++ b/src/CONFIG.ts
@@ -48,6 +48,7 @@ export default {
EXPENSIFY: {
// Note: This will be EXACTLY what is set for EXPENSIFY_URL whether the proxy is enabled or not.
EXPENSIFY_URL: expensifyURL,
+ SECURE_EXPENSIFY_URL: secureExpensifyUrl,
NEW_EXPENSIFY_URL: newExpensifyURL,
// The DEFAULT API is the API used by most environments, except staging, where we use STAGING (defined below)
@@ -72,7 +73,7 @@ export default {
IS_USING_LOCAL_WEB: useNgrok || expensifyURLRoot.includes('dev'),
PUSHER: {
APP_KEY: get(Config, 'PUSHER_APP_KEY', '268df511a204fbb60884'),
- SUFFIX: get(Config, 'PUSHER_DEV_SUFFIX', ''),
+ SUFFIX: ENVIRONMENT === CONST.ENVIRONMENT.DEV ? get(Config, 'PUSHER_DEV_SUFFIX', '') : '',
CLUSTER: 'mt1',
},
SITE_TITLE: 'New Expensify',
diff --git a/src/CONST.ts b/src/CONST.ts
index 74e722cdba59..a6df33987c8d 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -633,9 +633,10 @@ const CONST = {
LIMIT: 50,
// OldDot Actions render getMessage from Web-Expensify/lib/Report/Action PHP files via getMessageOfOldDotReportAction in ReportActionsUtils.ts
TYPE: {
+ ACTIONABLEJOINREQUEST: 'ACTIONABLEJOINREQUEST',
ACTIONABLEMENTIONWHISPER: 'ACTIONABLEMENTIONWHISPER',
+ ACTIONABLETRACKEXPENSEWHISPER: 'ACTIONABLETRACKEXPENSEWHISPER',
ADDCOMMENT: 'ADDCOMMENT',
- ACTIONABLEJOINREQUEST: 'ACTIONABLEJOINREQUEST',
APPROVED: 'APPROVED',
CHANGEFIELD: 'CHANGEFIELD', // OldDot Action
CHANGEPOLICY: 'CHANGEPOLICY', // OldDot Action
@@ -768,6 +769,9 @@ const CONST = {
INVITE: 'invited',
NOTHING: 'nothing',
},
+ ACTIONABLE_TRACK_EXPENSE_WHISPER_RESOLUTION: {
+ NOTHING: 'nothing',
+ },
ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION: {
ACCEPT: 'accept',
DECLINE: 'decline',
@@ -875,7 +879,7 @@ const CONST = {
},
TIMING: {
CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION: 'calc_most_recent_last_modified_action',
- SEARCH_RENDER: 'search_render',
+ CHAT_FINDER_RENDER: 'search_render',
CHAT_RENDER: 'chat_render',
OPEN_REPORT: 'open_report',
HOMEPAGE_INITIAL_RENDER: 'homepage_initial_render',
@@ -1211,9 +1215,9 @@ const CONST = {
NOT_IMPORTED: 'NOT_IMPORTED',
IMPORTED: 'IMPORTED',
},
- QUICK_BOOKS_ONLINE: 'quickbooksOnline',
+ QUICKBOOKS_ONLINE: 'quickbooksOnline',
- QUICK_BOOKS_IMPORTS: {
+ QUICKBOOKS_IMPORTS: {
SYNC_CLASSES: 'syncClasses',
ENABLE_NEW_CATEGORIES: 'enableNewCategories',
SYNC_CUSTOMERS: 'syncCustomers',
@@ -1359,7 +1363,7 @@ const CONST = {
},
KYC_WALL_SOURCE: {
- REPORT: 'REPORT', // The user attempted to pay a money request
+ REPORT: 'REPORT', // The user attempted to pay an expense
ENABLE_WALLET: 'ENABLE_WALLET', // The user clicked on the `Enable wallet` button on the Wallet page
TRANSFER_BALANCE: 'TRANSFER_BALANCE', // The user attempted to transfer their wallet balance to their bank account or debit card
},
@@ -1395,7 +1399,7 @@ const CONST = {
},
IOU: {
- // This is the transactionID used when going through the create money request flow so that it mimics a real transaction (like the edit flow)
+ // This is the transactionID used when going through the create expense flow so that it mimics a real transaction (like the edit flow)
OPTIMISTIC_TRANSACTION_ID: '1',
// Note: These payment types are used when building IOU reportAction message values in the server and should
// not be changed.
@@ -1407,6 +1411,9 @@ const CONST = {
ACTION: {
EDIT: 'edit',
CREATE: 'create',
+ MOVE: 'move',
+ CATEGORIZE: 'categorize',
+ SHARE: 'share',
},
DEFAULT_AMOUNT: 0,
TYPE: {
@@ -1429,6 +1436,7 @@ const CONST = {
DELETE: 'delete',
APPROVE: 'approve',
TRACK: 'track',
+ MOVE: 'move',
},
AMOUNT_MAX_LENGTH: 10,
RECEIPT_STATE: {
@@ -1448,6 +1456,11 @@ const CONST = {
CANCEL_REASON: {
PAYMENT_EXPIRED: 'CANCEL_REASON_PAYMENT_EXPIRED',
},
+ SHARE: {
+ ROLE: {
+ ACCOUNTANT: 'accountant',
+ },
+ },
},
GROWL: {
@@ -1601,25 +1614,23 @@ const CONST = {
GENERAL_SETTINGS: 'generalSettings',
},
CONNECTIONS: {
- SYNC_STATUS: {
- STARTING: 'starting',
- FINISHED: 'finished',
- PROGRESS: 'progress',
- },
NAME: {
// Here we will add other connections names when we add support for them
QBO: 'quickbooksOnline',
},
SYNC_STAGE_NAME: {
STARTING_IMPORT: 'startingImport',
- QBO_CUSTOMERS: 'quickbooksOnlineImportCustomers',
- QBO_EMPLOYEES: 'quickbooksOnlineImportEmployees',
- QBO_ACCOUNTS: 'quickbooksOnlineImportAccounts',
- QBO_CLASSES: 'quickbooksOnlineImportClasses',
- QBO_LOCATIONS: 'quickbooksOnlineImportLocations',
- QBO_PROCESSING: 'quickbooksOnlineImportProcessing',
- QBO_PAYMENTS: 'quickbooksOnlineSyncBillPayments',
- QBO_TAX_CODES: 'quickbooksOnlineSyncTaxCodes',
+ QBO_IMPORT_MAIN: 'quickbooksOnlineImportMain',
+ QBO_IMPORT_CUSTOMERS: 'quickbooksOnlineImportCustomers',
+ QBO_IMPORT_EMPLOYEES: 'quickbooksOnlineImportEmployees',
+ QBO_IMPORT_ACCOUNTS: 'quickbooksOnlineImportAccounts',
+ QBO_IMPORT_CLASSES: 'quickbooksOnlineImportClasses',
+ QBO_IMPORT_LOCATIONS: 'quickbooksOnlineImportLocations',
+ QBO_IMPORT_PROCESSING: 'quickbooksOnlineImportProcessing',
+ QBO_SYNC_PAYMENTS: 'quickbooksOnlineSyncBillPayments',
+ QBO_IMPORT_TAX_CODES: 'quickbooksOnlineSyncTaxCodes',
+ QBO_CHECK_CONNECTION: 'quickbooksOnlineCheckConnection',
+ JOB_DONE: 'jobDone',
},
},
},
@@ -3538,12 +3549,11 @@ const CONST = {
ONBOARDING_CONCIERGE: {
[onboardingChoices.TRACK]:
- "# Welcome to Expensify, let's start tracking your expenses!\n" +
- "Hi there, I'm Concierge. Chat with me here for anything you need.\n" +
+ "# Let's start tracking your expenses!\n" +
'\n' +
"To track your expenses, create a workspace to keep everything in one place. Here's how:\n" +
'1. From the home screen, click the green + button > New Workspace\n' +
- '2. Give your workspace a name (e.g. "My business expenses”).\n' +
+ '2. Give your workspace a name (e.g. "My business expenses").\n' +
'\n' +
'Then, add expenses to your workspace:\n' +
'1. Find your workspace using the search field.\n' +
@@ -3552,8 +3562,7 @@ const CONST = {
'\n' +
"We'll store all expenses in your new workspace for easy access. Let me know if you have any questions!",
[onboardingChoices.EMPLOYER]:
- '# Welcome to Expensify, the fastest way to get paid back!\n' +
- "Hi there, I'm Concierge. Chat with me here for anything you need.\n" +
+ '# Expensify is the fastest way to get paid back!\n' +
'\n' +
'To submit expenses for reimbursement:\n' +
'1. From the home screen, click the green + button > Request money.\n' +
@@ -3561,21 +3570,19 @@ const CONST = {
'\n' +
"That'll send a request to get you paid back. Let me know if you have any questions!",
[onboardingChoices.MANAGE_TEAM]:
- "# Welcome to Expensify, let's start managing your team's expenses!\n" +
- "Hi there, I'm Concierge. Chat with me here for anything you need.\n" +
+ "# Let's start managing your team's expenses!\n" +
'\n' +
"To manage your team's expenses, create a workspace to keep everything in one place. Here's how:\n" +
'1. From the home screen, click the green + button > New Workspace\n' +
- '2. Give your workspace a name (e.g. “Sales team expenses”).\n' +
+ '2. Give your workspace a name (e.g. "Sales team expenses").\n' +
'\n' +
- 'Then, invite your team to your workspace via the Members pane and connect a business bank account to reimburse them. Let me know if you have any questions!',
+ 'Then, invite your team to your workspace via the Members pane and [connect a business bank account](https://help.expensify.com/articles/new-expensify/bank-accounts/Connect-a-Bank-Account) to reimburse them. Let me know if you have any questions!',
[onboardingChoices.PERSONAL_SPEND]:
- "# Welcome to Expensify, let's start tracking your expenses!\n" +
- "Hi there, I'm Concierge. Chat with me here for anything you need.\n" +
+ "# Let's start tracking your expenses! \n" +
'\n' +
"To track your expenses, create a workspace to keep everything in one place. Here's how:\n" +
'1. From the home screen, click the green + button > New Workspace\n' +
- '2. Give your workspace a name (e.g. "My expenses”).\n' +
+ '2. Give your workspace a name (e.g. "My expenses").\n' +
'\n' +
'Then, add expenses to your workspace:\n' +
'1. Find your workspace using the search field.\n' +
@@ -3584,19 +3591,13 @@ const CONST = {
'\n' +
"We'll store all expenses in your new workspace for easy access. Let me know if you have any questions!",
[onboardingChoices.CHAT_SPLIT]:
- '# Welcome to Expensify, where splitting the bill is an easy conversation!\n' +
- "Hi there, I'm Concierge. Chat with me here for anything you need.\n" +
+ '# Splitting the bill is as easy as a conversation!\n' +
'\n' +
'To split an expense:\n' +
'1. From the home screen, click the green + button > Request money.\n' +
'2. Enter an amount or scan a receipt, then choose who you want to split it with.\n' +
'\n' +
"We'll send a request to each person so they can pay you back. Let me know if you have any questions!",
- [onboardingChoices.LOOKING_AROUND]:
- '# Welcome to Expensify!\n' +
- "Hi there, I'm Concierge. Chat with me here for anything you need.\n" +
- '\n' +
- "Expensify is best known for expense and corporate card management, but we do a lot more than that. Let me know what you're interested in and I'll help get you started.",
},
REPORT_FIELD_TITLE_FIELD_ID: 'text_title',
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 3959f76a626f..7c9247bcdbd7 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -126,7 +126,7 @@ const ONYXKEYS = {
/** The NVP with the last payment method used per policy */
NVP_LAST_PAYMENT_METHOD: 'nvp_private_lastPaymentMethod',
- /** This NVP holds to most recent waypoints that a person has used when creating a distance request */
+ /** This NVP holds to most recent waypoints that a person has used when creating a distance expense */
NVP_RECENT_WAYPOINTS: 'expensify_recentWaypoints',
/** This NVP will be `true` if the user has ever dismissed the engagement modal on either OldDot or NewDot. If it becomes true it should stay true forever. */
@@ -312,15 +312,19 @@ const ONYXKEYS = {
COLLECTION: {
DOWNLOAD: 'download_',
POLICY: 'policy_',
- POLICY_MEMBERS: 'policyMembers_',
POLICY_DRAFTS: 'policyDrafts_',
- POLICY_MEMBERS_DRAFTS: 'policyMembersDrafts_',
POLICY_JOIN_MEMBER: 'policyJoinMember_',
POLICY_CATEGORIES: 'policyCategories_',
POLICY_RECENTLY_USED_CATEGORIES: 'policyRecentlyUsedCategories_',
POLICY_TAGS: 'policyTags_',
POLICY_RECENTLY_USED_TAGS: 'nvp_recentlyUsedTags_',
+ // Whether the policy's connection data was attempted to be fetched in
+ // the current user session. As this state only exists client-side, it
+ // should not be included as part of the policy object. The policy
+ // object should mirror the data as it's stored in the database.
+ POLICY_HAS_CONNECTIONS_DATA_BEEN_FETCHED: 'policyHasConnectionsDataBeenFetched_',
OLD_POLICY_RECENTLY_USED_TAGS: 'policyRecentlyUsedTags_',
+ POLICY_CONNECTION_SYNC_PROGRESS: 'policyConnectionSyncProgress_',
WORKSPACE_INVITE_MEMBERS_DRAFT: 'workspaceInviteMembersDraft_',
WORKSPACE_INVITE_MESSAGE_DRAFT: 'workspaceInviteMessageDraft_',
REPORT: 'report_',
@@ -347,13 +351,11 @@ const ONYXKEYS = {
PRIVATE_NOTES_DRAFT: 'privateNotesDraft_',
NEXT_STEP: 'reportNextStep_',
- // Manual request tab selector
+ // Manual expense tab selector
SELECTED_TAB: 'selectedTab_',
/** This is deprecated, but needed for a migration, so we still need to include it here so that it will be initialized in Onyx.init */
DEPRECATED_POLICY_MEMBER_LIST: 'policyMemberList_',
-
- POLICY_CONNECTION_SYNC_PROGRESS: 'policyConnectionSyncProgress_',
},
/** List of Form ids */
@@ -526,10 +528,9 @@ type OnyxCollectionValuesMapping = {
[ONYXKEYS.COLLECTION.POLICY_DRAFTS]: OnyxTypes.Policy;
[ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategories;
[ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTagList;
- [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMembers;
- [ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS]: OnyxTypes.PolicyMember;
[ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories;
- [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMembers;
+ [ONYXKEYS.COLLECTION.POLICY_HAS_CONNECTIONS_DATA_BEEN_FETCHED]: boolean;
+ [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyEmployeeList;
[ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: OnyxTypes.InvitedEmailsToAccountIDs;
[ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT]: string;
[ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report;
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index df5c510ca954..7d73d8e55503 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -28,7 +28,7 @@ const ROUTES = {
route: 'flag/:reportID/:reportActionID',
getRoute: (reportID: string, reportActionID: string) => `flag/${reportID}/${reportActionID}` as const,
},
- SEARCH: 'search',
+ CHAT_FINDER: 'chat-finder',
DETAILS: {
route: 'details',
getRoute: (login: string) => `details?login=${encodeURIComponent(login)}` as const,
@@ -301,8 +301,8 @@ const ROUTES = {
getRoute: (reportID: string) => `r/${reportID}/members` as const,
},
ROOM_INVITE: {
- route: 'r/:reportID/invite',
- getRoute: (reportID: string) => `r/${reportID}/invite` as const,
+ route: 'r/:reportID/invite/:role?',
+ getRoute: (reportID: string, role?: string) => `r/${reportID}/invite/${role}` as const,
},
MONEY_REQUEST_PARTICIPANTS: {
route: ':iouType/new/participants/:reportID?',
@@ -376,9 +376,9 @@ const ROUTES = {
getUrlWithBackToParam(`${action}/${iouType}/merchant/${transactionID}/${reportID}`, backTo),
},
MONEY_REQUEST_STEP_PARTICIPANTS: {
- route: 'create/:iouType/participants/:transactionID/:reportID',
- getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') =>
- getUrlWithBackToParam(`create/${iouType}/participants/${transactionID}/${reportID}`, backTo),
+ route: ':action/:iouType/participants/:transactionID/:reportID',
+ getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '', action: ValueOf = 'create') =>
+ getUrlWithBackToParam(`${action}/${iouType}/participants/${transactionID}/${reportID}`, backTo),
},
MONEY_REQUEST_STEP_SCAN: {
route: ':action/:iouType/scan/:transactionID/:reportID',
@@ -563,7 +563,7 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/members',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/members` as const,
},
- WORKSPACE_ACCOUNTING: {
+ POLICY_ACCOUNTING: {
route: 'settings/workspaces/:policyID/accounting',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting` as const,
},
@@ -707,27 +707,27 @@ const ROUTES = {
route: 'r/:reportID/transaction/:transactionID/receipt',
getRoute: (reportID: string, transactionID: string) => `r/${reportID}/transaction/${transactionID}/receipt` as const,
},
- WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_IMPORT: {
+ POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_IMPORT: {
route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import` as const,
},
- WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS: {
+ POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS: {
route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/accounts',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/accounts` as const,
},
- WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CLASSES: {
+ POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_CLASSES: {
route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/classes',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/classes` as const,
},
- WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CUSTOMERS: {
+ POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_CUSTOMERS: {
route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/customers',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/customers` as const,
},
- WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_LOCATIONS: {
+ POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_LOCATIONS: {
route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/locations',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/locations` as const,
},
- WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_TAXES: {
+ POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_TAXES: {
route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/taxes',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/taxes` as const,
},
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 96372d5bbabb..d474945d332e 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -95,7 +95,7 @@ const SCREENS = {
ROOT: 'SaveTheWorld_Root',
},
LEFT_MODAL: {
- SEARCH: 'Search',
+ CHAT_FINDER: 'ChatFinder',
WORKSPACE_SWITCHER: 'WorkspaceSwitcher',
},
WORKSPACE_SWITCHER: {
@@ -203,6 +203,15 @@ const SCREENS = {
},
WORKSPACE: {
+ ACCOUNTING: {
+ ROOT: 'Policy_Accounting',
+ QUICKBOOKS_ONLINE_IMPORT: 'Policy_Accounting_Quickbooks_Online_Import',
+ QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS: 'Policy_Accounting_Quickbooks_Online_Import_Chart_Of_Accounts',
+ QUICKBOOKS_ONLINE_CLASSES: 'Policy_Accounting_Quickbooks_Online_Import_Classes',
+ QUICKBOOKS_ONLINE_CUSTOMERS: 'Policy_Accounting_Quickbooks_Online_Import_Customers',
+ QUICKBOOKS_ONLINE_LOCATIONS: 'Policy_Accounting_Quickbooks_Online_Import_Locations',
+ QUICKBOOKS_ONLINE_TAXES: 'Policy_Accounting_Quickbooks_Online_Import_Taxes',
+ },
INITIAL: 'Workspace_Initial',
PROFILE: 'Workspace_Profile',
CARD: 'Workspace_Card',
@@ -214,7 +223,6 @@ const SCREENS = {
INVOICES: 'Workspace_Invoices',
TRAVEL: 'Workspace_Travel',
MEMBERS: 'Workspace_Members',
- ACCOUNTING: 'Workspace_Accounting',
INVITE: 'Workspace_Invite',
INVITE_MESSAGE: 'Workspace_Invite_Message',
CATEGORIES: 'Workspace_Categories',
@@ -254,12 +262,6 @@ const SCREENS = {
DISTANCE_RATES: 'Distance_Rates',
CREATE_DISTANCE_RATE: 'Create_Distance_Rate',
DISTANCE_RATES_SETTINGS: 'Distance_Rates_Settings',
- QUICKBOOKS_ONLINE_IMPORT: 'Workspace_Accounting_Quickbooks_Online_Import',
- QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS: 'Workspace_Accounting_Quickbooks_Online_Import_Chart_Of_Accounts',
- QUICKBOOKS_ONLINE_CLASSES: 'Workspace_Accounting_Quickbooks_Online_Import_Classes',
- QUICKBOOKS_ONLINE_CUSTOMERS: 'Workspace_Accounting_Quickbooks_Online_Import_Customers',
- QUICKBOOKS_ONLINE_LOCATIONS: 'Workspace_Accounting_Quickbooks_Online_Import_Locations',
- QUICKBOOKS_ONLINE_TAXES: 'Workspace_Accounting_Quickbooks_Online_Import_Taxes',
DISTANCE_RATE_DETAILS: 'Distance_Rate_Details',
DISTANCE_RATE_EDIT: 'Distance_Rate_Edit',
},
@@ -319,7 +321,7 @@ const SCREENS = {
},
ROOM_MEMBERS_ROOT: 'RoomMembers_Root',
ROOM_INVITE_ROOT: 'RoomInvite_Root',
- SEARCH_ROOT: 'Search_Root',
+ CHAT_FINDER_ROOT: 'ChatFinder_Root',
FLAG_COMMENT_ROOT: 'FlagComment_Root',
REIMBURSEMENT_ACCOUNT: 'ReimbursementAccount',
GET_ASSISTANCE: 'GetAssistance',
diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx
index f6afb4dae2d6..c7a4ece0de97 100644
--- a/src/components/AvatarWithDisplayName.tsx
+++ b/src/components/AvatarWithDisplayName.tsx
@@ -65,7 +65,6 @@ function AvatarWithDisplayName({
const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(report?.ownerAccountID ? [report.ownerAccountID] : [], personalDetails);
const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(Object.values(ownerPersonalDetails) as PersonalDetails[], false);
const shouldShowSubscriptAvatar = ReportUtils.shouldReportShowSubscript(report);
- const isExpenseRequest = ReportUtils.isExpenseRequest(report);
const avatarBorderColor = isAnonymous ? theme.highlightBG : theme.componentBG;
const actorAccountID = useRef(null);
@@ -128,7 +127,7 @@ function AvatarWithDisplayName({
/>
)}
-
+
;
};
diff --git a/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx b/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx
new file mode 100644
index 000000000000..4d482cb92ead
--- /dev/null
+++ b/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx
@@ -0,0 +1,68 @@
+import React, {useState} from 'react';
+import type {OnyxEntry} from 'react-native-onyx';
+import {withOnyx} from 'react-native-onyx';
+import {WebView} from 'react-native-webview';
+import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView';
+import Button from '@components/Button';
+import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import Modal from '@components/Modal';
+import useLocalize from '@hooks/useLocalize';
+import {getQuickBooksOnlineSetupLink} from '@libs/actions/connections/QuickBooksOnline';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Session} from '@src/types/onyx';
+import type {ConnectToQuickbooksOnlineButtonProps} from './types';
+
+type ConnectToQuickbooksOnlineButtonOnyxProps = {
+ /** Session info for the currently logged in user. */
+ session: OnyxEntry;
+};
+
+const renderLoading = () => ;
+
+function ConnectToQuickbooksOnlineButton({policyID, session}: ConnectToQuickbooksOnlineButtonProps & ConnectToQuickbooksOnlineButtonOnyxProps) {
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const {translate} = useLocalize();
+
+ return (
+ <>
+
);
}
diff --git a/src/components/Indicator.tsx b/src/components/Indicator.tsx
index e3d226a17999..8830681bc55f 100644
--- a/src/components/Indicator.tsx
+++ b/src/components/Indicator.tsx
@@ -8,14 +8,11 @@ import * as PolicyUtils from '@libs/PolicyUtils';
import * as UserUtils from '@libs/UserUtils';
import * as PaymentMethods from '@userActions/PaymentMethods';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {BankAccountList, FundList, LoginList, Policy, PolicyMembers, ReimbursementAccount, UserWallet, WalletTerms} from '@src/types/onyx';
+import type {BankAccountList, FundList, LoginList, Policy, ReimbursementAccount, UserWallet, WalletTerms} from '@src/types/onyx';
type CheckingMethod = () => boolean;
type IndicatorOnyxProps = {
- /** The employee list of all policies (coming from Onyx) */
- allPolicyMembers: OnyxCollection;
-
/** All the user's policies (from Onyx via withFullPolicy) */
policies: OnyxCollection;
@@ -40,14 +37,13 @@ type IndicatorOnyxProps = {
type IndicatorProps = IndicatorOnyxProps;
-function Indicator({reimbursementAccount, allPolicyMembers, policies, bankAccountList, fundList, userWallet, walletTerms, loginList}: IndicatorOnyxProps) {
+function Indicator({reimbursementAccount, policies, bankAccountList, fundList, userWallet, walletTerms, loginList}: IndicatorOnyxProps) {
const theme = useTheme();
const styles = useThemeStyles();
// If a policy was just deleted from Onyx, then Onyx will pass a null value to the props, and
// those should be cleaned out before doing any error checking
const cleanPolicies = Object.fromEntries(Object.entries(policies ?? {}).filter(([, policy]) => policy?.id));
- const cleanAllPolicyMembers = Object.fromEntries(Object.entries(allPolicyMembers ?? {}).filter(([, policyMembers]) => !!policyMembers));
// All of the error & info-checking methods are put into an array. This is so that using _.some() will return
// early as soon as the first error / info condition is returned. This makes the checks very efficient since
@@ -57,7 +53,7 @@ function Indicator({reimbursementAccount, allPolicyMembers, policies, bankAccoun
() => PaymentMethods.hasPaymentMethodError(bankAccountList, fundList),
() => Object.values(cleanPolicies).some(PolicyUtils.hasPolicyError),
() => Object.values(cleanPolicies).some(PolicyUtils.hasCustomUnitsError),
- () => Object.values(cleanAllPolicyMembers).some(PolicyUtils.hasPolicyMemberError),
+ () => Object.values(cleanPolicies).some(PolicyUtils.hasEmployeeListError),
() => Object.keys(reimbursementAccount?.errors ?? {}).length > 0,
() => !!loginList && UserUtils.hasLoginListError(loginList),
@@ -77,9 +73,6 @@ function Indicator({reimbursementAccount, allPolicyMembers, policies, bankAccoun
Indicator.displayName = 'Indicator';
export default withOnyx({
- allPolicyMembers: {
- key: ONYXKEYS.COLLECTION.POLICY_MEMBERS,
- },
policies: {
key: ONYXKEYS.COLLECTION.POLICY,
},
diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index 14227d6a2051..56dc6bf0075d 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -43,7 +43,7 @@ type MoneyReportHeaderProps = MoneyReportHeaderOnyxProps & {
/** The report currently being looked at */
report: OnyxTypes.Report;
- /** The policy tied to the money request report */
+ /** The policy tied to the expense report */
policy: OnyxEntry;
/** Array of report actions for the report */
@@ -288,7 +288,7 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money
danger
/>
setIsDeleteRequestModalVisible(false)}
diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx
index aa6c75edbf5d..4b3e4096484f 100755
--- a/src/components/MoneyRequestConfirmationList.tsx
+++ b/src/components/MoneyRequestConfirmationList.tsx
@@ -342,12 +342,12 @@ function MoneyRequestConfirmationList({
if (isSplitBill && iouAmount === 0) {
text = translate('iou.split');
} else if ((!!receiptPath && isTypeRequest) || isDistanceRequestWithPendingRoute) {
- text = translate('iou.request');
+ text = translate('iou.expense');
if (iouAmount !== 0) {
- text = translate('iou.requestAmount', {amount: formattedAmount});
+ text = translate('iou.submitAmount', {amount: formattedAmount});
}
} else {
- const translationKey = isSplitBill ? 'iou.splitAmount' : 'iou.requestAmount';
+ const translationKey = isSplitBill ? 'iou.splitAmount' : 'iou.submitAmount';
text = translate(translationKey, {amount: formattedAmount});
}
return [
diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx
index f451f5f15581..f7825ef2f622 100644
--- a/src/components/MoneyRequestHeader.tsx
+++ b/src/components/MoneyRequestHeader.tsx
@@ -63,7 +63,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction,
const isOnHold = TransactionUtils.isOnHold(transaction);
const {isSmallScreenWidth, windowWidth} = useWindowDimensions();
- // Only the requestor can take delete the request, admins can only edit it.
+ // Only the requestor can take delete the expense, admins can only edit it.
const isActionOwner = typeof parentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && parentReportAction.actorAccountID === session?.accountID;
const isPolicyAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN;
const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && (session?.accountID ?? null) === moneyRequestReport?.managerID;
@@ -118,14 +118,14 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction,
if (isOnHold && (isHoldCreator || (!isRequestIOU && canModifyStatus))) {
threeDotsMenuItems.push({
icon: Expensicons.Stopwatch,
- text: translate('iou.unholdRequest'),
+ text: translate('iou.unholdExpense'),
onSelected: () => changeMoneyRequestStatus(),
});
}
if (!isOnHold && (isRequestIOU || canModifyStatus)) {
threeDotsMenuItems.push({
icon: Expensicons.Stopwatch,
- text: translate('iou.holdRequest'),
+ text: translate('iou.holdExpense'),
onSelected: () => changeMoneyRequestStatus(),
});
}
@@ -196,7 +196,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction,
{isOnHold && }
setIsDeleteModalVisible(false)}
diff --git a/src/components/MoneyRequestSkeletonView.tsx b/src/components/MoneyRequestSkeletonView.tsx
index e11e7bcecc07..400e3c9534d7 100644
--- a/src/components/MoneyRequestSkeletonView.tsx
+++ b/src/components/MoneyRequestSkeletonView.tsx
@@ -13,8 +13,8 @@ function MoneyRequestSkeletonView() {
animate
width={styles.w100.width}
height={variables.moneyRequestSkeletonHeight}
- backgroundColor={theme.borderLighter}
- foregroundColor={theme.border}
+ backgroundColor={theme.skeletonLHNIn}
+ foregroundColor={theme.skeletonLHNOut}
>
;
- /** Unit and rate used for if the money request is a distance request */
+ /** Unit and rate used for if the expense is a distance expense */
mileageRate: OnyxEntry;
};
@@ -72,7 +73,7 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps &
/** Callback to inform parent modal of success */
onConfirm?: (selectedParticipants: Participant[]) => void;
- /** Callback to parent modal to send money */
+ /** Callback to parent modal to pay someone */
onSendMoney?: (paymentMethod: PaymentMethodType | undefined) => void;
/** Callback to inform a participant is selected */
@@ -111,7 +112,7 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps &
/** Selected participants from MoneyRequestModal with login / accountID */
selectedParticipants: Participant[];
- /** Payee of the money request with login */
+ /** Payee of the expense with login */
payeePersonalDetails?: OnyxTypes.PersonalDetails;
/** Can the participants be modified or not */
@@ -138,16 +139,16 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps &
/** List styles for OptionsSelector */
listStyles?: StyleProp;
- /** Transaction that represents the money request */
+ /** Transaction that represents the expense */
transaction?: OnyxEntry;
- /** Whether the money request is a distance request */
+ /** Whether the expense is a distance expense */
isDistanceRequest?: boolean;
- /** Whether the money request is a scan request */
+ /** Whether the expense is a scan expense */
isScanRequest?: boolean;
- /** Whether we're editing a split bill */
+ /** Whether we're editing a split expense */
isEditingSplitBill?: boolean;
/** Whether we should show the amount, date, and merchant fields. */
@@ -160,6 +161,8 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps &
hasSmartScanFailed?: boolean;
reportActionID?: string;
+
+ action?: ValueOf;
};
const getTaxAmount = (transaction: OnyxEntry, defaultTaxValue: string) => {
@@ -205,6 +208,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
onToggleBillable,
hasSmartScanFailed,
reportActionID,
+ action = CONST.IOU.ACTION.CREATE,
}: MoneyRequestConfirmationListProps) {
const theme = useTheme();
const styles = useThemeStyles();
@@ -232,7 +236,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
// A flag and a toggler for showing the rest of the form fields
const [shouldExpandFields, toggleShouldExpandFields] = useReducer((state) => !state, false);
- // Do not hide fields in case of send money request
+ // Do not hide fields in case of paying someone
const shouldShowAllFields = isDistanceRequest || shouldExpandFields || !shouldShowSmartScanFields || isTypeSend || isEditingSplitBill;
const shouldShowDate = (shouldShowSmartScanFields || isDistanceRequest) && !isTypeSend;
@@ -248,9 +252,9 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
// A flag for showing the billable field
const shouldShowBillable = policy?.disabledFields?.defaultBillable === false;
-
+ const isMovingTransactionFromTrackExpense = IOUUtils.isMovingTransactionFromTrackExpense(action);
const hasRoute = TransactionUtils.hasRoute(transaction);
- const isDistanceRequestWithPendingRoute = isDistanceRequest && (!hasRoute || !rate);
+ const isDistanceRequestWithPendingRoute = isDistanceRequest && (!hasRoute || !rate) && !isMovingTransactionFromTrackExpense;
const formattedAmount = isDistanceRequestWithPendingRoute
? ''
: CurrencyUtils.convertToDisplayString(
@@ -350,7 +354,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
[iouAmount, iouCurrencyCode],
);
- // If completing a split bill fails, set didConfirm to false to allow the user to edit the fields again
+ // If completing a split expense fails, set didConfirm to false to allow the user to edit the fields again
if (isEditingSplitBill && didConfirm) {
setDidConfirm(false);
}
@@ -360,14 +364,14 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
if (isTypeTrackExpense) {
text = translate('iou.trackExpense');
} else if (isTypeSplit && iouAmount === 0) {
- text = translate('iou.split');
+ text = translate('iou.splitExpense');
} else if ((receiptPath && isTypeRequest) || isDistanceRequestWithPendingRoute) {
- text = translate('iou.request');
+ text = translate('iou.submitExpense');
if (iouAmount !== 0) {
- text = translate('iou.requestAmount', {amount: formattedAmount});
+ text = translate('iou.submitAmount', {amount: formattedAmount});
}
} else {
- const translationKey = isTypeSplit ? 'iou.splitAmount' : 'iou.requestAmount';
+ const translationKey = isTypeSplit ? 'iou.splitAmount' : 'iou.submitAmount';
text = translate(translationKey, {amount: formattedAmount});
}
return [
@@ -452,7 +456,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
}, [selectedParticipants, hasMultipleParticipants, personalDetailsOfPayee]);
useEffect(() => {
- if (!isDistanceRequest) {
+ if (!isDistanceRequest || isMovingTransactionFromTrackExpense) {
return;
}
@@ -465,7 +469,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
const distanceMerchant = DistanceRequestUtils.getDistanceMerchant(hasRoute, distance, unit, rate ?? 0, currency ?? 'USD', translate, toLocaleDigit);
IOU.setMoneyRequestMerchant(transaction?.transactionID ?? '', distanceMerchant, true);
- }, [isDistanceRequestWithPendingRoute, hasRoute, distance, unit, rate, currency, translate, toLocaleDigit, isDistanceRequest, transaction]);
+ }, [isDistanceRequestWithPendingRoute, hasRoute, distance, unit, rate, currency, translate, toLocaleDigit, isDistanceRequest, transaction, action, isMovingTransactionFromTrackExpense]);
// Auto select the category if there is only one enabled category and it is required
useEffect(() => {
@@ -474,7 +478,8 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
return;
}
IOU.setMoneyRequestCategory(transaction?.transactionID ?? '', enabledCategories[0].name);
- }, [iouCategory, shouldShowCategories, policyCategories, transaction, isCategoryRequired]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [shouldShowCategories, policyCategories, isCategoryRequired]);
// Auto select the tag if there is only one enabled tag and it is required
useEffect(() => {
@@ -490,7 +495,8 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
if (updatedTagsString !== TransactionUtils.getTag(transaction) && updatedTagsString) {
IOU.setMoneyRequestTag(transaction?.transactionID ?? '', updatedTagsString);
}
- }, [policyTagLists, transaction, policyTags, canUseViolations]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [policyTagLists, policyTags, canUseViolations]);
/**
*/
@@ -546,7 +552,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
Log.info(`[IOU] Sending money via: ${paymentMethod}`);
onSendMoney?.(paymentMethod);
} else {
- // validate the amount for distance requests
+ // validate the amount for distance expenses
const decimals = CurrencyUtils.getCurrencyDecimals(iouCurrencyCode);
if (isDistanceRequest && !isDistanceRequestWithPendingRoute && !MoneyRequestUtils.validateAmount(String(iouAmount), decimals)) {
setFormError('common.error.invalidAmount');
@@ -656,9 +662,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID ?? '', CONST.EDIT_REQUEST_FIELD.AMOUNT));
return;
}
- Navigation.navigate(
- ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute(CONST.IOU.ACTION.CREATE, iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()),
- );
+ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute(action, iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()));
}}
style={[styles.moneyRequestMenuItem, styles.mt2]}
titleStyle={styles.moneyRequestConfirmationAmount}
@@ -680,13 +684,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
description={translate('common.description')}
onPress={() => {
Navigation.navigate(
- ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(
- CONST.IOU.ACTION.CREATE,
- iouType,
- transaction?.transactionID ?? '',
- reportID,
- Navigation.getActiveRouteWithoutParams(),
- ),
+ ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(action, iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()),
);
}}
style={[styles.moneyRequestMenuItem]}
@@ -703,25 +701,20 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
item: (
Navigation.navigate(
- ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(
- CONST.IOU.ACTION.CREATE,
- iouType,
- transaction?.transactionID ?? '',
- reportID,
- Navigation.getActiveRouteWithoutParams(),
- ),
+ ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(action, iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()),
)
}
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
disabled={didConfirm}
- interactive={!isReadOnly}
+ // todo: handle edit for transaction while moving from track expense
+ interactive={!isReadOnly && !isMovingTransactionFromTrackExpense}
/>
),
shouldShow: isDistanceRequest,
@@ -738,13 +731,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
titleStyle={styles.flex1}
onPress={() => {
Navigation.navigate(
- ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute(
- CONST.IOU.ACTION.CREATE,
- iouType,
- transaction?.transactionID ?? '',
- reportID,
- Navigation.getActiveRouteWithoutParams(),
- ),
+ ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute(action, iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()),
);
}}
disabled={didConfirm}
@@ -768,9 +755,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
onPress={() => {
- Navigation.navigate(
- ROUTES.MONEY_REQUEST_STEP_DATE.getRoute(CONST.IOU.ACTION.CREATE, iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()),
- );
+ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DATE.getRoute(action, iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()));
}}
disabled={didConfirm}
interactive={!isReadOnly}
@@ -791,13 +776,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
numberOfLinesTitle={2}
onPress={() =>
Navigation.navigate(
- ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(
- CONST.IOU.ACTION.CREATE,
- iouType,
- transaction?.transactionID ?? '',
- reportID,
- Navigation.getActiveRouteWithoutParams(),
- ),
+ ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(action, iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()),
)
}
style={[styles.moneyRequestMenuItem]}
@@ -808,7 +787,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
/>
),
shouldShow: shouldShowCategories,
- isSupplementary: !isCategoryRequired,
+ isSupplementary: action === CONST.IOU.ACTION.CATEGORIZE ? false : !isCategoryRequired,
},
...policyTagLists.map(({name, required}, index) => {
const isTagRequired = required === undefined ? false : canUseViolations && required;
@@ -822,14 +801,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
numberOfLinesTitle={2}
onPress={() =>
Navigation.navigate(
- ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(
- CONST.IOU.ACTION.CREATE,
- iouType,
- index,
- transaction?.transactionID ?? '',
- reportID,
- Navigation.getActiveRouteWithoutParams(),
- ),
+ ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(action, iouType, index, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()),
)
}
style={[styles.moneyRequestMenuItem]}
@@ -853,13 +825,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
titleStyle={styles.flex1}
onPress={() =>
Navigation.navigate(
- ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(
- CONST.IOU.ACTION.CREATE,
- iouType,
- transaction?.transactionID ?? '',
- reportID,
- Navigation.getActiveRouteWithoutParams(),
- ),
+ ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(action, iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()),
)
}
disabled={didConfirm}
@@ -880,13 +846,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
titleStyle={styles.flex1}
onPress={() =>
Navigation.navigate(
- ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(
- CONST.IOU.ACTION.CREATE,
- iouType,
- transaction?.transactionID ?? '',
- reportID,
- Navigation.getActiveRouteWithoutParams(),
- ),
+ ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(action, iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams()),
)
}
disabled={didConfirm}
@@ -926,12 +886,15 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
isLocalFile,
} = receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction ?? null, receiptPath, receiptFilename) : ({} as ReceiptUtils.ThumbnailAndImageURI);
+ const resolvedThumbnail = isLocalFile ? receiptThumbnail : tryResolveUrlFromApiRoot(receiptThumbnail ?? '');
+ const resolvedReceiptImage = isLocalFile ? receiptImage : tryResolveUrlFromApiRoot(receiptImage ?? '');
+
const receiptThumbnailContent = useMemo(
() =>
isLocalFile && Str.isPDF(receiptFilename) ? (
),
- [isLocalFile, receiptFilename, receiptImage, styles.moneyRequestImage, isAttachmentInvalid, isThumbnail, receiptThumbnail, fileExtension],
+ [isLocalFile, receiptFilename, resolvedThumbnail, styles.moneyRequestImage, isAttachmentInvalid, isThumbnail, resolvedReceiptImage, receiptThumbnail, fileExtension],
);
return (
@@ -978,9 +941,9 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
)}
- {
+ {(!isMovingTransactionFromTrackExpense || !hasRoute) &&
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- receiptImage || receiptThumbnail
+ (receiptImage || receiptThumbnail
? receiptThumbnailContent
: // The empty receipt component should only show for IOU Requests of a paid policy ("Team" or "Corporate")
PolicyUtils.isPaidGroupPolicy(policy) &&
@@ -999,8 +962,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
)
}
/>
- )
- }
+ ))}
{primaryFields}
{!shouldShowAllFields && (
diff --git a/src/components/OptionsList/BaseOptionsList.tsx b/src/components/OptionsList/BaseOptionsList.tsx
index 436f4c147931..7bbd3e344c3f 100644
--- a/src/components/OptionsList/BaseOptionsList.tsx
+++ b/src/components/OptionsList/BaseOptionsList.tsx
@@ -184,7 +184,7 @@ function BaseOptionsList(
option={item}
showTitleTooltip={showTitleTooltip}
hoverStyle={optionHoveredStyle}
- optionIsFocused={!disableFocusOptions && !isItemDisabled && focusedIndex === index + section.indexOffset}
+ optionIsFocused={!disableFocusOptions && !isItemDisabled && focusedIndex === index + (section.indexOffset ?? 0)}
onSelectRow={onSelectRow}
isSelected={isSelected}
showSelectedState={canSelectMultipleOptions}
diff --git a/src/components/OptionsList/types.ts b/src/components/OptionsList/types.ts
index b7180e6281b4..7f23da965f39 100644
--- a/src/components/OptionsList/types.ts
+++ b/src/components/OptionsList/types.ts
@@ -22,7 +22,7 @@ type Section = {
type SectionWithIndexOffset = Section & {
/** The initial index of this section given the total number of options in each section's data array */
- indexOffset: number;
+ indexOffset?: number;
};
type OptionsListProps = {
diff --git a/src/components/ParentNavigationSubtitle.tsx b/src/components/ParentNavigationSubtitle.tsx
index 0ad32f18659b..cbc9e1352f21 100644
--- a/src/components/ParentNavigationSubtitle.tsx
+++ b/src/components/ParentNavigationSubtitle.tsx
@@ -35,7 +35,12 @@ function ParentNavigationSubtitle({parentNavigationSubtitleData, parentReportAct
onPress={() => {
const parentAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? '');
const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(parentAction, parentAction?.reportActionID ?? '');
- Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(parentReportID, isVisibleAction && !isOffline ? parentReportActionID : undefined));
+ // Pop the thread report screen before navigating to the chat report.
+ Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(parentReportID));
+ if (isVisibleAction && !isOffline) {
+ // Pop the chat report screen before navigating to the linked report action.
+ Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(parentReportID, parentReportActionID));
+ }
}}
accessibilityLabel={translate('threads.parentNavigationSummary', {reportName, workspaceName})}
role={CONST.ROLE.LINK}
diff --git a/src/components/ReportActionItem/ActionableItemButtons.tsx b/src/components/ReportActionItem/ActionableItemButtons.tsx
index 6ead20d3e643..8e5590537f51 100644
--- a/src/components/ReportActionItem/ActionableItemButtons.tsx
+++ b/src/components/ReportActionItem/ActionableItemButtons.tsx
@@ -10,10 +10,12 @@ type ActionableItem = {
key: string;
onPress: () => void;
text: TranslationPaths;
+ isMediumSized?: boolean;
};
type ActionableItemButtonsProps = {
items: ActionableItem[];
+ layout?: 'horizontal' | 'vertical';
};
function ActionableItemButtons(props: ActionableItemButtonsProps) {
@@ -21,14 +23,14 @@ function ActionableItemButtons(props: ActionableItemButtonsProps) {
const {translate} = useLocalize();
return (
-
+
{props.items?.map((item) => (
))}
diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx
index 7d9ba2697c7a..4f91b2084b45 100644
--- a/src/components/ReportActionItem/MoneyRequestAction.tsx
+++ b/src/components/ReportActionItem/MoneyRequestAction.tsx
@@ -37,7 +37,7 @@ type MoneyRequestActionProps = MoneyRequestActionOnyxProps & {
/** The ID of the associated chatReport */
chatReportID: string;
- /** The ID of the associated request report */
+ /** The ID of the associated expense report */
requestReportID: string;
/** The ID of the current report */
@@ -114,10 +114,8 @@ function MoneyRequestAction({
let message: TranslationPaths;
if (isReversedTransaction) {
message = 'parentReportAction.reversedTransaction';
- } else if (isTrackExpenseAction) {
- message = 'parentReportAction.deletedExpense';
} else {
- message = 'parentReportAction.deletedRequest';
+ message = 'parentReportAction.deletedExpense';
}
return ${translate(message)}`} />;
}
diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx
index 97287e64b829..8994d456904a 100644
--- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx
+++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx
@@ -101,7 +101,7 @@ function MoneyRequestPreviewContent({
/*
Show the merchant for IOUs and expenses only if:
- the merchant is not empty, is custom, or is not related to scanning smartscan;
- - the request is not a distance request with a pending route and amount = 0 - in this case,
+ - the expense is not a distance expense with a pending route and amount = 0 - in this case,
the merchant says: "Route pending...", which is already shown in the amount field;
*/
const shouldShowMerchant =
diff --git a/src/components/ReportActionItem/MoneyRequestPreview/types.ts b/src/components/ReportActionItem/MoneyRequestPreview/types.ts
index 3b3eda4ec30a..0e3eb37ce6e3 100644
--- a/src/components/ReportActionItem/MoneyRequestPreview/types.ts
+++ b/src/components/ReportActionItem/MoneyRequestPreview/types.ts
@@ -53,7 +53,7 @@ type MoneyRequestPreviewProps = MoneyRequestPreviewOnyxProps & {
/** Extra styles to pass to View wrapper */
containerStyles?: StyleProp;
- /** True if this is this IOU is a split instead of a 1:1 request */
+ /** True if this IOU has a type of split */
isBillSplit: boolean;
/** Whether this IOU is a track expense */
@@ -62,7 +62,7 @@ type MoneyRequestPreviewProps = MoneyRequestPreviewOnyxProps & {
/** True if the IOU Preview card is hovered */
isHovered?: boolean;
- /** Whether or not an IOU report contains money requests in a different currency
+ /** Whether or not an IOU report contains expenses in a different currency
* that are either created or cancelled offline, and thus haven't been converted to the report's currency yet
*/
shouldShowPendingConversionMessage?: boolean;
diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 73fc7e9bae6e..c5cad0eccdeb 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -132,7 +132,7 @@ function MoneyRequestView({
? transaction && TransactionUtils.getDefaultTaxName(taxRates, transaction)
: transactionTaxCode && TransactionUtils.getTaxName(taxRates?.taxes, transactionTaxCode));
- // Flags for allowing or disallowing editing a money request
+ // Flags for allowing or disallowing editing an expense
const isSettled = ReportUtils.isSettled(moneyRequestReport?.reportID);
const isCancelled = moneyRequestReport && moneyRequestReport.isCancelledIOU;
@@ -220,7 +220,7 @@ function MoneyRequestView({
const getErrorForField = useCallback(
(field: ViolationField, data?: OnyxTypes.TransactionViolation['data']) => {
- // Checks applied when creating a new money request
+ // Checks applied when creating a new expense
// NOTE: receipt field can return multiple violations, so we need to handle it separately
const fieldChecks: Partial> = {
amount: {
diff --git a/src/components/ReportActionItem/ReportActionItemImages.tsx b/src/components/ReportActionItem/ReportActionItemImages.tsx
index ee8cb0849ca0..e2bcce9b9f1b 100644
--- a/src/components/ReportActionItem/ReportActionItemImages.tsx
+++ b/src/components/ReportActionItem/ReportActionItemImages.tsx
@@ -30,7 +30,7 @@ type ReportActionItemImagesProps = {
/**
* This component displays a row of images in a report action item like a card, such
- * as report previews or money request previews which contain receipt images. The maximum of images
+ * as report previews or expense previews which contain receipt images. The maximum of images
* shown in this row is dictated by the size prop, which, if not passed, is just the number of images.
* Otherwise, if size is passed and the number of images is over size, we show a small overlay on the
* last image of how many additional images there are. If passed, total prop can be used to change how this
diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx
index 190343e48abd..d14d2df1bb43 100644
--- a/src/components/ReportActionItem/ReportPreview.tsx
+++ b/src/components/ReportActionItem/ReportPreview.tsx
@@ -36,7 +36,7 @@ import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
import ReportActionItemImages from './ReportActionItemImages';
type ReportPreviewOnyxProps = {
- /** The policy tied to the money request report */
+ /** The policy tied to the expense report */
policy: OnyxEntry;
/** ChatReport associated with iouReport */
@@ -216,11 +216,11 @@ function ReportPreview({
const shouldShowRBR = !iouSettled && hasErrors;
/*
- Show subtitle if at least one of the money requests is not being smart scanned, and either:
- - There is more than one money request – in this case, the "X requests, Y scanning" subtitle is shown;
- - There is only one money request, it has a receipt and is not being smart scanned – in this case, the request merchant or description is shown;
+ Show subtitle if at least one of the expenses is not being smart scanned, and either:
+ - There is more than one expense – in this case, the "X expenses, Y scanning" subtitle is shown;
+ - There is only one expense, it has a receipt and is not being smart scanned – in this case, the expense merchant or description is shown;
- * There is an edge case when there is only one distance request with a pending route and amount = 0.
+ * There is an edge case when there is only one distance expense with a pending route and amount = 0.
In this case, we don't want to show the merchant or description because it says: "Pending route...", which is already displayed in the amount field.
*/
const shouldShowSingleRequestMerchantOrDescription =
@@ -237,7 +237,7 @@ function ReportPreview({
}
return {
isSupportTextHtml: false,
- supportText: translate('iou.requestCount', {
+ supportText: translate('iou.expenseCount', {
count: numberOfRequests - numberOfScanningReceipts - numberOfPendingRequests,
scanningReceipts: numberOfScanningReceipts,
pendingReceipts: numberOfPendingRequests,
diff --git a/src/components/ReportActionItem/TaskAction.tsx b/src/components/ReportActionItem/TaskAction.tsx
index e85a2e708feb..e1b36713592f 100644
--- a/src/components/ReportActionItem/TaskAction.tsx
+++ b/src/components/ReportActionItem/TaskAction.tsx
@@ -1,3 +1,4 @@
+import ExpensiMark from 'expensify-common/lib/ExpensiMark';
import React from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
@@ -15,10 +16,15 @@ type TaskActionProps = {
function TaskAction({action}: TaskActionProps) {
const styles = useThemeStyles();
const message = TaskUtils.getTaskReportActionMessage(action);
+ const parser = new ExpensiMark();
return (
- {message.html ? ${message.html}`} /> : {message.text}}
+ {message.html ? (
+ ${parser.replace(message.html)}`} />
+ ) : (
+ {message.text}
+ )}
);
}
diff --git a/src/components/SelectionList/BaseListItem.tsx b/src/components/SelectionList/BaseListItem.tsx
index 5f4438f18f60..9e6fb31d0316 100644
--- a/src/components/SelectionList/BaseListItem.tsx
+++ b/src/components/SelectionList/BaseListItem.tsx
@@ -28,7 +28,9 @@ function BaseListItem({
FooterComponent,
children,
isFocused,
+ shouldSyncFocus = true,
onFocus = () => {},
+ hoverStyle,
}: BaseListItemProps) {
const theme = useTheme();
const styles = useThemeStyles();
@@ -37,7 +39,7 @@ function BaseListItem({
const pressableRef = useRef(null);
// Sync focus on an item
- useSyncFocus(pressableRef, Boolean(isFocused));
+ useSyncFocus(pressableRef, Boolean(isFocused && shouldSyncFocus));
const rightHandSideComponentRender = () => {
if (canSelectMultiple || !rightHandSideComponent) {
@@ -68,7 +70,7 @@ function BaseListItem({
accessibilityLabel={item.text ?? ''}
role={CONST.ROLE.BUTTON}
hoverDimmingValue={1}
- hoverStyle={!item.isDisabled && !item.isSelected && styles.hoveredComponentBG}
+ hoverStyle={[!item.isDisabled && styles.hoveredComponentBG, hoverStyle]}
dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
onMouseDown={shouldPreventDefaultFocusOnSelectRow ? (e) => e.preventDefault() : undefined}
nativeID={keyForList ?? ''}
diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx
index 62f098e76228..b24fe5351fad 100644
--- a/src/components/SelectionList/BaseSelectionList.tsx
+++ b/src/components/SelectionList/BaseSelectionList.tsx
@@ -2,7 +2,7 @@ import {useFocusEffect, useIsFocused} from '@react-navigation/native';
import isEmpty from 'lodash/isEmpty';
import type {ForwardedRef} from 'react';
import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
-import type {LayoutChangeEvent, SectionList as RNSectionList, TextInput as RNTextInput, SectionListRenderItemInfo} from 'react-native';
+import type {LayoutChangeEvent, SectionList as RNSectionList, TextInput as RNTextInput, SectionListData, SectionListRenderItemInfo} from 'react-native';
import {View} from 'react-native';
import Button from '@components/Button';
import Checkbox from '@components/Checkbox';
@@ -52,6 +52,7 @@ function BaseSelectionList(
onConfirm,
headerContent,
footerContent,
+ listFooterContent,
showScrollIndicator = true,
showLoadingPlaceholder = false,
showConfirmButton = false,
@@ -92,6 +93,7 @@ function BaseSelectionList(
const [itemsToHighlight, setItemsToHighlight] = useState | null>(null);
const itemFocusTimeoutRef = useRef(null);
const [currentPage, setCurrentPage] = useState(1);
+ const isTextInputFocusedRef = useRef(false);
const incrementPage = () => setCurrentPage((prev) => prev + 1);
@@ -294,7 +296,7 @@ function BaseSelectionList(
*
* [{header}, {sectionHeader}, {item}, {item}, {sectionHeader}, {item}, {item}, {footer}]
*/
- const getItemLayout = (data: Array> | null, flatDataArrayIndex: number) => {
+ const getItemLayout = (data: Array>> | null, flatDataArrayIndex: number) => {
const targetItem = flattenedSections.itemLayouts[flatDataArrayIndex];
if (!targetItem) {
@@ -313,6 +315,10 @@ function BaseSelectionList(
};
const renderSectionHeader = ({section}: {section: SectionListDataType}) => {
+ if (section.CustomSectionHeader) {
+ return ;
+ }
+
if (!section.title || isEmptyObject(section.data)) {
return null;
}
@@ -329,7 +335,7 @@ function BaseSelectionList(
};
const renderItem = ({item, index, section}: SectionListRenderItemInfo>) => {
- const normalizedIndex = index + section.indexOffset;
+ const normalizedIndex = index + (section?.indexOffset ?? 0);
const isDisabled = !!section.isDisabled || item.isDisabled;
const isItemFocused = !isDisabled && (focusedIndex === normalizedIndex || itemsToHighlight?.has(item.keyForList ?? ''));
// We only create tooltips for the first 10 users or so since some reports have hundreds of users, causing performance to degrade.
@@ -349,7 +355,8 @@ function BaseSelectionList(
rightHandSideComponent={rightHandSideComponent}
keyForList={item.keyForList ?? ''}
isMultilineSupported={isRowMultilineSupported}
- onFocus={() => setFocusedIndex(index)}
+ onFocus={() => setFocusedIndex(normalizedIndex)}
+ shouldSyncFocus={!isTextInputFocusedRef.current}
/>
);
};
@@ -522,6 +529,8 @@ function BaseSelectionList(
textInputRef.current = element as RNTextInput;
}
}}
+ onFocus={() => (isTextInputFocusedRef.current = true)}
+ onBlur={() => (isTextInputFocusedRef.current = false)}
label={textInputLabel}
accessibilityLabel={textInputLabel}
hint={textInputHint}
@@ -603,7 +612,7 @@ function BaseSelectionList(
testID="selection-list"
onLayout={onSectionListLayout}
style={(!maxToRenderPerBatch || (shouldHideListOnInitialRender && isInitialSectionListRender)) && styles.opacity0}
- ListFooterComponent={ShowMoreButtonInstance}
+ ListFooterComponent={listFooterContent ?? ShowMoreButtonInstance}
/>
{children}
>
diff --git a/src/components/SelectionList/InviteMemberListItem.tsx b/src/components/SelectionList/InviteMemberListItem.tsx
index 03a27c88fa68..69774e24b970 100644
--- a/src/components/SelectionList/InviteMemberListItem.tsx
+++ b/src/components/SelectionList/InviteMemberListItem.tsx
@@ -26,6 +26,8 @@ function InviteMemberListItem({
onDismissError,
shouldPreventDefaultFocusOnSelectRow,
rightHandSideComponent,
+ onFocus,
+ shouldSyncFocus,
}: InviteMemberListItemProps) {
const styles = useThemeStyles();
const theme = useTheme();
@@ -66,6 +68,8 @@ function InviteMemberListItem({
) : undefined
}
keyForList={item.keyForList}
+ onFocus={onFocus}
+ shouldSyncFocus={shouldSyncFocus}
>
{(hovered?: boolean) => (
<>
diff --git a/src/components/SelectionList/RadioListItem.tsx b/src/components/SelectionList/RadioListItem.tsx
index e26926e75e13..7ad4819b9690 100644
--- a/src/components/SelectionList/RadioListItem.tsx
+++ b/src/components/SelectionList/RadioListItem.tsx
@@ -17,6 +17,7 @@ function RadioListItem({
rightHandSideComponent,
isMultilineSupported = false,
onFocus,
+ shouldSyncFocus,
}: RadioListItemProps) {
const styles = useThemeStyles();
const fullTitle = isMultilineSupported ? item.text?.trimStart() : item.text;
@@ -36,6 +37,7 @@ function RadioListItem({
rightHandSideComponent={rightHandSideComponent}
keyForList={item.keyForList}
onFocus={onFocus}
+ shouldSyncFocus={shouldSyncFocus}
>
<>
diff --git a/src/components/SelectionList/TableListItem.tsx b/src/components/SelectionList/TableListItem.tsx
index c2680c92780a..d07d658f6b12 100644
--- a/src/components/SelectionList/TableListItem.tsx
+++ b/src/components/SelectionList/TableListItem.tsx
@@ -24,6 +24,7 @@ function TableListItem({
shouldPreventDefaultFocusOnSelectRow,
rightHandSideComponent,
onFocus,
+ shouldSyncFocus,
}: TableListItemProps) {
const styles = useThemeStyles();
const theme = useTheme();
@@ -58,6 +59,8 @@ function TableListItem({
pendingAction={item.pendingAction}
keyForList={item.keyForList}
onFocus={onFocus}
+ shouldSyncFocus={shouldSyncFocus}
+ hoverStyle={item.isSelected && styles.activeComponentBG}
>
{(hovered) => (
<>
diff --git a/src/components/SelectionList/UserListItem.tsx b/src/components/SelectionList/UserListItem.tsx
index 940828ebcac3..68349293e134 100644
--- a/src/components/SelectionList/UserListItem.tsx
+++ b/src/components/SelectionList/UserListItem.tsx
@@ -27,6 +27,8 @@ function UserListItem({
onDismissError,
shouldPreventDefaultFocusOnSelectRow,
rightHandSideComponent,
+ onFocus,
+ shouldSyncFocus,
}: UserListItemProps) {
const styles = useThemeStyles();
const theme = useTheme();
@@ -67,6 +69,8 @@ function UserListItem({
) : undefined
}
keyForList={item.keyForList}
+ onFocus={onFocus}
+ shouldSyncFocus={shouldSyncFocus}
>
{(hovered?: boolean) => (
<>
diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts
index d89f4d5b92f3..a96d6c3abb17 100644
--- a/src/components/SelectionList/types.ts
+++ b/src/components/SelectionList/types.ts
@@ -12,6 +12,11 @@ import type RadioListItem from './RadioListItem';
import type TableListItem from './TableListItem';
import type UserListItem from './UserListItem';
+type TRightHandSideComponent = {
+ /** Component to display on the right side */
+ rightHandSideComponent?: ((item: TItem) => ReactElement | null | undefined) | ReactElement | null;
+};
+
type CommonListItemProps = {
/** Whether this item is focused (for arrow key controls) */
isFocused?: boolean;
@@ -34,9 +39,6 @@ type CommonListItemProps = {
/** Callback to fire when an error is dismissed */
onDismissError?: (item: TItem) => void;
- /** Component to display on the right side */
- rightHandSideComponent?: ((item: TItem) => ReactElement | null) | ReactElement | null;
-
/** Styles for the pressable component */
pressableStyle?: StyleProp;
@@ -54,7 +56,7 @@ type CommonListItemProps = {
/** Handles what to do when the item is focused */
onFocus?: () => void;
-};
+} & TRightHandSideComponent;
type ListItem = {
/** Text to display */
@@ -137,6 +139,12 @@ type ListItemProps = CommonListItemProps & {
/** Key used internally by React */
keyForList?: string;
+
+ /**
+ * Whether the focus on the element should be synchronized. For example it should be set to false when the text input above list items is currently focused.
+ * When we type something into the text input, the first element found is focused, in this situation we should not synchronize the focus on the element because we will lose the focus from the text input.
+ */
+ shouldSyncFocus?: boolean;
};
type BaseListItemProps = CommonListItemProps & {
@@ -147,6 +155,8 @@ type BaseListItemProps = CommonListItemProps & {
pendingAction?: PendingAction | null;
FooterComponent?: ReactElement;
children?: ReactElement> | ((hovered: boolean) => ReactElement>);
+ shouldSyncFocus?: boolean;
+ hoverStyle?: StyleProp;
};
type UserListItemProps = ListItemProps & {
@@ -184,12 +194,12 @@ type Section = {
type SectionWithIndexOffset = Section & {
/** The initial index of this section given the total number of options in each section's data array */
- indexOffset: number;
+ indexOffset?: number;
};
type BaseSelectionListProps = Partial & {
/** Sections for the section list */
- sections: Array>> | typeof CONST.EMPTY_ARRAY;
+ sections: Array> | typeof CONST.EMPTY_ARRAY;
/** Default renderer for every item in the list */
ListItem: ValidListItem;
@@ -281,6 +291,9 @@ type BaseSelectionListProps = Partial & {
/** Custom content to display in the footer */
footerContent?: ReactNode;
+ /** Custom content to display in the footer of list component. If present ShowMore button won't be displayed */
+ listFooterContent?: React.JSX.Element | null;
+
/** Whether to use dynamic maxToRenderPerBatch depending on the visible number of elements */
shouldUseDynamicMaxToRenderPerBatch?: boolean;
@@ -293,9 +306,6 @@ type BaseSelectionListProps = Partial & {
/** Whether focus event should be delayed */
shouldDelayFocus?: boolean;
- /** Component to display on the right side of each child */
- rightHandSideComponent?: ((item: TItem) => ReactElement | null) | ReactElement | null;
-
/** Whether to show the loading indicator for new options */
isLoadingNewOptions?: boolean;
@@ -322,7 +332,7 @@ type BaseSelectionListProps = Partial & {
* When false, the list will render immediately and scroll to the bottom which works great for small lists.
*/
shouldHideListOnInitialRender?: boolean;
-};
+} & TRightHandSideComponent;
type SelectionListHandle = {
scrollAndHighlightItem?: (items: string[], timeout: number) => void;
@@ -343,7 +353,11 @@ type FlattenedSectionsReturn = {
type ButtonOrCheckBoxRoles = 'button' | 'checkbox';
-type SectionListDataType = SectionListData>;
+type ExtendedSectionListData> = SectionListData & {
+ CustomSectionHeader?: ({section}: {section: TSection}) => ReactElement;
+};
+
+type SectionListDataType = ExtendedSectionListData>;
export type {
BaseSelectionListProps,
diff --git a/src/components/SettlementButton.tsx b/src/components/SettlementButton.tsx
index 84c57ae381e3..f56c4dd1a863 100644
--- a/src/components/SettlementButton.tsx
+++ b/src/components/SettlementButton.tsx
@@ -180,13 +180,13 @@ function SettlementButton({
};
const canUseWallet = !isExpenseReport && currency === CONST.CURRENCY.USD;
- // Only show the Approve button if the user cannot pay the request
+ // Only show the Approve button if the user cannot pay the expense
if (shouldHidePaymentOptions && shouldShowApproveButton) {
return [approveButtonOption];
}
// 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,
+ // If the user has previously chosen a specific payment option or paid for some expense,
// let's use the last payment method or use default.
const paymentMethod = nvpLastPaymentMethod?.[policyID] ?? '';
if (canUseWallet) {
diff --git a/src/components/TagPicker/index.tsx b/src/components/TagPicker/index.tsx
index f968af4f6030..97cd9aa5c691 100644
--- a/src/components/TagPicker/index.tsx
+++ b/src/components/TagPicker/index.tsx
@@ -32,7 +32,7 @@ type TagPickerProps = TagPickerOnyxProps & {
// eslint-disable-next-line react/no-unused-prop-types
policyID: string;
- /** The selected tag of the money request */
+ /** The selected tag of the expense */
selectedTag: string;
/** The name of tag list we are getting tags for */
diff --git a/src/components/VideoPlayerContexts/PlaybackContext.tsx b/src/components/VideoPlayerContexts/PlaybackContext.tsx
index 0fe0fe378ba6..499dd2b07f67 100644
--- a/src/components/VideoPlayerContexts/PlaybackContext.tsx
+++ b/src/components/VideoPlayerContexts/PlaybackContext.tsx
@@ -10,6 +10,7 @@ const Context = React.createContext(null);
function PlaybackContextProvider({children}: ChildrenProps) {
const [currentlyPlayingURL, setCurrentlyPlayingURL] = useState(null);
+ const [currentlyPlayingURLReportID, setCurrentlyPlayingURLReportID] = useState();
const [sharedElement, setSharedElement] = useState(null);
const [originalParent, setOriginalParent] = useState(null);
const currentVideoPlayerRef = useRef(null);
@@ -21,7 +22,7 @@ function PlaybackContextProvider({children}: ChildrenProps) {
}, [currentVideoPlayerRef]);
const stopVideo = useCallback(() => {
- currentVideoPlayerRef.current?.stopAsync?.();
+ currentVideoPlayerRef.current?.setStatusAsync?.({shouldPlay: false, positionMillis: 0});
}, [currentVideoPlayerRef]);
const playVideo = useCallback(() => {
@@ -43,9 +44,10 @@ function PlaybackContextProvider({children}: ChildrenProps) {
if (currentlyPlayingURL && url !== currentlyPlayingURL) {
pauseVideo();
}
+ setCurrentlyPlayingURLReportID(currentReportID);
setCurrentlyPlayingURL(url);
},
- [currentlyPlayingURL, pauseVideo],
+ [currentlyPlayingURL, currentReportID, pauseVideo],
);
const shareVideoPlayerElements = useCallback(
@@ -91,6 +93,7 @@ function PlaybackContextProvider({children}: ChildrenProps) {
() => ({
updateCurrentlyPlayingURL,
currentlyPlayingURL,
+ currentlyPlayingURLReportID,
originalParent,
sharedElement,
currentVideoPlayerRef,
@@ -101,7 +104,18 @@ function PlaybackContextProvider({children}: ChildrenProps) {
checkVideoPlaying,
videoResumeTryNumber,
}),
- [updateCurrentlyPlayingURL, currentlyPlayingURL, originalParent, sharedElement, shareVideoPlayerElements, playVideo, pauseVideo, checkVideoPlaying, setCurrentlyPlayingURL],
+ [
+ updateCurrentlyPlayingURL,
+ currentlyPlayingURL,
+ currentlyPlayingURLReportID,
+ originalParent,
+ sharedElement,
+ shareVideoPlayerElements,
+ playVideo,
+ pauseVideo,
+ checkVideoPlaying,
+ setCurrentlyPlayingURL,
+ ],
);
return {children};
}
diff --git a/src/components/VideoPlayerContexts/types.ts b/src/components/VideoPlayerContexts/types.ts
index e6a20ec090fe..ff8d9378caf7 100644
--- a/src/components/VideoPlayerContexts/types.ts
+++ b/src/components/VideoPlayerContexts/types.ts
@@ -9,6 +9,7 @@ import type CONST from '@src/CONST';
type PlaybackContext = {
updateCurrentlyPlayingURL: (url: string | null) => void;
currentlyPlayingURL: string | null;
+ currentlyPlayingURLReportID: string | undefined;
originalParent: View | HTMLDivElement | null;
sharedElement: View | HTMLDivElement | null;
videoResumeTryNumber: MutableRefObject;
diff --git a/src/components/VideoPlayerPreview/index.tsx b/src/components/VideoPlayerPreview/index.tsx
index 923ab919a55a..414e95b0ff32 100644
--- a/src/components/VideoPlayerPreview/index.tsx
+++ b/src/components/VideoPlayerPreview/index.tsx
@@ -21,6 +21,9 @@ type VideoPlayerPreviewProps = {
/** Url to a video. */
videoUrl: string;
+ /** reportID of the video */
+ reportID: string;
+
/** Dimension of a video. */
videoDimensions: VideoDimensions;
@@ -37,10 +40,10 @@ type VideoPlayerPreviewProps = {
onShowModalPress: (event?: GestureResponderEvent | KeyboardEvent) => void | Promise;
};
-function VideoPlayerPreview({videoUrl, thumbnailUrl, fileName, videoDimensions, videoDuration, onShowModalPress}: VideoPlayerPreviewProps) {
+function VideoPlayerPreview({videoUrl, thumbnailUrl, reportID, fileName, videoDimensions, videoDuration, onShowModalPress}: VideoPlayerPreviewProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
- const {currentlyPlayingURL, updateCurrentlyPlayingURL} = usePlaybackContext();
+ const {currentlyPlayingURL, currentlyPlayingURLReportID, updateCurrentlyPlayingURL} = usePlaybackContext();
const {isSmallScreenWidth} = useWindowDimensions();
const [isThumbnail, setIsThumbnail] = useState(true);
const [measuredDimensions, setMeasuredDimensions] = useState(videoDimensions);
@@ -60,11 +63,11 @@ function VideoPlayerPreview({videoUrl, thumbnailUrl, fileName, videoDimensions,
};
useEffect(() => {
- if (videoUrl !== currentlyPlayingURL) {
+ if (videoUrl !== currentlyPlayingURL || reportID !== currentlyPlayingURLReportID) {
return;
}
setIsThumbnail(false);
- }, [currentlyPlayingURL, updateCurrentlyPlayingURL, videoUrl]);
+ }, [currentlyPlayingURL, currentlyPlayingURLReportID, updateCurrentlyPlayingURL, videoUrl, reportID]);
return (
diff --git a/src/components/transactionPropTypes.js b/src/components/transactionPropTypes.js
index 7eb1b776358c..f951837503f3 100644
--- a/src/components/transactionPropTypes.js
+++ b/src/components/transactionPropTypes.js
@@ -39,7 +39,7 @@ export default PropTypes.shape({
/** The text of the comment */
comment: PropTypes.string,
- /** The waypoints defining the distance request */
+ /** The waypoints defining the distance expense */
waypoints: PropTypes.shape({
/** The latitude of the waypoint */
lat: PropTypes.number,
diff --git a/src/hooks/useMarkdownStyle.ts b/src/hooks/useMarkdownStyle.ts
index 21c8d02e9194..b1f430e232e4 100644
--- a/src/hooks/useMarkdownStyle.ts
+++ b/src/hooks/useMarkdownStyle.ts
@@ -49,6 +49,10 @@ function useMarkdownStyle(message: string | null = null): MarkdownStyle {
color: theme.mentionText,
backgroundColor: theme.mentionBG,
},
+ mentionReport: {
+ color: theme.mentionText,
+ backgroundColor: theme.mentionBG,
+ },
}),
[theme, emojiFontSize],
);
diff --git a/src/hooks/useViewportOffsetTop/index.ts b/src/hooks/useViewportOffsetTop/index.ts
index da2325a7e13f..6f617aa38121 100644
--- a/src/hooks/useViewportOffsetTop/index.ts
+++ b/src/hooks/useViewportOffsetTop/index.ts
@@ -18,7 +18,7 @@ export default function useViewportOffsetTop(shouldAdjustScrollView = false): nu
if (Browser.isMobileSafari() && shouldAdjustScrollView && window.visualViewport) {
const clientHeight = document.body.clientHeight;
- const adjustScrollY = Math.round(clientHeight - window.visualViewport.height);
+ const adjustScrollY = clientHeight - window.visualViewport.height;
if (cachedDefaultOffsetTop.current === 0) {
cachedDefaultOffsetTop.current = targetOffsetTop;
}
@@ -43,7 +43,7 @@ export default function useViewportOffsetTop(shouldAdjustScrollView = false): nu
if (!shouldAdjustScrollView) {
return;
}
- window.scrollTo({top: viewportOffsetTop, behavior: 'instant'});
+ window.scrollTo({top: viewportOffsetTop, behavior: 'smooth'});
}, [shouldAdjustScrollView, viewportOffsetTop]);
return viewportOffsetTop;
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 9451407c822f..ed2587e5e2c6 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -50,6 +50,7 @@ import type {
PayerPaidAmountParams,
PayerPaidParams,
PayerSettledParams,
+ PaySomeoneParams,
RemovedTheRequestParams,
RenamedRoomActionParams,
ReportArchiveReasonsClosedParams,
@@ -418,7 +419,7 @@ export default {
},
login: {
hero: {
- header: 'Split bills, request payments, and chat with friends.',
+ header: 'Manage spend, split expenses, and chat with your team.',
body: 'Welcome to the future of Expensify, your new go-to place for financial collaboration with friends and teammates alike.',
},
},
@@ -470,14 +471,9 @@ export default {
copyEmailToClipboard: 'Copy email to clipboard',
markAsUnread: 'Mark as unread',
markAsRead: 'Mark as read',
- editAction: ({action}: EditActionParams) =>
- `Edit ${action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? `${action?.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK ? 'expense' : 'request'}` : 'comment'}`,
- deleteAction: ({action}: DeleteActionParams) =>
- `Delete ${action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? `${action?.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK ? 'expense' : 'request'}` : 'comment'}`,
- deleteConfirmation: ({action}: DeleteConfirmationParams) =>
- `Are you sure you want to delete this ${
- action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? `${action?.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK ? 'expense' : 'request'}` : 'comment'
- }?`,
+ editAction: ({action}: EditActionParams) => `Edit ${action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? 'expense' : 'comment'}`,
+ deleteAction: ({action}: DeleteActionParams) => `Delete ${action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? 'expense' : 'comment'}`,
+ deleteConfirmation: ({action}: DeleteConfirmationParams) => `Are you sure you want to delete this ${action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? 'expense' : 'comment'}?`,
onlyVisible: 'Only visible to',
replyInThread: 'Reply in thread',
joinThread: 'Join thread',
@@ -505,7 +501,7 @@ export default {
beginningOfChatHistory: 'This is the beginning of your chat with ',
beginningOfChatHistoryPolicyExpenseChatPartOne: 'Collaboration between ',
beginningOfChatHistoryPolicyExpenseChatPartTwo: ' and ',
- beginningOfChatHistoryPolicyExpenseChatPartThree: ' starts here! 🎉 This is the place to chat, request money and settle up.',
+ beginningOfChatHistoryPolicyExpenseChatPartThree: ' starts here! 🎉 This is the place to chat, submit expenses and settle up.',
beginningOfChatHistorySelfDM: 'This is your personal space. Use it for notes, tasks, drafts, and reminders.',
chatWithAccountManager: 'Chat with your account manager here',
sayHello: 'Say hello!',
@@ -513,9 +509,9 @@ export default {
welcomeToRoom: ({roomName}: WelcomeToRoomParams) => `Welcome to ${roomName}!`,
usePlusButton: ({additionalText}: UsePlusButtonParams) => `\nYou can also use the + button to ${additionalText}, or assign a task!`,
iouTypes: {
- send: 'send money',
- split: 'split a bill',
- request: 'request money',
+ send: 'pay expenses',
+ split: 'split an expense',
+ request: 'submit an expense',
// eslint-disable-next-line @typescript-eslint/naming-convention
'track-expense': 'track an expense',
},
@@ -595,15 +591,15 @@ export default {
quickAction: {
scanReceipt: 'Scan receipt',
recordDistance: 'Record distance',
- requestMoney: 'Request money',
- splitBill: 'Split bill',
+ requestMoney: 'Submit expense',
+ splitBill: 'Split expense',
splitScan: 'Split receipt',
splitDistance: 'Split distance',
- sendMoney: 'Send money',
+ sendMoney: 'Pay someone',
assignTask: 'Assign task',
header: 'Quick action',
- trackManual: 'Track manual',
- trackScan: 'Track scan',
+ trackManual: 'Track expense',
+ trackScan: 'Track receipt',
trackDistance: 'Track distance',
},
iou: {
@@ -616,12 +612,13 @@ export default {
card: 'Card',
original: 'Original',
split: 'Split',
- addToSplit: 'Add to split',
- splitBill: 'Split bill',
- request: 'Request',
+ splitExpense: 'Split expense',
+ paySomeone: ({name}: PaySomeoneParams) => `Pay ${name ?? 'someone'}`,
+ expense: 'Expense',
+ categorize: 'Categorize',
+ share: 'Share',
participants: 'Participants',
- requestMoney: 'Request money',
- sendMoney: 'Send money',
+ submitExpense: 'Submit expense',
trackExpense: 'Track expense',
pay: 'Pay',
cancelPayment: 'Cancel payment',
@@ -640,20 +637,20 @@ export default {
receiptStatusText: "Only you can see this receipt when it's scanning. Check back later or enter the details now.",
receiptScanningFailed: 'Receipt scanning failed. Enter the details manually.',
transactionPendingText: 'It takes a few days from the date the card was used for the transaction to post.',
- requestCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) =>
- `${count} ${Str.pluralize('request', 'requests', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}${
+ expenseCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) =>
+ `${count} ${Str.pluralize('expense', 'expenses', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}${
pendingReceipts > 0 ? `, ${pendingReceipts} pending` : ''
}`,
- deleteRequest: 'Delete request',
- deleteConfirmation: 'Are you sure that you want to delete this request?',
+ deleteExpense: 'Delete expense',
+ deleteConfirmation: 'Are you sure that you want to delete this expense?',
settledExpensify: 'Paid',
settledElsewhere: 'Paid elsewhere',
settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} with Expensify` : `Pay with Expensify`),
payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} elsewhere` : `Pay elsewhere`),
nextStep: 'Next Steps',
finished: 'Finished',
- requestAmount: ({amount}: RequestAmountParams) => `request ${amount}`,
- requestedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `requested ${formattedAmount}${comment ? ` for ${comment}` : ''}`,
+ submitAmount: ({amount}: RequestAmountParams) => `submit ${amount}`,
+ submittedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `submitted ${formattedAmount}${comment ? ` for ${comment}` : ''}`,
trackedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `tracking ${formattedAmount}${comment ? ` for ${comment}` : ''}`,
splitAmount: ({amount}: SplitAmountParams) => `split ${amount}`,
didSplitAmount: ({formattedAmount, comment}: DidSplitAmountMessageParams) => `split ${formattedAmount}${comment ? ` for ${comment}` : ''}`,
@@ -678,16 +675,16 @@ export default {
paidWithExpensifyWithAmount: ({payer, amount}: PaidWithExpensifyWithAmountParams) => `${payer ? `${payer} ` : ''}paid ${amount} using Expensify`,
noReimbursableExpenses: 'This report has an invalid amount',
pendingConversionMessage: "Total will update when you're back online",
- changedTheRequest: 'changed the request',
+ changedTheExpense: 'changed the expense',
setTheRequest: ({valueName, newValueToDisplay}: SetTheRequestParams) => `the ${valueName} to ${newValueToDisplay}`,
setTheDistance: ({newDistanceToDisplay, newAmountToDisplay}: SetTheDistanceParams) => `set the distance to ${newDistanceToDisplay}, which set the amount to ${newAmountToDisplay}`,
removedTheRequest: ({valueName, oldValueToDisplay}: RemovedTheRequestParams) => `the ${valueName} (previously ${oldValueToDisplay})`,
updatedTheRequest: ({valueName, newValueToDisplay, oldValueToDisplay}: UpdatedTheRequestParams) => `the ${valueName} to ${newValueToDisplay} (previously ${oldValueToDisplay})`,
updatedTheDistance: ({newDistanceToDisplay, oldDistanceToDisplay, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceParams) =>
`changed the distance to ${newDistanceToDisplay} (previously ${oldDistanceToDisplay}), which updated the amount to ${newAmountToDisplay} (previously ${oldAmountToDisplay})`,
- threadRequestReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `for ${comment}` : 'request'}`,
+ threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `for ${comment}` : 'expense'}`,
threadTrackReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `Tracking ${formattedAmount} ${comment ? `for ${comment}` : ''}`,
- threadSentMoneyReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} sent${comment ? ` for ${comment}` : ''}`,
+ threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} sent${comment ? ` for ${comment}` : ''}`,
tagSelection: 'Select a tag to better organize your spend.',
categorySelection: 'Select a category to better organize your spend.',
error: {
@@ -696,36 +693,36 @@ export default {
invalidTaxAmount: ({amount}: RequestAmountParams) => `Maximum tax amount is ${amount}`,
invalidSplit: 'Split amounts do not equal total amount',
other: 'Unexpected error, please try again later',
- genericCreateFailureMessage: 'Unexpected error requesting money, please try again later',
+ genericCreateFailureMessage: 'Unexpected error submitting this expense. Please try again later.',
receiptFailureMessage: "The receipt didn't upload. ",
saveFileMessage: 'Download the file ',
loseFileMessage: 'or dismiss this error and lose it',
- genericDeleteFailureMessage: 'Unexpected error deleting the money request, please try again later',
- genericEditFailureMessage: 'Unexpected error editing the money request, please try again later',
+ genericDeleteFailureMessage: 'Unexpected error deleting this expense, please try again later',
+ genericEditFailureMessage: 'Unexpected error editing this expense, please try again later',
genericSmartscanFailureMessage: 'Transaction is missing fields',
duplicateWaypointsErrorMessage: 'Please remove duplicate waypoints',
atLeastTwoDifferentWaypoints: 'Please enter at least two different addresses',
- splitBillMultipleParticipantsErrorMessage: 'Split bill is only allowed between a single workspace or individual users. Please update your selection.',
+ splitExpenseMultipleParticipantsErrorMessage: 'An expense cannot be split between a workspace and other members. Please update your selection.',
invalidMerchant: 'Please enter a correct merchant.',
},
waitingOnEnabledWallet: ({submitterDisplayName}: WaitingOnBankAccountParams) => `Started settling up, payment is held until ${submitterDisplayName} enables their Wallet`,
enableWallet: 'Enable Wallet',
hold: 'Hold',
- holdRequest: 'Hold request',
- unholdRequest: 'Unhold request',
- heldRequest: 'held this request',
- unheldRequest: 'unheld this request',
- explainHold: "Explain why you're holding this request.",
+ holdExpense: 'Hold expense',
+ unholdExpense: 'Unhold expense',
+ heldExpense: 'held this expense',
+ unheldExpense: 'unheld this expense',
+ explainHold: "Explain why you're holding this expense.",
reason: 'Reason',
holdReasonRequired: 'A reason is required when holding.',
- requestOnHold: 'This request was put on hold. Review the comments for next steps.',
+ expenseOnHold: 'This expense was put on hold. Review the comments for next steps.',
confirmApprove: 'Confirm what to approve',
confirmApprovalAmount: 'Approve the entire report total or only the amount not on hold.',
confirmPay: 'Confirm what to pay',
confirmPayAmount: 'Pay all out-of-pocket spend or only the amount not on hold.',
payOnly: 'Pay only',
approveOnly: 'Approve only',
- holdEducationalTitle: 'This request is on',
+ holdEducationalTitle: 'This expense is on',
whatIsHoldTitle: 'What is hold?',
whatIsHoldExplain: 'Hold is our way of streamlining financial collaboration. "Reject" is so harsh!',
holdIsTemporaryTitle: 'Hold is usually temporary',
@@ -936,8 +933,7 @@ export default {
reasonForLeavingPrompt: 'We’d hate to see you go! Would you kindly tell us why, so we can improve?',
enterMessageHere: 'Enter message here',
closeAccountWarning: 'Closing your account cannot be undone.',
- closeAccountPermanentlyDeleteData:
- 'This will permanently delete all of your unsubmitted expense data and will cancel and decline any outstanding money requests. Are you sure you want to delete the account?',
+ closeAccountPermanentlyDeleteData: 'Are you sure you want to delete your account? This will permanently delete any outstanding expenses.',
enterDefaultContactToConfirm: 'Please type your default contact method to confirm you wish to close your account. Your default contact method is:',
enterDefaultContact: 'Enter your default contact method',
defaultContact: 'Default contact method:',
@@ -1265,19 +1261,6 @@ export default {
},
chooseThemeBelowOrSync: 'Choose a theme below, or sync with your device settings.',
},
- signInPage: {
- expensifyDotCash: 'New Expensify',
- theCode: 'the code',
- openJobs: 'open jobs',
- heroHeading: 'Split bills\nand chat with friends.',
- heroDescription: {
- phrase1: "Money talks. And now that chat and payments are in one place, it's also easy. Your payments get to you as fast as you can get your point across.",
- phrase2: 'The New Expensify is open source. View',
- phrase3: 'the code',
- phrase4: 'View',
- phrase5: 'open jobs',
- },
- },
termsOfUse: {
phrase1: 'By logging in, you agree to the',
phrase2: 'Terms of Service',
@@ -1343,7 +1326,7 @@ export default {
[CONST.ONBOARDING_CHOICES.EMPLOYER]: 'Get paid back by my employer',
[CONST.ONBOARDING_CHOICES.MANAGE_TEAM]: "Manage my team's expenses",
[CONST.ONBOARDING_CHOICES.PERSONAL_SPEND]: 'Track and budget personal spend',
- [CONST.ONBOARDING_CHOICES.CHAT_SPLIT]: 'Chat and split bills with friends',
+ [CONST.ONBOARDING_CHOICES.CHAT_SPLIT]: 'Chat and split expenses with friends',
[CONST.ONBOARDING_CHOICES.LOOKING_AROUND]: "I'm just looking around",
},
error: {
@@ -1399,7 +1382,6 @@ export default {
localTime: 'Local time',
},
newChatPage: {
- createChat: 'Create chat',
startGroup: 'Start group',
addToGroup: 'Add to group',
},
@@ -2113,7 +2095,18 @@ export default {
return 'Importing accounts';
case 'quickbooksOnlineImportClasses':
return 'Importing classes';
-
+ case 'quickbooksOnlineImportLocations':
+ return 'Importing locations';
+ case 'quickbooksOnlineImportProcessing':
+ return 'Processing imported data';
+ case 'quickbooksOnlineSyncBillPayments':
+ return 'Synchronizing reimbursed reports and bill Payments';
+ case 'quickbooksOnlineSyncTaxCodes':
+ return 'Importing tax codes';
+ case 'quickbooksOnlineCheckConnection':
+ return 'Checking QuickBooks Online connection';
+ case 'quickbooksOnlineImportMain':
+ return 'Importing your QuickBooks Online data';
default: {
return `Translation missing for stage: ${stage}`;
}
@@ -2527,7 +2520,6 @@ export default {
parentReportAction: {
deletedReport: '[Deleted report]',
deletedMessage: '[Deleted message]',
- deletedRequest: '[Deleted request]',
deletedExpense: '[Deleted expense]',
reversedTransaction: '[Reversed transaction]',
deletedTask: '[Deleted task]',
@@ -2575,6 +2567,12 @@ export default {
accept: 'Accept',
decline: 'Decline',
},
+ actionableMentionTrackExpense: {
+ submit: 'Submit it to someone',
+ categorize: 'Categorize it',
+ share: 'Share it with my accountant',
+ nothing: 'Nothing for now',
+ },
teachersUnitePage: {
teachersUnite: 'Teachers Unite',
joinExpensifyOrg: 'Join Expensify.org in eliminating injustice around the world and help teachers split their expenses for classrooms in need!',
@@ -2649,27 +2647,27 @@ export default {
body: `Get paid to talk to your friends! Start a chat with a new Expensify account and get $${CONST.REFERRAL_PROGRAM.REVENUE} when they become a customer.`,
},
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST]: {
- buttonText1: 'Request money, ',
+ buttonText1: 'Submit expense, ',
buttonText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}.`,
- header: `Request money, get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
- body: `It pays to get paid! Request money from a new Expensify account and get $${CONST.REFERRAL_PROGRAM.REVENUE} when they become a customer.`,
+ header: `Submit an expense, get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
+ body: `It pays to get paid! Submit an expense to a new Expensify account and get $${CONST.REFERRAL_PROGRAM.REVENUE} when they become a customer.`,
},
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY]: {
- buttonText1: 'Send money, ',
+ buttonText1: 'Pay Someone, ',
buttonText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}.`,
- header: `Send money, get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
- body: `You gotta send money to make money! Send money to a new Expensify account and get $${CONST.REFERRAL_PROGRAM.REVENUE} when they become a customer.`,
+ header: `Pay Someone, get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
+ body: `You gotta spend money to make money! Pay someone with Expensify and get $${CONST.REFERRAL_PROGRAM.REVENUE} when they become a customer.`,
},
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND]: {
buttonText1: 'Invite a friend, ',
buttonText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}.`,
header: `Get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
- body: `Be the first to chat, send or request money, split a bill, or share your invite link with a friend, and you'll get $${CONST.REFERRAL_PROGRAM.REVENUE} when they become a customer. You can post your invite link on social media, too!`,
+ body: `Chat, pay, submit, or split an expense with a friend and get $${CONST.REFERRAL_PROGRAM.REVENUE} when they become a customer. Otherwise, just share your invite link!`,
},
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SHARE_CODE]: {
buttonText1: `Get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
header: `Get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
- body: `Be the first to chat, send or request money, split a bill, or share your invite link with a friend, and you'll get $${CONST.REFERRAL_PROGRAM.REVENUE} when they become a customer. You can post your invite link on social media, too!`,
+ body: `Chat, pay, submit, or split an expense with a friend and get $${CONST.REFERRAL_PROGRAM.REVENUE} when they become a customer. Otherwise, just share your invite link!`,
},
copyReferralLink: 'Copy invite link',
},
@@ -2677,7 +2675,7 @@ export default {
[CONST.INTRO_CHOICES.TRACK]: 'Track business spend for taxes',
[CONST.INTRO_CHOICES.SUBMIT]: 'Get paid back by my employer',
[CONST.INTRO_CHOICES.MANAGE_TEAM]: "Manage my team's expenses",
- [CONST.INTRO_CHOICES.CHAT_SPLIT]: 'Chat and split bills with friends',
+ [CONST.INTRO_CHOICES.CHAT_SPLIT]: 'Chat and split expenses with friends',
welcomeMessage: 'Welcome to Expensify',
welcomeSubtitle: 'What would you like to do?',
},
diff --git a/src/languages/es.ts b/src/languages/es.ts
index a56c8ac2739d..beb654cf0bc4 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -49,6 +49,7 @@ import type {
PayerPaidAmountParams,
PayerPaidParams,
PayerSettledParams,
+ PaySomeoneParams,
RemovedTheRequestParams,
RenamedRoomActionParams,
ReportArchiveReasonsClosedParams,
@@ -409,7 +410,7 @@ export default {
},
login: {
hero: {
- header: 'Divida las facturas, solicite pagos y chatee con sus amigos.',
+ header: 'Gestiona, divide gastos y chatea con tu equipo.',
body: 'Bienvenido al futuro de Expensify, tu nuevo lugar de referencia para la colaboración financiera con amigos y compañeros de equipo por igual.',
},
},
@@ -461,18 +462,10 @@ export default {
copyEmailToClipboard: 'Copiar email al portapapeles',
markAsUnread: 'Marcar como no leído',
markAsRead: 'Marcar como leído',
- editAction: ({action}: EditActionParams) =>
- `Editar ${
- action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? `${action?.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK ? 'gastos' : 'solicitud'}` : 'comentario'
- }`,
- deleteAction: ({action}: DeleteActionParams) =>
- `Eliminar ${
- action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? `${action?.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK ? 'gastos' : 'solicitud'}` : 'comentario'
- }`,
+ editAction: ({action}: EditActionParams) => `Editar ${action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? 'gastos' : 'comentario'}`,
+ deleteAction: ({action}: DeleteActionParams) => `Eliminar ${action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? 'gastos' : 'comentario'}`,
deleteConfirmation: ({action}: DeleteConfirmationParams) =>
- `¿Estás seguro de que quieres eliminar esta ${
- action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? `${action?.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK ? 'gastos' : 'solicitud'}` : 'comentario'
- }`,
+ `¿Estás seguro de que quieres eliminar este ${action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? 'gasto' : 'comentario'}`,
onlyVisible: 'Visible sólo para',
replyInThread: 'Responder en el hilo',
joinThread: 'Unirse al hilo',
@@ -501,7 +494,7 @@ export default {
beginningOfChatHistory: 'Aquí comienzan tus conversaciones con ',
beginningOfChatHistoryPolicyExpenseChatPartOne: '¡La colaboración entre ',
beginningOfChatHistoryPolicyExpenseChatPartTwo: ' y ',
- beginningOfChatHistoryPolicyExpenseChatPartThree: ' empieza aquí! 🎉 Este es el lugar donde chatear, pedir dinero y pagar.',
+ beginningOfChatHistoryPolicyExpenseChatPartThree: ' empieza aquí! 🎉 Este es el lugar donde chatear y presentar o pagar gastos.',
beginningOfChatHistorySelfDM: 'Este es tu espacio personal. Úsalo para notas, tareas, borradores y recordatorios.',
chatWithAccountManager: 'Chatea con tu gestor de cuenta aquí',
sayHello: '¡Saluda!',
@@ -509,9 +502,9 @@ export default {
welcomeToRoom: ({roomName}: WelcomeToRoomParams) => `¡Bienvenido a ${roomName}!`,
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',
- request: 'pedir dinero',
+ send: 'pagar gastos',
+ split: 'dividir un gasto',
+ request: 'presentar un gasto',
// eslint-disable-next-line @typescript-eslint/naming-convention
'track-expense': 'rastrear un gasto',
},
@@ -591,11 +584,11 @@ export default {
quickAction: {
scanReceipt: 'Escanear recibo',
recordDistance: 'Grabar distancia',
- requestMoney: 'Solicitar dinero',
- splitBill: 'Dividir cuenta',
+ requestMoney: 'Presentar gasto',
+ splitBill: 'Dividir gasto',
splitScan: 'Dividir recibo',
splitDistance: 'Dividir distancia',
- sendMoney: 'Enviar dinero',
+ sendMoney: 'Pagar a alguien',
assignTask: 'Assignar tarea',
header: 'Acción rápida',
trackManual: 'Crear gasto',
@@ -612,12 +605,13 @@ export default {
card: 'Tarjeta',
original: 'Original',
split: 'Dividir',
- addToSplit: 'Añadir para dividir',
- splitBill: 'Dividir factura',
- request: 'Solicitar',
+ splitExpense: 'Dividir gasto',
+ expense: 'Gasto',
+ categorize: 'Categorizar',
+ share: 'Compartir',
participants: 'Participantes',
- requestMoney: 'Pedir dinero',
- sendMoney: 'Enviar dinero',
+ submitExpense: 'Presentar gasto',
+ paySomeone: ({name}: PaySomeoneParams) => `Pagar a ${name ?? 'alguien'}`,
trackExpense: 'Seguimiento de gastos',
pay: 'Pagar',
cancelPayment: 'Cancelar el pago',
@@ -636,11 +630,11 @@ export default {
receiptStatusText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o introduce los detalles ahora.',
receiptScanningFailed: 'El escaneo de recibo ha fallado. Introduce los detalles manualmente.',
transactionPendingText: 'La transacción tarda unos días en contabilizarse desde la fecha en que se utilizó la tarjeta.',
- requestCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) =>
- `${count} ${Str.pluralize('solicitude', 'solicitudes', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}${
+ expenseCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) =>
+ `${count} ${Str.pluralize('gasto', 'gastos', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}${
pendingReceipts > 0 ? `, ${pendingReceipts} pendiente` : ''
}`,
- deleteRequest: 'Eliminar solicitud',
+ deleteExpense: 'Eliminar gasto',
deleteConfirmation: '¿Estás seguro de que quieres eliminar esta solicitud?',
settledExpensify: 'Pagado',
settledElsewhere: 'Pagado de otra forma',
@@ -648,9 +642,9 @@ export default {
payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pagar ${formattedAmount} de otra forma` : `Pagar de otra forma`),
nextStep: 'Pasos Siguientes',
finished: 'Finalizado',
- requestAmount: ({amount}: RequestAmountParams) => `solicitar ${amount}`,
- requestedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `solicité ${formattedAmount}${comment ? ` para ${comment}` : ''}`,
- trackedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `seguimiento ${formattedAmount}${comment ? ` para ${comment}` : ''}`,
+ submitAmount: ({amount}: RequestAmountParams) => `solicitar ${amount}`,
+ submittedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `solicitó ${formattedAmount}${comment ? ` para ${comment}` : ''}`,
+ trackedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `realizó un seguimiento de ${formattedAmount}${comment ? ` para ${comment}` : ''}`,
splitAmount: ({amount}: SplitAmountParams) => `dividir ${amount}`,
didSplitAmount: ({formattedAmount, comment}: DidSplitAmountMessageParams) => `dividió ${formattedAmount}${comment ? ` para ${comment}` : ''}`,
amountEach: ({amount}: AmountEachParams) => `${amount} cada uno`,
@@ -674,7 +668,7 @@ export default {
paidWithExpensifyWithAmount: ({payer, amount}: PaidWithExpensifyWithAmountParams) => `${payer ? `${payer} ` : ''}pagó ${amount} con Expensify`,
noReimbursableExpenses: 'El importe de este informe no es válido',
pendingConversionMessage: 'El total se actualizará cuando estés online',
- changedTheRequest: 'cambió la solicitud',
+ changedTheExpense: 'cambió el gasto',
setTheRequest: ({valueName, newValueToDisplay}: SetTheRequestParams) => `${valueName === 'comerciante' ? 'el' : 'la'} ${valueName} a ${newValueToDisplay}`,
setTheDistance: ({newDistanceToDisplay, newAmountToDisplay}: SetTheDistanceParams) =>
`estableció la distancia a ${newDistanceToDisplay}, lo que estableció el importe a ${newAmountToDisplay}`,
@@ -683,9 +677,9 @@ export default {
`${valueName === 'comerciante' ? 'el' : 'la'} ${valueName} a ${newValueToDisplay} (previamente ${oldValueToDisplay})`,
updatedTheDistance: ({newDistanceToDisplay, oldDistanceToDisplay, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceParams) =>
`cambió la distancia a ${newDistanceToDisplay} (previamente ${oldDistanceToDisplay}), lo que cambió el importe a ${newAmountToDisplay} (previamente ${oldAmountToDisplay})`,
- threadRequestReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${comment ? `${formattedAmount} para ${comment}` : `Solicitud de ${formattedAmount}`}`,
+ threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${comment ? `${formattedAmount} para ${comment}` : `Gasto de ${formattedAmount}`}`,
threadTrackReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `Seguimiento ${formattedAmount} ${comment ? `para ${comment}` : ''}`,
- threadSentMoneyReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} enviado${comment ? ` para ${comment}` : ''}`,
+ threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} enviado${comment ? ` para ${comment}` : ''}`,
tagSelection: 'Selecciona una etiqueta para organizar mejor tu dinero.',
categorySelection: 'Seleccione una categoría para organizar mejor tu dinero.',
error: {
@@ -694,28 +688,28 @@ export default {
invalidTaxAmount: ({amount}: RequestAmountParams) => `El importe máximo del impuesto es ${amount}`,
invalidSplit: 'La suma de las partes no equivale al importe total',
other: 'Error inesperado, por favor inténtalo más tarde',
- genericCreateFailureMessage: 'Error inesperado solicitando dinero. Por favor, inténtalo más tarde',
+ genericCreateFailureMessage: 'Error inesperado al enviar este gasto. Por favor, inténtalo más tarde.',
receiptFailureMessage: 'El recibo no se subió. ',
saveFileMessage: 'Guarda el archivo ',
loseFileMessage: 'o descarta este error y piérdelo',
- genericDeleteFailureMessage: 'Error inesperado eliminando la solicitud de dinero. Por favor, inténtalo más tarde',
- genericEditFailureMessage: 'Error inesperado al guardar la solicitud de dinero. Por favor, inténtalo más tarde',
+ genericDeleteFailureMessage: 'Error inesperado al eliminar este gasto. Por favor, inténtalo más tarde',
+ genericEditFailureMessage: 'Error inesperado al editar este gasto. Por favor, inténtalo más tarde',
genericSmartscanFailureMessage: 'La transacción tiene campos vacíos',
duplicateWaypointsErrorMessage: 'Por favor, elimina los puntos de ruta duplicados',
atLeastTwoDifferentWaypoints: 'Por favor, introduce al menos dos direcciones diferentes',
- splitBillMultipleParticipantsErrorMessage: 'Solo puedes dividir una cuenta entre un único espacio de trabajo o con usuarios individuales. Por favor, actualiza tu selección.',
+ splitExpenseMultipleParticipantsErrorMessage: 'Solo puedes dividir un gasto entre un único espacio de trabajo o con usuarios individuales. Por favor, actualiza tu selección.',
invalidMerchant: 'Por favor, introduce un comerciante correcto.',
},
waitingOnEnabledWallet: ({submitterDisplayName}: WaitingOnBankAccountParams) => `Inició el pago, pero no se procesará hasta que ${submitterDisplayName} active su Billetera`,
enableWallet: 'Habilitar Billetera',
- holdRequest: 'Bloquear solicitud',
- unholdRequest: 'Desbloquear solicitud',
- heldRequest: 'bloqueó esta solicitud',
- unheldRequest: 'desbloqueó esta solicitud',
+ holdExpense: 'Bloquear gasto',
+ unholdExpense: 'Desbloquear gasto',
+ heldExpense: 'bloqueó este gasto',
+ unheldExpense: 'desbloqueó este gasto',
explainHold: 'Explica la razón para bloquear esta solicitud.',
reason: 'Razón',
holdReasonRequired: 'Se requiere una razón para bloquear.',
- requestOnHold: 'Este solicitud está bloqueada. Revisa los comentarios para saber como proceder.',
+ expenseOnHold: 'Este gasto está bloqueado. Revisa los comentarios para saber como proceder.',
confirmApprove: 'Confirma que quieres aprobar',
confirmApprovalAmount: 'Aprobar el total o solo la parte no bloqueada.',
confirmPay: 'Confirma que quieres pagar',
@@ -723,7 +717,7 @@ export default {
payOnly: 'Solo pagar',
approveOnly: 'Solo aprobar',
hold: 'Bloqueada',
- holdEducationalTitle: 'Esta solicitud está',
+ holdEducationalTitle: 'Este gasto está',
whatIsHoldTitle: '¿Qué es Bloquear?',
whatIsHoldExplain: 'Bloquear es nuestra forma de agilizar la colaboración financiera. ¡"Rechazar" es tan duro!',
holdIsTemporaryTitle: 'Bloquear suele ser temporal',
@@ -934,8 +928,7 @@ export default {
reasonForLeavingPrompt: '¡Lamentamos verte partir! ¿Serías tan amable de decirnos por qué, para que podamos mejorar?',
enterMessageHere: 'Escribe aquí tu mensaje',
closeAccountWarning: 'Una vez cerrada tu cuenta no se puede revertir.',
- closeAccountPermanentlyDeleteData:
- 'Esta acción eliminará permanentemente toda la información de tus gastos no enviados y cancelará o rechazará cualquier solicitud de dinero pendiente. ¿Estás seguro de que quieres eliminar tu cuenta?',
+ closeAccountPermanentlyDeleteData: '¿Estás seguro de que quieres eliminar tu cuenta? Esta acción eliminará permanentemente toda la información de cualquier gasto pendiente.',
enterDefaultContactToConfirm: 'Por favor, escribe tu método de contacto predeterminado para confirmar que deseas eliminar tu cuenta. Tu método de contacto predeterminado es:',
enterDefaultContact: 'Tu método de contacto predeterminado',
defaultContact: 'Método de contacto predeterminado:',
@@ -1267,19 +1260,6 @@ export default {
},
chooseThemeBelowOrSync: 'Elige un tema a continuación o sincronízalo con los ajustes de tu dispositivo.',
},
- signInPage: {
- expensifyDotCash: 'Nuevo Expensify',
- theCode: 'el código',
- openJobs: 'trabajos disponibles',
- heroHeading: 'Dividir cuentas\ny chatear con amigos.',
- heroDescription: {
- phrase1: 'El dinero habla. Y ahora que el chat y los pagos están en un solo lugar, también es fácil. Tus pagos te llegan tan rápido como puedes hacer llegar tu mensaje',
- phrase2: 'Nuevo Expensify es de código abierto. Vista',
- phrase3: 'el código',
- phrase4: 'Vista',
- phrase5: 'vacantes',
- },
- },
termsOfUse: {
phrase1: 'Al iniciar sesión, estás accediendo a los',
phrase2: 'Términos de Servicio',
@@ -1345,7 +1325,7 @@ export default {
[CONST.ONBOARDING_CHOICES.EMPLOYER]: 'Cobrar de mi empresa',
[CONST.ONBOARDING_CHOICES.MANAGE_TEAM]: 'Gestionar los gastos de mi equipo',
[CONST.ONBOARDING_CHOICES.PERSONAL_SPEND]: 'Controlar y presupuestar los gastos personales',
- [CONST.ONBOARDING_CHOICES.CHAT_SPLIT]: 'Chatea y divide cuentas con tus amigos',
+ [CONST.ONBOARDING_CHOICES.CHAT_SPLIT]: 'Chatea y divide gastos con tus amigos',
[CONST.ONBOARDING_CHOICES.LOOKING_AROUND]: 'Sólo estoy mirando',
},
error: {
@@ -1403,7 +1383,6 @@ export default {
localTime: 'Hora local',
},
newChatPage: {
- createChat: 'Crear chat',
startGroup: 'Grupo de inicio',
addToGroup: 'Añadir al grupo',
},
@@ -2103,12 +2082,23 @@ export default {
case 'quickbooksOnlineImportCustomers':
return 'Importando clientes';
case 'quickbooksOnlineImportEmployees':
- return 'Importing employees';
+ return 'Importando empleados';
case 'quickbooksOnlineImportAccounts':
- return 'Importing accounts';
+ return 'Importando cuentas';
case 'quickbooksOnlineImportClasses':
- return 'Importing classes';
-
+ return 'Importando clases';
+ case 'quickbooksOnlineImportLocations':
+ return 'Importando localidades';
+ case 'quickbooksOnlineImportProcessing':
+ return 'Procesando datos importados';
+ case 'quickbooksOnlineSyncBillPayments':
+ return 'Sincronizando reportes reembolsados y facturas pagadas';
+ case 'quickbooksOnlineSyncTaxCodes':
+ return 'Importando tipos de impuestos';
+ case 'quickbooksOnlineCheckConnection':
+ return 'Revisando conexión a QuickBooks Online';
+ case 'quickbooksOnlineImportMain':
+ return 'Importando datos desde QuickBooks Online';
default: {
return `Translation missing for stage: ${stage}`;
}
@@ -3019,7 +3009,6 @@ export default {
parentReportAction: {
deletedReport: '[Informe eliminado]',
deletedMessage: '[Mensaje eliminado]',
- deletedRequest: '[Solicitud eliminada]',
deletedExpense: '[Gasto eliminado]',
reversedTransaction: '[Transacción anulada]',
deletedTask: '[Tarea eliminada]',
@@ -3045,6 +3034,12 @@ export default {
accept: 'Aceptar',
decline: 'Rechazar',
},
+ actionableMentionTrackExpense: {
+ submit: 'Pedirle a alguien que lo pague',
+ categorize: 'Categorizarlo',
+ share: 'Compartirlo con mi contador',
+ nothing: 'Por ahora, nada',
+ },
moderation: {
flagDescription: 'Todos los mensajes marcados se enviarán a un moderador para su revisión.',
chooseAReason: 'Elige abajo un motivo para reportarlo:',
@@ -3142,27 +3137,27 @@ export default {
body: `¡Gana dinero por hablar con tus amigos! Inicia un chat con una cuenta nueva de Expensify y recibe $${CONST.REFERRAL_PROGRAM.REVENUE} cuando se conviertan en clientes.`,
},
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST]: {
- buttonText1: 'Pide dinero, ',
+ buttonText1: 'Presentar gasto, ',
buttonText2: `recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
- header: `Pide dinero y recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
- body: `¡Vale la pena cobrar! Pide dinero a una cuenta nueva de Expensify y recibe $${CONST.REFERRAL_PROGRAM.REVENUE} cuando se conviertan en clientes.`,
+ header: `Presenta un gasto y consigue $${CONST.REFERRAL_PROGRAM.REVENUE}`,
+ body: `¡Vale la pena cobrar! Envia un gasto a una cuenta nueva de Expensify y recibe $${CONST.REFERRAL_PROGRAM.REVENUE} cuando se conviertan en clientes.`,
},
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY]: {
- buttonText1: 'Envía dinero, ',
+ buttonText1: 'Pagar a alguien, ',
buttonText2: `recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
- header: `Envía dinero y recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
- body: `¡Hay que enviar dinero para ganar dinero! Envía dinero a una cuenta nueva de Expensify y recibe $${CONST.REFERRAL_PROGRAM.REVENUE} cuando se conviertan en clientes.`,
+ header: `Paga a alguien y recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
+ body: `¡Hay que gastar dinero para ganar dinero! Paga a alguien con Expensify y recibe $${CONST.REFERRAL_PROGRAM.REVENUE} cuando se conviertan en clientes.`,
},
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND]: {
buttonText1: 'Invita a un amigo y ',
buttonText2: `recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
header: `Recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
- body: `Sé el primero en chatear, enviar o pedir dinero, dividir una factura o compartir tu enlace de invitación con un amigo, y recibirás $${CONST.REFERRAL_PROGRAM.REVENUE} cuando se convierta en cliente. También puedes publicar tu enlace de invitación en las redes sociales.`,
+ body: `Chatea, paga, presenta y divide gastos con un amigo y recibirás $${CONST.REFERRAL_PROGRAM.REVENUE} cuando se convierta en cliente. También puedes publicar tu enlace de invitación en las redes sociales.`,
},
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SHARE_CODE]: {
buttonText1: `Recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
header: `Recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
- body: `Sé el primero en chatear, enviar o pedir dinero, dividir una factura o compartir tu enlace de invitación con un amigo, y recibirás $${CONST.REFERRAL_PROGRAM.REVENUE} cuando se convierta en cliente. También puedes publicar tu enlace de invitación en las redes sociales.`,
+ body: `Chatea, paga, presenta y divide gastos con un amigo y recibirás $${CONST.REFERRAL_PROGRAM.REVENUE} cuando se convierta en cliente. También puedes publicar tu enlace de invitación en las redes sociales.`,
},
copyReferralLink: 'Copiar enlace de invitación',
},
diff --git a/src/languages/types.ts b/src/languages/types.ts
index c365363f84af..30b7f842db4c 100644
--- a/src/languages/types.ts
+++ b/src/languages/types.ts
@@ -247,6 +247,8 @@ type ViolationsTagOutOfPolicyParams = {tagName?: string};
type ViolationsTaxOutOfPolicyParams = {taxName?: string};
+type PaySomeoneParams = {name?: string};
+
type TaskCreatedActionParams = {title: string};
/* Translation Object types */
@@ -400,4 +402,5 @@ export type {
ZipCodeExampleFormatParams,
LogSizeParams,
HeldRequestParams,
+ PaySomeoneParams,
};
diff --git a/src/libs/API/parameters/CategorizeTrackedExpenseParams.ts b/src/libs/API/parameters/CategorizeTrackedExpenseParams.ts
new file mode 100644
index 000000000000..78eb0adecc5e
--- /dev/null
+++ b/src/libs/API/parameters/CategorizeTrackedExpenseParams.ts
@@ -0,0 +1,25 @@
+import type {Receipt} from '@src/types/onyx/Transaction';
+
+type CategorizeTrackedExpenseParams = {
+ amount: number;
+ currency: string;
+ comment: string;
+ created: string;
+ merchant: string;
+ policyID: string;
+ transactionID: string;
+ moneyRequestPreviewReportActionID: string;
+ moneyRequestReportID: string;
+ moneyRequestCreatedReportActionID: string;
+ actionableWhisperReportActionID: string;
+ modifiedExpenseReportActionID: string;
+ reportPreviewReportActionID: string;
+ category?: string;
+ tag?: string;
+ receipt?: Receipt;
+ taxCode: string;
+ taxAmount: number;
+ billable?: boolean;
+};
+
+export default CategorizeTrackedExpenseParams;
diff --git a/src/libs/API/parameters/ConnectPolicyToQuickbooksOnlineParams.ts b/src/libs/API/parameters/ConnectPolicyToQuickbooksOnlineParams.ts
new file mode 100644
index 000000000000..4d889d69f5ad
--- /dev/null
+++ b/src/libs/API/parameters/ConnectPolicyToQuickbooksOnlineParams.ts
@@ -0,0 +1,5 @@
+type ConnectPolicyToQuickbooksOnlineParams = {
+ policyID: string;
+};
+
+export default ConnectPolicyToQuickbooksOnlineParams;
diff --git a/src/libs/API/parameters/ConvertTrackedExpenseToRequestParams.ts b/src/libs/API/parameters/ConvertTrackedExpenseToRequestParams.ts
new file mode 100644
index 000000000000..c51161b043a8
--- /dev/null
+++ b/src/libs/API/parameters/ConvertTrackedExpenseToRequestParams.ts
@@ -0,0 +1,21 @@
+import type {Receipt} from '@src/types/onyx/Transaction';
+
+type ConvertTrackedExpenseToRequestParams = {
+ amount: number;
+ currency: string;
+ created: string;
+ comment?: string;
+ merchant?: string;
+ payerAccountID: number;
+ chatReportID: string;
+ transactionID: string;
+ actionableWhisperReportActionID: string;
+ createdChatReportActionID: string;
+ receipt?: Receipt;
+ moneyRequestReportID: string;
+ moneyRequestCreatedReportActionID: string;
+ moneyRequestPreviewReportActionID: string;
+ reportPreviewReportActionID: string;
+};
+
+export default ConvertTrackedExpenseToRequestParams;
diff --git a/src/libs/API/parameters/DismissTrackExpenseActionableWhisperParams.ts b/src/libs/API/parameters/DismissTrackExpenseActionableWhisperParams.ts
new file mode 100644
index 000000000000..e441d100784d
--- /dev/null
+++ b/src/libs/API/parameters/DismissTrackExpenseActionableWhisperParams.ts
@@ -0,0 +1,5 @@
+type DismissTrackExpenseActionableWhisperParams = {
+ reportActionID: string;
+};
+
+export default DismissTrackExpenseActionableWhisperParams;
diff --git a/src/libs/API/parameters/OpenPolicyAccountingPageParams.ts b/src/libs/API/parameters/OpenPolicyAccountingPageParams.ts
new file mode 100644
index 000000000000..0ebaa58ef0d1
--- /dev/null
+++ b/src/libs/API/parameters/OpenPolicyAccountingPageParams.ts
@@ -0,0 +1,5 @@
+type OpenPolicyAccountingPageParams = {
+ policyID: string;
+};
+
+export default OpenPolicyAccountingPageParams;
diff --git a/src/libs/API/parameters/RemovePolicyConnectionParams.ts b/src/libs/API/parameters/RemovePolicyConnectionParams.ts
new file mode 100644
index 000000000000..a9a640bc8426
--- /dev/null
+++ b/src/libs/API/parameters/RemovePolicyConnectionParams.ts
@@ -0,0 +1,8 @@
+import type {PolicyConnectionName} from '@src/types/onyx/Policy';
+
+type RemovePolicyConnectionParams = {
+ policyID: string;
+ connectionName: PolicyConnectionName;
+};
+
+export default RemovePolicyConnectionParams;
diff --git a/src/libs/API/parameters/ShareTrackedExpenseParams.ts b/src/libs/API/parameters/ShareTrackedExpenseParams.ts
new file mode 100644
index 000000000000..cee4bc40d9ac
--- /dev/null
+++ b/src/libs/API/parameters/ShareTrackedExpenseParams.ts
@@ -0,0 +1,25 @@
+import type {Receipt} from '@src/types/onyx/Transaction';
+
+type ShareTrackedExpenseParams = {
+ amount: number;
+ currency: string;
+ comment: string;
+ created: string;
+ merchant: string;
+ policyID: string;
+ transactionID: string;
+ moneyRequestPreviewReportActionID: string;
+ moneyRequestReportID: string;
+ moneyRequestCreatedReportActionID: string;
+ actionableWhisperReportActionID: string;
+ modifiedExpenseReportActionID: string;
+ reportPreviewReportActionID: string;
+ category?: string;
+ tag?: string;
+ receipt?: Receipt;
+ taxCode: string;
+ taxAmount: number;
+ billable?: boolean;
+};
+
+export default ShareTrackedExpenseParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index 385aabf8acff..bfa89b5d3bd3 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -11,6 +11,7 @@ export type {default as BeginGoogleSignInParams} from './BeginGoogleSignInParams
export type {default as BeginSignInParams} from './BeginSignInParams';
export type {default as CloseAccountParams} from './CloseAccountParams';
export type {default as ConnectBankAccountParams} from './ConnectBankAccountParams';
+export type {default as ConnectPolicyToQuickbooksOnlineParams} from './ConnectPolicyToQuickbooksOnlineParams';
export type {default as DeleteContactMethodParams} from './DeleteContactMethodParams';
export type {default as DeletePaymentBankAccountParams} from './DeletePaymentBankAccountParams';
export type {default as DeletePaymentCardParams} from './DeletePaymentCardParams';
@@ -204,5 +205,11 @@ export type {default as SetPolicyCustomTaxNameParams} from './SetPolicyCustomTax
export type {default as SetPolicyForeignCurrencyDefaultParams} from './SetPolicyForeignCurrencyDefaultParams';
export type {default as SetPolicyCurrencyDefaultParams} from './SetPolicyCurrencyDefaultParams';
export type {default as UpdatePolicyConnectionConfigParams} from './UpdatePolicyConnectionConfigParams';
+export type {default as RemovePolicyConnectionParams} from './RemovePolicyConnectionParams';
export type {default as RenamePolicyTaxParams} from './RenamePolicyTaxParams';
+export type {default as DismissTrackExpenseActionableWhisperParams} from './DismissTrackExpenseActionableWhisperParams';
+export type {default as ConvertTrackedExpenseToRequestParams} from './ConvertTrackedExpenseToRequestParams';
+export type {default as ShareTrackedExpenseParams} from './ShareTrackedExpenseParams';
+export type {default as CategorizeTrackedExpenseParams} from './CategorizeTrackedExpenseParams';
export type {default as LeavePolicyParams} from './LeavePolicyParams';
+export type {default as OpenPolicyAccountingPageParams} from './OpenPolicyAccountingPageParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index 91b95dd6327e..f91b694548ba 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -193,6 +193,7 @@ const WRITE_COMMANDS = {
DECLINE_JOIN_REQUEST: 'DeclineJoinRequest',
CREATE_POLICY_TAX: 'CreatePolicyTax',
UPDATE_POLICY_CONNECTION_CONFIG: 'UpdatePolicyConnectionConfig',
+ REMOVE_POLICY_CONNECTION: 'RemovePolicyConnection',
SET_POLICY_TAXES_ENABLED: 'SetPolicyTaxesEnabled',
DELETE_POLICY_TAXES: 'DeletePolicyTaxes',
UPDATE_POLICY_TAX_VALUE: 'UpdatePolicyTaxValue',
@@ -205,6 +206,10 @@ const WRITE_COMMANDS = {
UPDATE_POLICY_DISTANCE_RATE_VALUE: 'UpdatePolicyDistanceRateValue',
SET_POLICY_DISTANCE_RATES_ENABLED: 'SetPolicyDistanceRatesEnabled',
DELETE_POLICY_DISTANCE_RATES: 'DeletePolicyDistanceRates',
+ DISMISS_TRACK_EXPENSE_ACTIONABLE_WHISPER: 'DismissActionableWhisper',
+ CONVERT_TRACKED_EXPENSE_TO_REQUEST: 'ConvertTrackedExpenseToRequest',
+ CATEGORIZE_TRACKED_EXPENSE: 'CategorizeTrackedExpense',
+ SHARE_TRACKED_EXPENSE: 'ShareTrackedExpense',
LEAVE_POLICY: 'LeavePolicy',
} as const;
@@ -405,15 +410,22 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.RENAME_POLICY_TAX]: Parameters.RenamePolicyTaxParams;
[WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_UNIT]: Parameters.SetPolicyDistanceRatesUnitParams;
[WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_DEFAULT_CATEGORY]: Parameters.SetPolicyDistanceRatesDefaultCategoryParams;
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[WRITE_COMMANDS.UPDATE_POLICY_CONNECTION_CONFIG]: Parameters.UpdatePolicyConnectionConfigParams;
+ [WRITE_COMMANDS.REMOVE_POLICY_CONNECTION]: Parameters.RemovePolicyConnectionParams;
[WRITE_COMMANDS.UPDATE_POLICY_DISTANCE_RATE_VALUE]: Parameters.UpdatePolicyDistanceRateValueParams;
[WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_ENABLED]: Parameters.SetPolicyDistanceRatesEnabledParams;
[WRITE_COMMANDS.DELETE_POLICY_DISTANCE_RATES]: Parameters.DeletePolicyDistanceRatesParams;
+ [WRITE_COMMANDS.DISMISS_TRACK_EXPENSE_ACTIONABLE_WHISPER]: Parameters.DismissTrackExpenseActionableWhisperParams;
+ [WRITE_COMMANDS.CONVERT_TRACKED_EXPENSE_TO_REQUEST]: Parameters.ConvertTrackedExpenseToRequestParams;
+ [WRITE_COMMANDS.CATEGORIZE_TRACKED_EXPENSE]: Parameters.CategorizeTrackedExpenseParams;
+ [WRITE_COMMANDS.SHARE_TRACKED_EXPENSE]: Parameters.ShareTrackedExpenseParams;
[WRITE_COMMANDS.LEAVE_POLICY]: Parameters.LeavePolicyParams;
};
const READ_COMMANDS = {
+ CONNECT_POLICY_TO_QUICKBOOKS_ONLINE: 'ConnectPolicyToQuickbooksOnline',
OPEN_REIMBURSEMENT_ACCOUNT_PAGE: 'OpenReimbursementAccountPage',
OPEN_WORKSPACE_VIEW: 'OpenWorkspaceView',
GET_MAPBOX_ACCESS_TOKEN: 'GetMapboxAccessToken',
@@ -449,11 +461,13 @@ const READ_COMMANDS = {
OPEN_POLICY_WORKFLOWS_PAGE: 'OpenPolicyWorkflowsPage',
OPEN_POLICY_DISTANCE_RATES_PAGE: 'OpenPolicyDistanceRatesPage',
OPEN_POLICY_MORE_FEATURES_PAGE: 'OpenPolicyMoreFeaturesPage',
+ OPEN_POLICY_ACCOUNTING_PAGE: 'OpenPolicyAccountingPage',
} as const;
type ReadCommand = ValueOf;
type ReadCommandParameters = {
+ [READ_COMMANDS.CONNECT_POLICY_TO_QUICKBOOKS_ONLINE]: Parameters.ConnectPolicyToQuickbooksOnlineParams;
[READ_COMMANDS.OPEN_REIMBURSEMENT_ACCOUNT_PAGE]: Parameters.OpenReimbursementAccountPageParams;
[READ_COMMANDS.OPEN_WORKSPACE_VIEW]: Parameters.OpenWorkspaceViewParams;
[READ_COMMANDS.GET_MAPBOX_ACCESS_TOKEN]: EmptyObject;
@@ -489,6 +503,7 @@ type ReadCommandParameters = {
[READ_COMMANDS.OPEN_POLICY_WORKFLOWS_PAGE]: Parameters.OpenPolicyWorkflowsPageParams;
[READ_COMMANDS.OPEN_POLICY_DISTANCE_RATES_PAGE]: Parameters.OpenPolicyDistanceRatesPageParams;
[READ_COMMANDS.OPEN_POLICY_MORE_FEATURES_PAGE]: Parameters.OpenPolicyMoreFeaturesPageParams;
+ [READ_COMMANDS.OPEN_POLICY_ACCOUNTING_PAGE]: Parameters.OpenPolicyAccountingPageParams;
};
const SIDE_EFFECT_REQUEST_COMMANDS = {
diff --git a/src/libs/ApiUtils.ts b/src/libs/ApiUtils.ts
index 0c8fa3f53915..1da9795f333b 100644
--- a/src/libs/ApiUtils.ts
+++ b/src/libs/ApiUtils.ts
@@ -38,12 +38,14 @@ function getApiRoot(request?: Request): string {
const shouldUseSecure = request?.shouldUseSecure ?? false;
if (shouldUseStagingServer) {
- if (CONFIG.IS_USING_WEB_PROXY) {
+ if (CONFIG.IS_USING_WEB_PROXY && !request?.shouldSkipWebProxy) {
return shouldUseSecure ? proxyConfig.STAGING_SECURE : proxyConfig.STAGING;
}
return shouldUseSecure ? CONFIG.EXPENSIFY.STAGING_SECURE_API_ROOT : CONFIG.EXPENSIFY.STAGING_API_ROOT;
}
-
+ if (request?.shouldSkipWebProxy) {
+ return shouldUseSecure ? CONFIG.EXPENSIFY.SECURE_EXPENSIFY_URL : CONFIG.EXPENSIFY.EXPENSIFY_URL;
+ }
return shouldUseSecure ? CONFIG.EXPENSIFY.DEFAULT_SECURE_API_ROOT : CONFIG.EXPENSIFY.DEFAULT_API_ROOT;
}
diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts
index c4d67adcd54a..9e3a7f66131a 100644
--- a/src/libs/CardUtils.ts
+++ b/src/libs/CardUtils.ts
@@ -127,7 +127,7 @@ function maskCard(lastFour = ''): string {
* @returns a physical card object (or undefined if none is found)
*/
function findPhysicalCard(cards: Card[]) {
- return cards.find((card) => !card.isVirtual);
+ return cards.find((card) => !card.nameValuePairs?.isVirtual);
}
/**
diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts
index 44c7682b47f2..9b96bfa009dc 100644
--- a/src/libs/DateUtils.ts
+++ b/src/libs/DateUtils.ts
@@ -1,6 +1,7 @@
import {
addDays,
addHours,
+ addMilliseconds,
addMinutes,
eachDayOfInterval,
eachMonthOfInterval,
@@ -392,6 +393,13 @@ function subtractMillisecondsFromDateTime(dateTime: string, milliseconds: number
return getDBTime(newTimestamp);
}
+function addMillisecondsFromDateTime(dateTime: string, milliseconds: number): string {
+ const date = zonedTimeToUtc(dateTime, 'UTC');
+ const newTimestamp = addMilliseconds(date, milliseconds).valueOf();
+
+ return getDBTime(newTimestamp);
+}
+
/**
* @param isoTimestamp example: 2023-05-16 05:34:14.388
* @returns example: 2023-05-16
@@ -784,6 +792,7 @@ const DateUtils = {
getDBTimeWithSkew,
setLocale,
subtractMillisecondsFromDateTime,
+ addMillisecondsFromDateTime,
getDateStringFromISOTimestamp,
getThirtyMinutesFromNow,
getEndOfToday,
diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts
index 9a7e0a568627..12a240ae9041 100644
--- a/src/libs/DistanceRequestUtils.ts
+++ b/src/libs/DistanceRequestUtils.ts
@@ -79,7 +79,7 @@ function getRoundedDistanceInUnits(distanceInMeters: number, unit: Unit): string
}
/**
- * @param hasRoute Whether the route exists for the distance request
+ * @param hasRoute Whether the route exists for the distance expense
* @param distanceInMeters Distance traveled
* @param unit Unit that should be used to display the distance
* @param rate Expensable amount allowed per unit
@@ -100,7 +100,7 @@ function getDistanceForDisplay(hasRoute: boolean, distanceInMeters: number, unit
}
/**
- * @param hasRoute Whether the route exists for the distance request
+ * @param hasRoute Whether the route exists for the distance expense
* @param distanceInMeters Distance traveled
* @param unit Unit that should be used to display the distance
* @param rate Expensable amount allowed per unit
@@ -133,12 +133,12 @@ function getDistanceMerchant(
}
/**
- * Calculates the request amount based on distance, unit, and rate.
+ * Calculates the expense amount based on distance, unit, and rate.
*
* @param distance - The distance traveled in meters
* @param unit - The unit of measurement for the distance
- * @param rate - Rate used for calculating the request amount
- * @returns The computed request amount (rounded) in "cents".
+ * @param rate - Rate used for calculating the expense amount
+ * @returns The computed expense amount (rounded) in "cents".
*/
function getDistanceRequestAmount(distance: number, unit: Unit, rate: number): number {
const convertedDistance = convertDistanceUnit(distance, unit);
diff --git a/src/libs/E2E/reactNativeLaunchingTest.ts b/src/libs/E2E/reactNativeLaunchingTest.ts
index 776e3de74f06..9d5b0be0d2e7 100644
--- a/src/libs/E2E/reactNativeLaunchingTest.ts
+++ b/src/libs/E2E/reactNativeLaunchingTest.ts
@@ -35,7 +35,7 @@ if (!appInstanceId) {
// import your test here, define its name and config first in e2e/config.js
const tests: Tests = {
[E2EConfig.TEST_NAMES.AppStartTime]: require('./tests/appStartTimeTest.e2e').default,
- [E2EConfig.TEST_NAMES.OpenSearchPage]: require('./tests/openSearchPageTest.e2e').default,
+ [E2EConfig.TEST_NAMES.OpenChatFinderPage]: require('./tests/openChatFinderPageTest.e2e').default,
[E2EConfig.TEST_NAMES.ChatOpening]: require('./tests/chatOpeningTest.e2e').default,
[E2EConfig.TEST_NAMES.ReportTyping]: require('./tests/reportTypingTest.e2e').default,
[E2EConfig.TEST_NAMES.Linking]: require('./tests/linkingTest.e2e').default,
diff --git a/src/libs/E2E/tests/openSearchPageTest.e2e.ts b/src/libs/E2E/tests/openChatFinderPageTest.e2e.ts
similarity index 82%
rename from src/libs/E2E/tests/openSearchPageTest.e2e.ts
rename to src/libs/E2E/tests/openChatFinderPageTest.e2e.ts
index 86da851396f6..9d2b117a7044 100644
--- a/src/libs/E2E/tests/openSearchPageTest.e2e.ts
+++ b/src/libs/E2E/tests/openChatFinderPageTest.e2e.ts
@@ -9,7 +9,7 @@ import ROUTES from '@src/ROUTES';
const test = () => {
// check for login (if already logged in the action will simply resolve)
- console.debug('[E2E] Logging in for search');
+ console.debug('[E2E] Logging in for chat finder');
E2ELogin().then((neededLogin: boolean): Promise | undefined => {
if (neededLogin) {
@@ -19,24 +19,24 @@ const test = () => {
);
}
- console.debug('[E2E] Logged in, getting search metrics and submitting them…');
+ console.debug('[E2E] Logged in, getting chat finder metrics and submitting them…');
Performance.subscribeToMeasurements((entry) => {
if (entry.name === CONST.TIMING.SIDEBAR_LOADED) {
- console.debug(`[E2E] Sidebar loaded, navigating to search route…`);
- Navigation.navigate(ROUTES.SEARCH);
+ console.debug(`[E2E] Sidebar loaded, navigating to chat finder route…`);
+ Navigation.navigate(ROUTES.CHAT_FINDER);
return;
}
console.debug(`[E2E] Entry: ${JSON.stringify(entry)}`);
- if (entry.name !== CONST.TIMING.SEARCH_RENDER) {
+ if (entry.name !== CONST.TIMING.CHAT_FINDER_RENDER) {
return;
}
console.debug(`[E2E] Submitting!`);
E2EClient.submitTestResults({
branch: Config.E2E_BRANCH,
- name: 'Open Search Page TTI',
+ name: 'Open Chat Finder Page TTI',
duration: entry.duration,
})
.then(() => {
diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts
index 415872750243..27eff132ef40 100644
--- a/src/libs/IOUUtils.ts
+++ b/src/libs/IOUUtils.ts
@@ -8,7 +8,17 @@ import * as CurrencyUtils from './CurrencyUtils';
import Navigation from './Navigation/Navigation';
import * as TransactionUtils from './TransactionUtils';
-function navigateToStartMoneyRequestStep(requestType: IOURequestType, iouType: ValueOf, transactionID: string, reportID: string) {
+function navigateToStartMoneyRequestStep(
+ requestType: IOURequestType,
+ iouType: ValueOf,
+ transactionID: string,
+ reportID: string,
+ iouAction?: ValueOf,
+): void {
+ if (iouAction === CONST.IOU.ACTION.CATEGORIZE || iouAction === CONST.IOU.ACTION.MOVE) {
+ Navigation.goBack();
+ return;
+ }
// If the participants were automatically added to the transaction, then the user needs taken back to the starting step
switch (requestType) {
case CONST.IOU.REQUEST_TYPE.DISTANCE:
@@ -53,8 +63,8 @@ function calculateAmount(numberOfParticipants: number, total: number, currency:
* For example: if user1 owes user2 $10, then we have: {ownerAccountID: user2, managerID: user1, total: $10 (a positive amount, owed to user2)}
* If user1 requests $17 from user2, then we have: {ownerAccountID: user1, managerID: user2, total: $7 (still a positive amount, but now owed to user1)}
*
- * @param isDeleting - whether the user is deleting the request
- * @param isUpdating - whether the user is updating the request
+ * @param isDeleting - whether the user is deleting the expense
+ * @param isUpdating - whether the user is updating the expense
*/
function updateIOUOwnerAndTotal>(
iouReport: TReport,
@@ -92,7 +102,7 @@ function updateIOUOwnerAndTotal>(
}
/**
- * Returns whether or not an IOU report contains money requests in a different currency
+ * Returns whether or not an IOU report contains expenses in a different currency
* that are either created or cancelled offline, and thus haven't been converted to the report's currency yet
*/
function isIOUReportPendingCurrencyConversion(iouReport: Report): boolean {
@@ -124,4 +134,20 @@ function insertTagIntoTransactionTagsString(transactionTags: string, tag: string
return tagArray.join(CONST.COLON).replace(/:*$/, '');
}
-export {calculateAmount, updateIOUOwnerAndTotal, isIOUReportPendingCurrencyConversion, isValidMoneyRequestType, navigateToStartMoneyRequestStep, insertTagIntoTransactionTagsString};
+function isMovingTransactionFromTrackExpense(action?: ValueOf) {
+ if (action === CONST.IOU.ACTION.MOVE || action === CONST.IOU.ACTION.SHARE || action === CONST.IOU.ACTION.CATEGORIZE) {
+ return true;
+ }
+
+ return false;
+}
+
+export {
+ calculateAmount,
+ updateIOUOwnerAndTotal,
+ isIOUReportPendingCurrencyConversion,
+ isValidMoneyRequestType,
+ navigateToStartMoneyRequestStep,
+ insertTagIntoTransactionTagsString,
+ isMovingTransactionFromTrackExpense,
+};
diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts
index 0d961ea27115..d73771734636 100644
--- a/src/libs/ModifiedExpenseMessage.ts
+++ b/src/libs/ModifiedExpenseMessage.ts
@@ -253,7 +253,7 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr
buildMessageFragmentForValue(
reportActionOriginalMessage?.billable ?? '',
reportActionOriginalMessage?.oldBillable ?? '',
- Localize.translateLocal('iou.request'),
+ Localize.translateLocal('iou.expense'),
true,
setFragments,
removalFragments,
@@ -266,7 +266,7 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr
getMessageLine(`\n${Localize.translateLocal('iou.set')}`, setFragments) +
getMessageLine(`\n${Localize.translateLocal('iou.removed')}`, removalFragments);
if (message === '') {
- return Localize.translateLocal('iou.changedTheRequest');
+ return Localize.translateLocal('iou.changedTheExpense');
}
return `${message.substring(1, message.length)}`;
}
diff --git a/src/libs/MoneyRequestUtils.ts b/src/libs/MoneyRequestUtils.ts
index 1a573ce74628..ec934cb87888 100644
--- a/src/libs/MoneyRequestUtils.ts
+++ b/src/libs/MoneyRequestUtils.ts
@@ -78,14 +78,14 @@ function replaceAllDigits(text: string, convertFn: (char: string) => string): st
}
/**
- * Check if distance request or not
+ * Check if distance expense or not
*/
function isDistanceRequest(iouType: ValueOf, selectedTab: OnyxEntry): boolean {
return iouType === CONST.IOU.TYPE.REQUEST && selectedTab === CONST.TAB_REQUEST.DISTANCE;
}
/**
- * Check if scan request or not
+ * Check if scan expense or not
*/
function isScanRequest(selectedTab: SelectedTabRequest): boolean {
return selectedTab === CONST.TAB_REQUEST.SCAN;
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
index fde0202d3d2f..9157d7486c9e 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
@@ -238,7 +238,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
const unsubscribeSearchShortcut = KeyboardShortcut.subscribe(
searchShortcutConfig.shortcutKey,
() => {
- Modal.close(Session.checkIfActionIsAllowed(() => Navigation.navigate(ROUTES.SEARCH)));
+ Modal.close(Session.checkIfActionIsAllowed(() => Navigation.navigate(ROUTES.CHAT_FINDER)));
},
shortcutsOverviewShortcutConfig.descriptionKey,
shortcutsOverviewShortcutConfig.modifiers,
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/WorkspaceSettingsModalStackNavigator.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/WorkspaceSettingsModalStackNavigator.tsx
index 2dce4247c7ae..172b62239131 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/WorkspaceSettingsModalStackNavigator.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/WorkspaceSettingsModalStackNavigator.tsx
@@ -52,9 +52,9 @@ function WorkspaceSettingsModalStackNavigator() {
/>
require('@pages/workspace/accounting/WorkspaceAccountingPage').default as React.ComponentType}
+ key={SCREENS.WORKSPACE.ACCOUNTING.ROOT}
+ name={SCREENS.WORKSPACE.ACCOUNTING.ROOT}
+ getComponent={() => require('@pages/workspace/accounting/PolicyAccountingPage').default as React.ComponentType}
/>
require('../../../../pages/RoomInvitePage').default as React.ComponentType,
});
-const SearchModalStackNavigator = createModalStackNavigator({
- [SCREENS.SEARCH_ROOT]: () => require('../../../../pages/SearchPage').default as React.ComponentType,
+const ChatFinderModalStackNavigator = createModalStackNavigator({
+ [SCREENS.CHAT_FINDER_ROOT]: () => require('../../../../pages/ChatFinderPage').default as React.ComponentType,
});
const NewChatModalStackNavigator = createModalStackNavigator({
@@ -262,12 +262,13 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/ExitSurvey/ExitSurveyReasonPage').default as React.ComponentType,
[SCREENS.SETTINGS.EXIT_SURVEY.RESPONSE]: () => require('../../../../pages/settings/ExitSurvey/ExitSurveyResponsePage').default as React.ComponentType,
[SCREENS.SETTINGS.EXIT_SURVEY.CONFIRM]: () => require('../../../../pages/settings/ExitSurvey/ExitSurveyConfirmPage').default as React.ComponentType,
- [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_IMPORT]: () => require('../../../../pages/workspace/accounting/qbo/QuickbooksImportPage').default as React.ComponentType,
- [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS]: () => require('../../../../pages/workspace/accounting/qbo/QuickbooksChartOfAccountsPage').default as React.ComponentType,
- [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CUSTOMERS]: () => require('../../../../pages/workspace/accounting/qbo/QuickbooksCustomersPage').default as React.ComponentType,
- [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_TAXES]: () => require('../../../../pages/workspace/accounting/qbo/QuickbooksTaxesPage').default as React.ComponentType,
- [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_LOCATIONS]: () => require('../../../../pages/workspace/accounting/qbo/QuickbooksLocationsPage').default as React.ComponentType,
- [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CLASSES]: () => require('../../../../pages/workspace/accounting/qbo/QuickbooksClassesPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_IMPORT]: () => require('../../../../pages/workspace/accounting/qbo/QuickbooksImportPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS]: () =>
+ require('../../../../pages/workspace/accounting/qbo/QuickbooksChartOfAccountsPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_CUSTOMERS]: () => require('../../../../pages/workspace/accounting/qbo/QuickbooksCustomersPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_TAXES]: () => require('../../../../pages/workspace/accounting/qbo/QuickbooksTaxesPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_LOCATIONS]: () => require('../../../../pages/workspace/accounting/qbo/QuickbooksLocationsPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_CLASSES]: () => require('../../../../pages/workspace/accounting/qbo/QuickbooksClassesPage').default as React.ComponentType,
[SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: () => require('../../../../pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage').default as React.ComponentType,
[SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET]: () =>
require('../../../../pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage').default as React.ComponentType,
@@ -342,7 +343,7 @@ export {
ReportDescriptionModalStackNavigator,
RoomInviteModalStackNavigator,
RoomMembersModalStackNavigator,
- SearchModalStackNavigator,
+ ChatFinderModalStackNavigator,
SettingsModalStackNavigator,
SignInModalStackNavigator,
SplitDetailsModalStackNavigator,
diff --git a/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx
index 8f76d8fbdd7b..159430a66a43 100644
--- a/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx
+++ b/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx
@@ -32,8 +32,8 @@ function LeftModalNavigator({navigation}: LeftModalNavigatorProps) {
;
- /** Members of all the workspaces the user is member of */
- policyMembers: OnyxCollection;
+ /** The personal details of the person who is logged in */
+ personalDetails: OnyxEntry;
/** Whether user is a new user */
isFirstTimeNewExpensifyUser: OnyxEntry;
@@ -58,7 +58,7 @@ const getLastAccessedReportID = (
};
// This wrapper is reponsible for opening the last accessed report if there is no reportID specified in the route params
-function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, navigation, isFirstTimeNewExpensifyUser = false, reportMetadata, accountID}: ReportScreenIDSetterProps) {
+function ReportScreenIDSetter({route, reports, policies, navigation, isFirstTimeNewExpensifyUser = false, reportMetadata, accountID, personalDetails}: ReportScreenIDSetterProps) {
const {canUseDefaultRooms} = usePermissions();
const {activeWorkspaceID} = useActiveWorkspace();
@@ -73,7 +73,7 @@ function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, nav
return;
}
- const policyMemberAccountIDs = getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, accountID);
+ const policyMemberAccountIDs = getPolicyEmployeeListByIdWithoutCurrentUser(policies, activeWorkspaceID, accountID);
// If there is no reportID in route, try to find last accessed and use it for setParams
const reportID = getLastAccessedReportID(
@@ -92,7 +92,7 @@ function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, nav
if (reportID) {
navigation.setParams({reportID: String(reportID)});
}
- }, [route, navigation, reports, canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, reportMetadata, activeWorkspaceID, policyMembers, accountID]);
+ }, [route, navigation, reports, canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, reportMetadata, activeWorkspaceID, personalDetails, accountID]);
// The ReportScreen without the reportID set will display a skeleton
// until the reportID is loaded and set in the route param
@@ -110,10 +110,6 @@ export default withOnyx session?.accountID,
},
+ personalDetails: {
+ key: ONYXKEYS.PERSONAL_DETAILS_LIST,
+ },
})(ReportScreenIDSetter);
diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx
index fd5282a8cfcd..a06c9d08c529 100644
--- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx
+++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx
@@ -66,7 +66,7 @@ function TopBar({policy, session}: TopBarProps) {
Navigation.navigate(ROUTES.SEARCH))}
+ onPress={Session.checkIfActionIsAllowed(() => Navigation.navigate(ROUTES.CHAT_FINDER))}
>
);
- const policyMemberAccountIDs = getPolicyMemberAccountIDs(policyID);
+ const policyMemberAccountIDs = getPolicyEmployeeAccountIDs(policyID);
const shouldOpenAllWorkspace = isEmptyObject(targetReport) ? true : !doesReportBelongToWorkspace(targetReport, policyMemberAccountIDs, policyID);
if (shouldOpenAllWorkspace) {
diff --git a/src/libs/Navigation/getTopmostBottomTabRoute.ts b/src/libs/Navigation/getTopmostBottomTabRoute.ts
index a589f2cbc837..48a8d80f4096 100644
--- a/src/libs/Navigation/getTopmostBottomTabRoute.ts
+++ b/src/libs/Navigation/getTopmostBottomTabRoute.ts
@@ -1,10 +1,11 @@
+import NAVIGATORS from '@src/NAVIGATORS';
import type {BottomTabName, NavigationPartialRoute, RootStackParamList, State} from './types';
function getTopmostBottomTabRoute(state: State | undefined): NavigationPartialRoute | undefined {
- const bottomTabNavigatorRoute = state?.routes[0];
+ const bottomTabNavigatorRoute = state?.routes.findLast((route) => route.name === NAVIGATORS.BOTTOM_TAB_NAVIGATOR);
// The bottomTabNavigatorRoute state may be empty if we just logged in.
- if (!bottomTabNavigatorRoute || bottomTabNavigatorRoute.name !== 'BottomTabNavigator' || bottomTabNavigatorRoute.state === undefined) {
+ if (!bottomTabNavigatorRoute || bottomTabNavigatorRoute.name !== NAVIGATORS.BOTTOM_TAB_NAVIGATOR || bottomTabNavigatorRoute.state === undefined) {
return undefined;
}
diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
index 14e007c4c6d5..af57b4e77693 100755
--- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
+++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
@@ -18,13 +18,13 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = {
SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET,
SCREENS.WORKSPACE.WORKFLOWS_PAYER,
],
- [SCREENS.WORKSPACE.ACCOUNTING]: [
- SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_IMPORT,
- SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS,
- SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CLASSES,
- SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_TAXES,
- SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_LOCATIONS,
- SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CUSTOMERS,
+ [SCREENS.WORKSPACE.ACCOUNTING.ROOT]: [
+ SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_IMPORT,
+ SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS,
+ SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_CLASSES,
+ SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_TAXES,
+ SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_LOCATIONS,
+ SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_CUSTOMERS,
],
[SCREENS.WORKSPACE.TAXES]: [
SCREENS.WORKSPACE.TAXES_SETTINGS,
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 6165ccb16fa3..67ffcf43dece 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -71,9 +71,9 @@ const config: LinkingOptions['config'] = {
[SCREENS.NOT_FOUND]: '*',
[NAVIGATORS.LEFT_MODAL_NAVIGATOR]: {
screens: {
- [SCREENS.LEFT_MODAL.SEARCH]: {
+ [SCREENS.LEFT_MODAL.CHAT_FINDER]: {
screens: {
- [SCREENS.SEARCH_ROOT]: ROUTES.SEARCH,
+ [SCREENS.CHAT_FINDER_ROOT]: ROUTES.CHAT_FINDER,
},
},
[SCREENS.LEFT_MODAL.WORKSPACE_SWITCHER]: {
@@ -263,12 +263,12 @@ const config: LinkingOptions['config'] = {
[SCREENS.WORKSPACE.CURRENCY]: {
path: ROUTES.WORKSPACE_PROFILE_CURRENCY.route,
},
- [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_IMPORT]: {path: ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_IMPORT.route},
- [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS]: {path: ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS.route},
- [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CLASSES]: {path: ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CLASSES.route},
- [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CUSTOMERS]: {path: ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CUSTOMERS.route},
- [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_LOCATIONS]: {path: ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_LOCATIONS.route},
- [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_TAXES]: {path: ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_TAXES.route},
+ [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_IMPORT]: {path: ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_IMPORT.route},
+ [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS]: {path: ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS.route},
+ [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_CLASSES]: {path: ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_CLASSES.route},
+ [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_CUSTOMERS]: {path: ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_CUSTOMERS.route},
+ [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_LOCATIONS]: {path: ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_LOCATIONS.route},
+ [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_TAXES]: {path: ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_TAXES.route},
[SCREENS.WORKSPACE.DESCRIPTION]: {
path: ROUTES.WORKSPACE_PROFILE_DESCRIPTION.route,
},
@@ -669,8 +669,8 @@ const config: LinkingOptions['config'] = {
[SCREENS.WORKSPACE.MEMBERS]: {
path: ROUTES.WORKSPACE_MEMBERS.route,
},
- [SCREENS.WORKSPACE.ACCOUNTING]: {
- path: ROUTES.WORKSPACE_ACCOUNTING.route,
+ [SCREENS.WORKSPACE.ACCOUNTING.ROOT]: {
+ path: ROUTES.POLICY_ACCOUNTING.route,
},
[SCREENS.WORKSPACE.CATEGORIES]: {
path: ROUTES.WORKSPACE_CATEGORIES.route,
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 89bcddfe6daf..7dd2f274aa9e 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -244,22 +244,22 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.DISTANCE_RATES_SETTINGS]: {
policyID: string;
};
- [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_IMPORT]: {
+ [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_IMPORT]: {
policyID: string;
};
- [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS]: {
+ [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS]: {
policyID: string;
};
- [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_LOCATIONS]: {
+ [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_LOCATIONS]: {
policyID: string;
};
- [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CLASSES]: {
+ [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_CLASSES]: {
policyID: string;
};
- [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_CUSTOMERS]: {
+ [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_CUSTOMERS]: {
policyID: string;
};
- [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_TAXES]: {
+ [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_TAXES]: {
policyID: string;
};
[SCREENS.GET_ASSISTANCE]: {
@@ -300,8 +300,8 @@ type NewChatNavigatorParamList = {
};
};
-type SearchNavigatorParamList = {
- [SCREENS.SEARCH_ROOT]: undefined;
+type ChatFinderNavigatorParamList = {
+ [SCREENS.CHAT_FINDER_ROOT]: undefined;
};
type DetailsNavigatorParamList = {
@@ -361,6 +361,7 @@ type RoomMembersNavigatorParamList = {
type RoomInviteNavigatorParamList = {
[SCREENS.ROOM_INVITE_ROOT]: {
reportID: string;
+ role?: 'accountant';
};
};
@@ -477,6 +478,7 @@ type MoneyRequestNavigatorParamList = {
currency?: string;
};
[SCREENS.MONEY_REQUEST.STEP_PARTICIPANTS]: {
+ action: ValueOf;
iouType: ValueOf;
transactionID: string;
reportID: string;
@@ -612,7 +614,7 @@ type PrivateNotesNavigatorParamList = {
};
type LeftModalNavigatorParamList = {
- [SCREENS.LEFT_MODAL.SEARCH]: NavigatorScreenParams;
+ [SCREENS.LEFT_MODAL.CHAT_FINDER]: NavigatorScreenParams;
[SCREENS.LEFT_MODAL.WORKSPACE_SWITCHER]: NavigatorScreenParams;
};
@@ -679,7 +681,7 @@ type WorkspacesCentralPaneNavigatorParamList = {
[SCREENS.WORKSPACE.MEMBERS]: {
policyID: string;
};
- [SCREENS.WORKSPACE.ACCOUNTING]: {
+ [SCREENS.WORKSPACE.ACCOUNTING.ROOT]: {
policyID: string;
};
[SCREENS.WORKSPACE.CATEGORIES]: {
@@ -698,7 +700,7 @@ type WorkspacesCentralPaneNavigatorParamList = {
[SCREENS.WORKSPACE.DISTANCE_RATES]: {
policyID: string;
};
- [SCREENS.WORKSPACE.ACCOUNTING]: {
+ [SCREENS.WORKSPACE.ACCOUNTING.ROOT]: {
policyID: string;
};
};
@@ -788,7 +790,7 @@ type AuthScreensParamList = SharedScreensParamList & {
};
};
-type RootStackParamList = PublicScreensParamList & AuthScreensParamList & SearchNavigatorParamList;
+type RootStackParamList = PublicScreensParamList & AuthScreensParamList & ChatFinderNavigatorParamList;
type BottomTabName = keyof BottomTabNavigatorParamList;
@@ -832,7 +834,7 @@ export type {
ParticipantsNavigatorParamList,
RoomMembersNavigatorParamList,
RoomInviteNavigatorParamList,
- SearchNavigatorParamList,
+ ChatFinderNavigatorParamList,
NewChatNavigatorParamList,
NewTaskNavigatorParamList,
TeachersUniteNavigatorParamList,
diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts
index 0e76596ba8fa..3b8fce748f45 100644
--- a/src/libs/NextStepUtils.ts
+++ b/src/libs/NextStepUtils.ts
@@ -230,7 +230,7 @@ function buildNextStep(
};
// Self review & another reviewer
- if (isOwner) {
+ if (!isSelfApproval || (isSelfApproval && isOwner)) {
optimisticNextStep.message = [
{
text: 'Waiting for ',
diff --git a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts
index 36051fa35c56..4b17adf86841 100644
--- a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts
+++ b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts
@@ -3,7 +3,7 @@ import * as OnyxUpdates from '@libs/actions/OnyxUpdates';
import * as ActiveClientManager from '@libs/ActiveClientManager';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
-import getPolicyMemberAccountIDs from '@libs/PolicyMembersUtils';
+import getPolicyEmployeeAccountIDs from '@libs/PolicyEmployeeListUtils';
import {extractPolicyIDFromPath} from '@libs/PolicyUtils';
import {doesReportBelongToWorkspace, getReport} from '@libs/ReportUtils';
import Visibility from '@libs/Visibility';
@@ -65,9 +65,9 @@ export default function subscribeToReportCommentPushNotifications() {
const policyID = lastVisitedPath && extractPolicyIDFromPath(lastVisitedPath);
const report = getReport(reportID.toString());
- const policyMembersAccountIDs = policyID ? getPolicyMemberAccountIDs(policyID) : [];
+ const policyEmployeeAccountIDs = policyID ? getPolicyEmployeeAccountIDs(policyID) : [];
- const reportBelongsToWorkspace = policyID && !isEmptyObject(report) && doesReportBelongToWorkspace(report, policyMembersAccountIDs, policyID);
+ const reportBelongsToWorkspace = policyID && !isEmptyObject(report) && doesReportBelongToWorkspace(report, policyEmployeeAccountIDs, policyID);
Log.info('[PushNotification] onSelected() - called', false, {reportID, reportActionID});
Navigation.isNavigationReady()
diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts
index 6baf0cd30529..b65ad87b0128 100644
--- a/src/libs/OptionsListUtils.ts
+++ b/src/libs/OptionsListUtils.ts
@@ -117,6 +117,7 @@ type TaxSection = {
type CategoryTreeSection = CategorySectionBase & {
data: OptionTree[];
+ indexOffset?: number;
};
type Category = {
@@ -1023,11 +1024,13 @@ function getCategoryListSections(
const numberOfEnabledCategories = enabledCategories.length;
if (numberOfEnabledCategories === 0 && selectedOptions.length > 0) {
+ const data = getCategoryOptionTree(selectedOptions, true);
categorySections.push({
// "Selected" section
title: '',
shouldShow: false,
- data: getCategoryOptionTree(selectedOptions, true),
+ data,
+ indexOffset: data.length,
});
return categorySections;
@@ -1046,22 +1049,26 @@ function getCategoryListSections(
});
});
+ const data = getCategoryOptionTree(searchCategories, true);
categorySections.push({
// "Search" section
title: '',
shouldShow: true,
- data: getCategoryOptionTree(searchCategories, true),
+ data,
+ indexOffset: data.length,
});
return categorySections;
}
if (selectedOptions.length > 0) {
+ const data = getCategoryOptionTree(selectedOptions, true);
categorySections.push({
// "Selected" section
title: '',
shouldShow: false,
- data: getCategoryOptionTree(selectedOptions, true),
+ data,
+ indexOffset: data.length,
});
}
@@ -1069,11 +1076,13 @@ function getCategoryListSections(
const filteredCategories = enabledCategories.filter((category) => !selectedOptionNames.includes(category.name));
if (numberOfEnabledCategories < CONST.CATEGORY_LIST_THRESHOLD) {
+ const data = getCategoryOptionTree(filteredCategories, false, selectedOptionNames);
categorySections.push({
// "All" section when items amount less than the threshold
title: '',
shouldShow: false,
- data: getCategoryOptionTree(filteredCategories, false, selectedOptionNames),
+ data,
+ indexOffset: data.length,
});
return categorySections;
@@ -1089,19 +1098,23 @@ function getCategoryListSections(
if (filteredRecentlyUsedCategories.length > 0) {
const cutRecentlyUsedCategories = filteredRecentlyUsedCategories.slice(0, maxRecentReportsToShow);
+ const data = getCategoryOptionTree(cutRecentlyUsedCategories, true);
categorySections.push({
// "Recent" section
title: Localize.translateLocal('common.recent'),
shouldShow: true,
- data: getCategoryOptionTree(cutRecentlyUsedCategories, true),
+ data,
+ indexOffset: data.length,
});
}
+ const data = getCategoryOptionTree(filteredCategories, false, selectedOptionNames);
categorySections.push({
// "All" section when items amount more than the threshold
title: Localize.translateLocal('common.all'),
shouldShow: true,
- data: getCategoryOptionTree(filteredCategories, false, selectedOptionNames),
+ data,
+ indexOffset: data.length,
});
return categorySections;
@@ -1707,7 +1720,7 @@ function getOptions(
return;
}
- // In case user needs to add credit bank account, don't allow them to request more money from the workspace.
+ // In case user needs to add credit bank account, don't allow them to submit an expense from the workspace.
if (includeOwnedWorkspaceChats && ReportUtils.hasIOUWaitingOnCurrentUserBankAccount(report)) {
return;
}
@@ -2356,4 +2369,4 @@ export {
getFirstKeyForList,
};
-export type {MemberForList, CategorySection, CategoryTreeSection, Options, OptionList, SearchOption, PayeePersonalDetails, Category, TaxRatesOption, Option};
+export type {MemberForList, CategorySection, CategoryTreeSection, Options, OptionList, SearchOption, PayeePersonalDetails, Category, TaxRatesOption, Option, OptionTree};
diff --git a/src/libs/PolicyEmployeeListUtils.ts b/src/libs/PolicyEmployeeListUtils.ts
new file mode 100644
index 000000000000..305ffda87e28
--- /dev/null
+++ b/src/libs/PolicyEmployeeListUtils.ts
@@ -0,0 +1,25 @@
+import type {OnyxCollection} from 'react-native-onyx';
+import Onyx from 'react-native-onyx';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Policy} from '@src/types/onyx';
+import {getCurrentUserAccountID} from './actions/Report';
+import {getPolicyEmployeeListByIdWithoutCurrentUser} from './PolicyUtils';
+
+let allPolicies: OnyxCollection = {};
+Onyx.connect({
+ key: ONYXKEYS.COLLECTION.POLICY,
+ waitForCollectionCallback: true,
+ callback: (value) => (allPolicies = value),
+});
+
+function getPolicyEmployeeAccountIDs(policyID?: string) {
+ if (!policyID) {
+ return [];
+ }
+
+ const currentUserAccountID = getCurrentUserAccountID();
+
+ return getPolicyEmployeeListByIdWithoutCurrentUser(allPolicies, policyID, currentUserAccountID);
+}
+
+export default getPolicyEmployeeAccountIDs;
diff --git a/src/libs/PolicyMembersUtils.ts b/src/libs/PolicyMembersUtils.ts
deleted file mode 100644
index 4376de150f17..000000000000
--- a/src/libs/PolicyMembersUtils.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import type {OnyxCollection} from 'react-native-onyx';
-import Onyx from 'react-native-onyx';
-import ONYXKEYS from '@src/ONYXKEYS';
-import type {PolicyMembers} from '@src/types/onyx';
-import {getCurrentUserAccountID} from './actions/Report';
-import {getPolicyMembersByIdWithoutCurrentUser} from './PolicyUtils';
-
-let policyMembers: OnyxCollection;
-Onyx.connect({
- key: ONYXKEYS.COLLECTION.POLICY_MEMBERS,
- waitForCollectionCallback: true,
- callback: (value) => (policyMembers = value),
-});
-
-function getPolicyMemberAccountIDs(policyID?: string) {
- if (!policyID) {
- return [];
- }
-
- const currentUserAccountID = getCurrentUserAccountID();
-
- return getPolicyMembersByIdWithoutCurrentUser(policyMembers, policyID, currentUserAccountID);
-}
-
-export default getPolicyMemberAccountIDs;
diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index b162a7c6df93..b4cf4b164a19 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -4,13 +4,14 @@ import type {ValueOf} from 'type-fest';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
-import type {PersonalDetailsList, Policy, PolicyCategories, PolicyMembers, PolicyTagList, PolicyTags, TaxRate} from '@src/types/onyx';
+import type {Policy, PolicyCategories, PolicyEmployeeList, PolicyTagList, PolicyTags, TaxRate} from '@src/types/onyx';
import type {PolicyFeatureName, Rate} from '@src/types/onyx/Policy';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import getPolicyIDFromState from './Navigation/getPolicyIDFromState';
import Navigation, {navigationRef} from './Navigation/Navigation';
import type {RootStackParamList, State} from './Navigation/types';
+import {getPersonalDetailByEmail} from './PersonalDetailsUtils';
type MemberEmailsToAccountIDs = Record;
@@ -25,11 +26,10 @@ function getActivePolicies(policies: OnyxCollection): Policy[] | undefin
}
/**
- * Checks if we have any errors stored within the POLICY_MEMBERS. Determines whether we should show a red brick road error or not.
- * Data structure: {accountID: {role:'user', errors: []}, accountID2: {role:'admin', errors: [{1231312313: 'Unable to do X'}]}, ...}
+ * Checks if we have any errors stored within the policy?.employeeList. Determines whether we should show a red brick road error or not.
*/
-function hasPolicyMemberError(policyMembers: OnyxEntry): boolean {
- return Object.values(policyMembers ?? {}).some((member) => Object.keys(member?.errors ?? {}).length > 0);
+function hasEmployeeListError(policy: OnyxEntry): boolean {
+ return Object.values(policy?.employeeList ?? {}).some((employee) => Object.keys(employee?.errors ?? {}).length > 0);
}
/**
@@ -90,9 +90,8 @@ function getUnitRateValue(toLocaleDigit: (arg: string) => string, customUnitRate
/**
* Get the brick road indicator status for a policy. The policy has an error status if there is a policy member error, a custom unit error or a field error.
*/
-function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry, policyMembersCollection: OnyxCollection): ValueOf | undefined {
- const policyMembers = policyMembersCollection?.[`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policy?.id}`] ?? {};
- if (hasPolicyMemberError(policyMembers) || hasCustomUnitsError(policy) || hasPolicyErrorFields(policy)) {
+function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry): ValueOf | undefined {
+ if (hasEmployeeListError(policy) || hasCustomUnitsError(policy) || hasPolicyErrorFields(policy)) {
return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
}
return undefined;
@@ -128,7 +127,7 @@ const isPolicyAdmin = (policy: OnyxEntry | EmptyObject): boolean => poli
*/
const isFreeGroupPolicy = (policy: OnyxEntry | EmptyObject): boolean => policy?.type === CONST.POLICY.TYPE.FREE;
-const isPolicyMember = (policyID: string, policies: OnyxCollection): boolean => Object.values(policies ?? {}).some((policy) => policy?.id === policyID);
+const isPolicyEmployee = (policyID: string, policies: OnyxCollection): boolean => Object.values(policies ?? {}).some((policy) => policy?.id === policyID);
/**
* Checks if the current user is an owner (creator) of the policy.
@@ -140,18 +139,19 @@ const isPolicyOwner = (policy: OnyxEntry, currentUserAccountID: number):
*
* We only return members without errors. Otherwise, the members with errors would immediately be removed before the user has a chance to read the error.
*/
-function getMemberAccountIDsForWorkspace(policyMembers: OnyxEntry, personalDetails: OnyxEntry): MemberEmailsToAccountIDs {
+function getMemberAccountIDsForWorkspace(employeeList: PolicyEmployeeList | undefined): MemberEmailsToAccountIDs {
+ const members = employeeList ?? {};
const memberEmailsToAccountIDs: MemberEmailsToAccountIDs = {};
- Object.keys(policyMembers ?? {}).forEach((accountID) => {
- const member = policyMembers?.[accountID];
+ Object.keys(members).forEach((email) => {
+ const member = members?.[email];
if (Object.keys(member?.errors ?? {})?.length > 0) {
return;
}
- const personalDetail = personalDetails?.[accountID];
+ const personalDetail = getPersonalDetailByEmail(email);
if (!personalDetail?.login) {
return;
}
- memberEmailsToAccountIDs[personalDetail.login] = Number(accountID);
+ memberEmailsToAccountIDs[email] = Number(personalDetail.accountID);
});
return memberEmailsToAccountIDs;
}
@@ -159,19 +159,19 @@ function getMemberAccountIDsForWorkspace(policyMembers: OnyxEntry
/**
* Get login list that we should not show in the workspace invite options
*/
-function getIneligibleInvitees(policyMembers: OnyxEntry, personalDetails: OnyxEntry): string[] {
+function getIneligibleInvitees(employeeList?: PolicyEmployeeList): string[] {
+ const policyEmployeeList = employeeList ?? {};
const memberEmailsToExclude: string[] = [...CONST.EXPENSIFY_EMAILS];
- Object.keys(policyMembers ?? {}).forEach((accountID) => {
- const policyMember = policyMembers?.[accountID];
+ Object.keys(policyEmployeeList).forEach((email) => {
+ const policyEmployee = policyEmployeeList?.[email];
// Policy members that are pending delete or have errors are not valid and we should show them in the invite options (don't exclude them).
- if (policyMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policyMember?.errors ?? {}).length > 0) {
+ if (policyEmployee?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policyEmployee?.errors ?? {}).length > 0) {
return;
}
- const memberEmail = personalDetails?.[accountID]?.login;
- if (!memberEmail) {
+ if (!email) {
return;
}
- memberEmailsToExclude.push(memberEmail);
+ memberEmailsToExclude.push(email);
});
return memberEmailsToExclude;
@@ -279,12 +279,12 @@ function getPathWithoutPolicyID(path: string) {
return path.replace(CONST.REGEX.PATH_WITHOUT_POLICY_ID, '/');
}
-function getPolicyMembersByIdWithoutCurrentUser(policyMembers: OnyxCollection, currentPolicyID?: string, currentUserAccountID?: number) {
- return policyMembers
- ? Object.keys(policyMembers[`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${currentPolicyID}`] ?? {})
- .map((policyMemberAccountID) => Number(policyMemberAccountID))
- .filter((policyMemberAccountID) => policyMemberAccountID !== currentUserAccountID)
- : [];
+function getPolicyEmployeeListByIdWithoutCurrentUser(policies: OnyxCollection>, currentPolicyID?: string, currentUserAccountID?: number) {
+ const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${currentPolicyID}`] ?? null;
+ const policyMemberEmailsToAccountIDs = getMemberAccountIDsForWorkspace(policy?.employeeList);
+ return Object.values(policyMemberEmailsToAccountIDs)
+ .map((policyMemberAccountID) => Number(policyMemberAccountID))
+ .filter((policyMemberAccountID) => policyMemberAccountID !== currentUserAccountID);
}
function goBackFromInvalidPolicy() {
@@ -321,7 +321,7 @@ function getPolicyIDFromNavigationState() {
export {
getActivePolicies,
hasAccountingConnections,
- hasPolicyMemberError,
+ hasEmployeeListError,
hasPolicyError,
hasPolicyErrorFields,
hasCustomUnitsError,
@@ -345,12 +345,12 @@ export {
getCleanedTagName,
getCountOfEnabledTagsOfList,
isPendingDeletePolicy,
- isPolicyMember,
+ isPolicyEmployee,
isPolicyOwner,
isPaidGroupPolicy,
extractPolicyIDFromPath,
getPathWithoutPolicyID,
- getPolicyMembersByIdWithoutCurrentUser,
+ getPolicyEmployeeListByIdWithoutCurrentUser,
goBackFromInvalidPolicy,
isPolicyFeatureEnabled,
hasTaxRateError,
diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts
index e8f2189f5f7d..d4a2afafb420 100644
--- a/src/libs/ReportActionsUtils.ts
+++ b/src/libs/ReportActionsUtils.ts
@@ -20,7 +20,6 @@ import type {Message, ReportActionBase, ReportActions} from '@src/types/onyx/Rep
import type ReportAction from '@src/types/onyx/ReportAction';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
-import * as CollectionUtils from './CollectionUtils';
import * as Environment from './Environment/Environment';
import isReportMessageAttachment from './isReportMessageAttachment';
import * as Localize from './Localize';
@@ -59,16 +58,16 @@ Onyx.connect({
},
});
-const allReportActions: OnyxCollection = {};
+let allReportActions: OnyxCollection;
Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- callback: (actions, key) => {
- if (!key || !actions) {
+ waitForCollectionCallback: true,
+ callback: (actions) => {
+ if (!actions) {
return;
}
- const reportID = CollectionUtils.extractCollectionItemID(key);
- allReportActions[reportID] = actions;
+ allReportActions = actions;
},
});
@@ -195,7 +194,7 @@ function getParentReportAction(report: OnyxEntry | EmptyObject): ReportA
if (!report?.parentReportID || !report.parentReportActionID) {
return {};
}
- return allReportActions?.[report.parentReportID]?.[report.parentReportActionID] ?? {};
+ return allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`]?.[report.parentReportActionID] ?? {};
}
/**
@@ -225,7 +224,7 @@ function isTransactionThread(parentReportAction: OnyxEntry | Empty
/**
* Returns the reportID for the transaction thread associated with a report by iterating over the reportActions and identifying the IOU report actions with a childReportID. Returns a reportID if there is exactly one transaction thread for the report, and null otherwise.
*/
-function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEntry | ReportAction[]): string | null {
+function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEntry | ReportAction[], isOffline: boolean | undefined = undefined): string | null {
// If the report is not an IOU or Expense report, it shouldn't be treated as one-transaction report.
const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
if (report?.type !== CONST.REPORT.TYPE.IOU && report?.type !== CONST.REPORT.TYPE.EXPENSE) {
@@ -250,7 +249,7 @@ function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEn
action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU &&
(iouRequestTypes.includes(action.originalMessage.type) ?? []) &&
action.childReportID &&
- action.originalMessage.IOUTransactionID,
+ (Boolean(action.originalMessage.IOUTransactionID) || (action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && (isOffline ?? isNetworkOffline))),
);
// If we don't have any IOU request actions, or we have more than one IOU request actions, this isn't a oneTransaction report
@@ -518,7 +517,7 @@ function shouldReportActionBeVisible(reportAction: OnyxEntry, key:
return false;
}
- // Ignore markedAsReimbursed action here since we're already display message that explains the request was paid
+ // Ignore markedAsReimbursed action here since we're already display message that explains the expense was paid
// elsewhere in the IOU reportAction
if (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED) {
return false;
@@ -593,7 +592,7 @@ function replaceBaseURLInPolicyChangeLogAction(reportAction: ReportAction): Repo
}
function getLastVisibleAction(reportID: string, actionsToMerge: OnyxCollection = {}): OnyxEntry {
- const reportActions = Object.values(fastMerge(allReportActions?.[reportID] ?? {}, actionsToMerge ?? {}, true));
+ const reportActions = Object.values(fastMerge(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? {}, actionsToMerge ?? {}, true));
const visibleReportActions = Object.values(reportActions ?? {}).filter((action): action is ReportAction => shouldReportActionBeVisibleAsLastAction(action));
const sortedReportActions = getSortedReportActions(visibleReportActions, true);
if (sortedReportActions.length === 0) {
@@ -714,7 +713,7 @@ function getLatestReportActionFromOnyxData(onyxData: OnyxUpdate[] | null): OnyxE
* Find the transaction associated with this reportAction, if one exists.
*/
function getLinkedTransactionID(reportActionOrID: string | OnyxEntry, reportID?: string): string | null {
- const reportAction = typeof reportActionOrID === 'string' ? allReportActions?.[reportID ?? '']?.[reportActionOrID] : reportActionOrID;
+ const reportAction = typeof reportActionOrID === 'string' ? allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]?.[reportActionOrID] : reportActionOrID;
if (!reportAction || reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) {
return null;
}
@@ -722,7 +721,7 @@ function getLinkedTransactionID(reportActionOrID: string | OnyxEntry {
- return allReportActions?.[reportID]?.[reportActionID] ?? null;
+ return allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]?.[reportActionID] ?? null;
}
function getMostRecentReportActionLastModified(): string {
@@ -769,7 +768,7 @@ function getMostRecentReportActionLastModified(): string {
*/
function getReportPreviewAction(chatReportID: string, iouReportID: string): OnyxEntry {
return (
- Object.values(allReportActions?.[chatReportID] ?? {}).find(
+ Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`] ?? {}).find(
(reportAction) => reportAction && reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && reportAction.originalMessage.linkedReportID === iouReportID,
) ?? null
);
@@ -794,7 +793,7 @@ function isMessageDeleted(reportAction: OnyxEntry): boolean {
}
/**
- * Returns the number of money requests associated with a report preview
+ * Returns the number of expenses associated with a report preview
*/
function getNumberOfMoneyRequests(reportPreviewAction: OnyxEntry): number {
return reportPreviewAction?.childMoneyRequestCount ?? 0;
@@ -823,7 +822,7 @@ function isTaskAction(reportAction: OnyxEntry): boolean {
* If there are no visible actions left (including system messages), we can hide the report from view entirely
*/
function doesReportHaveVisibleActions(reportID: string, actionsToMerge: ReportActions = {}): boolean {
- const reportActions = Object.values(fastMerge(allReportActions?.[reportID] ?? {}, actionsToMerge, true));
+ const reportActions = Object.values(fastMerge(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? {}, actionsToMerge, true));
const visibleReportActions = Object.values(reportActions ?? {}).filter((action) => shouldReportActionBeVisibleAsLastAction(action));
// Exclude the task system message and the created message
@@ -832,7 +831,7 @@ function doesReportHaveVisibleActions(reportID: string, actionsToMerge: ReportAc
}
function getAllReportActions(reportID: string): ReportActions {
- return allReportActions?.[reportID] ?? {};
+ return allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? {};
}
/**
@@ -997,7 +996,7 @@ function getMemberChangeMessagePlainText(reportAction: OnyxEntry):
}
/**
- * Helper method to determine if the provided accountID has made a request on the specified report.
+ * Helper method to determine if the provided accountID has submitted an expense on the specified report.
*
* @param reportID
* @param currentAccountID
@@ -1080,6 +1079,10 @@ function isActionableJoinRequest(reportAction: OnyxEntry): boolean
return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ACTIONABLEJOINREQUEST;
}
+function isActionableTrackExpense(reportAction: OnyxEntry): boolean {
+ return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ACTIONABLETRACKEXPENSEWHISPER;
+}
+
/**
* Checks if any report actions correspond to a join request action that is still pending.
* @param reportID
@@ -1163,6 +1166,7 @@ export {
isCurrentActionUnread,
isActionableJoinRequest,
isActionableJoinRequestPending,
+ isActionableTrackExpense,
};
export type {LastVisibleMessage};
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index f7b160bd67e2..28a0f6b385bb 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -53,8 +53,8 @@ import type {Receipt, TransactionChanges, WaypointCollection} from '@src/types/o
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type IconAsset from '@src/types/utils/IconAsset';
+import * as IOU from './actions/IOU';
import * as store from './actions/ReimbursementAccount/store';
-import * as CollectionUtils from './CollectionUtils';
import * as CurrencyUtils from './CurrencyUtils';
import DateUtils from './DateUtils';
import {hasValidDraftComment} from './DraftCommentUtils';
@@ -190,6 +190,8 @@ type OptimisticIOUReportAction = Pick<
| 'receipt'
| 'whisperedToAccountIDs'
| 'childReportID'
+ | 'childVisibleActionCount'
+ | 'childCommenterCount'
>;
type ReportRouteParams = {
@@ -534,16 +536,15 @@ Onyx.connect({
},
});
-const reportActionsByReport: OnyxCollection = {};
+let allReportActions: OnyxCollection;
Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- callback: (actions, key) => {
- if (!key || !actions) {
+ waitForCollectionCallback: true,
+ callback: (actions) => {
+ if (!actions) {
return;
}
-
- const reportID = CollectionUtils.extractCollectionItemID(key);
- reportActionsByReport[reportID] = actions;
+ allReportActions = actions;
},
});
@@ -1169,6 +1170,15 @@ function isJoinRequestInAdminRoom(report: OnyxEntry): boolean {
if (!report) {
return false;
}
+ // If this policy isn't owned by Expensify,
+ // Account manager/guide should not have the workspace join request pinned to their LHN,
+ // since they are not a part of the company, and should not action it on their behalf.
+ if (report.policyID) {
+ const policy = getPolicy(report.policyID);
+ if (!PolicyUtils.isExpensifyTeam(policy.owner) && PolicyUtils.isExpensifyTeam(currentUserPersonalDetails?.login)) {
+ return false;
+ }
+ }
return ReportActionsUtils.isActionableJoinRequestPending(report.reportID);
}
@@ -1317,7 +1327,7 @@ function isMoneyRequestReport(reportOrID: OnyxEntry | EmptyObject | stri
* Checks if a report has only one transaction associated with it
*/
function isOneTransactionReport(reportID: string): boolean {
- const reportActions = reportActionsByReport?.[reportID] ?? ([] as ReportAction[]);
+ const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? ([] as ReportAction[]);
return ReportActionsUtils.getOneTransactionThreadReportID(reportID, reportActions) !== null;
}
@@ -1325,7 +1335,7 @@ function isOneTransactionReport(reportID: string): boolean {
* Checks if a report is a transaction thread associated with a report that has only one transaction
*/
function isOneTransactionThread(reportID: string, parentReportID: string): boolean {
- const parentReportActions = reportActionsByReport?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`] ?? ([] as ReportAction[]);
+ const parentReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`] ?? ([] as ReportAction[]);
const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(parentReportID, parentReportActions);
return reportID === transactionThreadReportID;
}
@@ -1349,7 +1359,7 @@ function isOneOnOneChat(report: OnyxEntry): boolean {
}
/**
- * Checks if the current user is a payer of the request
+ * Checks if the current user is a payer of the expense
*/
function isPayer(session: OnyxEntry, iouReport: OnyxEntry) {
@@ -1439,9 +1449,10 @@ function canDeleteReportAction(reportAction: OnyxEntry, reportID:
return false;
}
+ const linkedReport = isThreadFirstChat(reportAction, reportID) ? getReport(report?.parentReportID) : report;
if (isActionOwner) {
- if (!isEmptyObject(report) && isMoneyRequestReport(report)) {
- return canAddOrDeleteTransactions(report);
+ if (!isEmptyObject(linkedReport) && isMoneyRequestReport(linkedReport)) {
+ return canAddOrDeleteTransactions(linkedReport);
}
return true;
}
@@ -2127,7 +2138,7 @@ function isUnreadWithMention(reportOrOption: OnyxEntry | OptionData): bo
* Determines if the option requires action from the current user. This can happen when it:
* - is unread and the user was mentioned in one of the unread comments
* - is for an outstanding task waiting on the user
- * - has an outstanding child money request that is waiting for an action from the current user (e.g. pay, approve, add bank account)
+ * - has an outstanding child expense that is waiting for an action from the current user (e.g. pay, approve, add bank account)
*
* @param option (report or optionItem)
* @param parentReportAction (the report action the current report is a thread of)
@@ -2387,7 +2398,7 @@ function getMoneyRequestReportName(report: OnyxEntry, policy: OnyxEntry<
}
/**
- * Gets transaction created, amount, currency, comment, and waypoints (for distance request)
+ * Gets transaction created, amount, currency, comment, and waypoints (for distance expense)
* into a flat object. Used for displaying transactions and sending them in API commands
*/
@@ -2475,11 +2486,11 @@ function canEditMoneyRequest(reportAction: OnyxEntry): boolean {
}
/**
- * Checks if the current user can edit the provided property of a money request
+ * Checks if the current user can edit the provided property of an expense
*
*/
function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, fieldToEdit: ValueOf): boolean {
- // A list of fields that cannot be edited by anyone, once a money request has been settled
+ // A list of fields that cannot be edited by anyone, once an expense has been settled
const restrictedFields: string[] = [
CONST.EDIT_REQUEST_FIELD.AMOUNT,
CONST.EDIT_REQUEST_FIELD.CURRENCY,
@@ -2533,7 +2544,7 @@ function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, field
*
* - It was written by the current user
* - It's an ADDCOMMENT that is not an attachment
- * - It's money request where conditions for editability are defined in canEditMoneyRequest method
+ * - It's an expense where conditions for editability are defined in canEditMoneyRequest method
* - It's not pending deletion
*/
function canEditReportAction(reportAction: OnyxEntry): boolean {
@@ -2563,7 +2574,7 @@ function getTransactionsWithReceipts(iouReportID: string | undefined): Transacti
* instead of the report total only when we have no report total ready to show. This is the case when
* all requests are receipts that are being SmartScanned. As soon as we have a non-receipt request,
* or as soon as one receipt request is done scanning, we have at least one
- * "ready" money request, and we remove this indicator to show the partial report total.
+ * "ready" expense, and we remove this indicator to show the partial report total.
*/
function areAllRequestsBeingSmartScanned(iouReportID: string, reportPreviewAction: OnyxEntry): boolean {
const transactionsWithReceipts = getTransactionsWithReceipts(iouReportID);
@@ -2607,10 +2618,7 @@ function getTransactionReportName(reportAction: OnyxEntry, policy: OnyxEntry = nu
return Localize.translateLocal('parentReportAction.deletedTask');
}
+ if (isGroupChat(report)) {
+ return getGroupChatName(undefined, true, report?.reportID) ?? '';
+ }
+
if (isChatRoom(report) || isTaskReport(report)) {
formattedName = report?.reportName;
}
@@ -3026,6 +3038,22 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu
return participantsWithoutCurrentUser.map((accountID) => getDisplayNameForParticipant(accountID, isMultipleParticipantReport)).join(', ');
}
+/**
+ * Get the payee name given a report.
+ */
+function getPayeeName(report: OnyxEntry): string | undefined {
+ if (isEmptyObject(report)) {
+ return undefined;
+ }
+
+ const participantAccountIDs = report?.participantAccountIDs ?? [];
+ const participantsWithoutCurrentUser = participantAccountIDs.filter((accountID) => accountID !== currentUserAccountID);
+ if (participantsWithoutCurrentUser.length === 0) {
+ return undefined;
+ }
+ return getDisplayNameForParticipant(participantsWithoutCurrentUser[0], true);
+}
+
/**
* Get either the policyName or domainName the chat is tied to
*/
@@ -3119,6 +3147,27 @@ function hasReportNameError(report: OnyxEntry): boolean {
return !isEmptyObject(report?.errorFields?.reportName);
}
+/**
+ * Adds a domain to a short mention, converting it into a full mention with email or SMS domain.
+ * @param mention The user mention to be converted.
+ * @returns The converted mention as a full mention string or undefined if conversion is not applicable.
+ */
+function addDomainToShortMention(mention: string): string | undefined {
+ if (!Str.isValidEmail(mention) && currentUserPrivateDomain) {
+ const mentionWithEmailDomain = `${mention}@${currentUserPrivateDomain}`;
+ if (allPersonalDetailLogins.includes(mentionWithEmailDomain)) {
+ return mentionWithEmailDomain;
+ }
+ }
+ if (Str.isValidE164Phone(mention)) {
+ const mentionWithSmsDomain = PhoneNumber.addSMSDomainIfPhoneNumber(mention);
+ if (allPersonalDetailLogins.includes(mentionWithSmsDomain)) {
+ return mentionWithSmsDomain;
+ }
+ }
+ return undefined;
+}
+
/**
* For comments shorter than or equal to 10k chars, convert the comment from MD into HTML because that's how it is stored in the database
* For longer comments, skip parsing, but still escape the text, and display plaintext for performance reasons. It takes over 40s to parse a 100k long string!!
@@ -3127,21 +3176,8 @@ function getParsedComment(text: string): string {
const parser = new ExpensiMark();
const textWithMention = text.replace(CONST.REGEX.SHORT_MENTION, (match) => {
const mention = match.substring(1);
-
- if (!Str.isValidEmail(mention) && currentUserPrivateDomain) {
- const mentionWithEmailDomain = `${mention}@${currentUserPrivateDomain}`;
- if (allPersonalDetailLogins.includes(mentionWithEmailDomain)) {
- return `@${mentionWithEmailDomain}`;
- }
- }
- if (Str.isValidE164Phone(mention)) {
- const mentionWithSmsDomain = PhoneNumber.addSMSDomainIfPhoneNumber(mention);
- if (allPersonalDetailLogins.includes(mentionWithSmsDomain)) {
- return `@${mentionWithSmsDomain}`;
- }
- }
-
- return match;
+ const mentionWithDomain = addDomainToShortMention(mention);
+ return mentionWithDomain ? `@${mentionWithDomain}` : match;
});
return text.length <= CONST.MAX_MARKUP_LENGTH ? parser.replace(textWithMention, {shouldEscapeText: !shouldAllowRawHTMLMessages()}) : lodashEscape(text);
@@ -3304,7 +3340,7 @@ function buildOptimisticTaskCommentReportAction(taskReportID: string, taskTitle:
* @param total - IOU amount in the smallest unit of the currency.
* @param chatReportID - Report ID of the chat where the IOU is.
* @param currency - IOU currency.
- * @param isSendingMoney - If we send money the IOU should be created as settled
+ * @param isSendingMoney - If we pay someone the IOU should be created as settled
*/
function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number, total: number, chatReportID: string, currency: string, isSendingMoney = false): OptimisticIOUReport {
@@ -3457,7 +3493,7 @@ function getIOUReportActionMessage(iouReportID: string, type: string, total: num
iouMessage = `submitted ${amount}`;
break;
case CONST.IOU.REPORT_ACTION_TYPE.CREATE:
- iouMessage = `requested ${amount}${comment && ` for ${comment}`}`;
+ iouMessage = `submitted ${amount}${comment && ` for ${comment}`}`;
break;
case CONST.IOU.REPORT_ACTION_TYPE.TRACK:
iouMessage = `tracking ${amount}${comment && ` for ${comment}`}`;
@@ -3466,7 +3502,7 @@ function getIOUReportActionMessage(iouReportID: string, type: string, total: num
iouMessage = `split ${amount}${comment && ` for ${comment}`}`;
break;
case CONST.IOU.REPORT_ACTION_TYPE.DELETE:
- iouMessage = `deleted the ${amount} request${comment && ` for ${comment}`}`;
+ iouMessage = `deleted the ${amount} expense${comment && ` for ${comment}`}`;
break;
case CONST.IOU.REPORT_ACTION_TYPE.PAY:
iouMessage = isSettlingUp ? `paid ${amount}${paymentMethodMessage}` : `sent ${amount}${comment && ` for ${comment}`}${paymentMethodMessage}`;
@@ -3497,7 +3533,7 @@ function getIOUReportActionMessage(iouReportID: string, type: string, total: num
* @param [paymentType] - Only required if the IOUReportAction type is 'pay'. Can be oneOf(elsewhere, Expensify).
* @param [iouReportID] - Only required if the IOUReportActions type is oneOf(decline, cancel, pay). Generates a randomID as default.
* @param [isSettlingUp] - Whether we are settling up an IOU.
- * @param [isSendMoneyFlow] - Whether this is send money flow
+ * @param [isSendMoneyFlow] - Whether this is pay someone flow
* @param [receipt]
* @param [isOwnPolicyExpenseChat] - Whether this is an expense report create from the current user's policy expense chat
*/
@@ -3515,6 +3551,7 @@ function buildOptimisticIOUReportAction(
receipt: Receipt = {},
isOwnPolicyExpenseChat = false,
created = DateUtils.getDBTime(),
+ linkedExpenseReportAction: ReportAction | EmptyObject = {},
): OptimisticIOUReportAction {
const IOUReportID = iouReportID || generateReportID();
@@ -3528,7 +3565,7 @@ function buildOptimisticIOUReportAction(
};
if (type === CONST.IOU.REPORT_ACTION_TYPE.PAY) {
- // In send money flow, we store amount, comment, currency in IOUDetails when type = pay
+ // In pay someone flow, we store amount, comment, currency in IOUDetails when type = pay
if (isSendMoneyFlow) {
const keys = ['amount', 'comment', 'currency'] as const;
keys.forEach((key) => {
@@ -3537,7 +3574,7 @@ function buildOptimisticIOUReportAction(
originalMessage.IOUDetails = {amount, comment, currency};
originalMessage.paymentType = paymentType;
} else {
- // In case of pay money request action, we dont store the comment
+ // In case of pay someone action, we dont store the comment
// and there is no single transctionID to link the action to.
delete originalMessage.IOUTransactionID;
delete originalMessage.comment;
@@ -3548,7 +3585,7 @@ function buildOptimisticIOUReportAction(
// IOUs of type split only exist in group DMs and those don't have an iouReport so we need to delete the IOUReportID key
if (type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT) {
delete originalMessage.IOUReportID;
- // Split bill made from a policy expense chat only have the payee's accountID as the participant because the payer could be any policy admin
+ // Split expense made from a policy expense chat only have the payee's accountID as the participant because the payer could be any policy admin
if (isOwnPolicyExpenseChat) {
originalMessage.participantAccountIDs = currentUserAccountID ? [currentUserAccountID] : [];
} else {
@@ -3559,6 +3596,7 @@ function buildOptimisticIOUReportAction(
}
return {
+ ...linkedExpenseReportAction,
actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
actorAccountID: currentUserAccountID,
automatic: false,
@@ -3771,6 +3809,44 @@ function buildOptimisticModifiedExpenseReportAction(
};
}
+/**
+ * Builds an optimistic modified expense action for a tracked expense move with a randomly generated reportActionID.
+ * @param transactionThreadID - The reportID of the transaction thread
+ * @param movedToReportID - The reportID of the report the transaction is moved to
+ */
+function buildOptimisticMovedTrackedExpenseModifiedReportAction(transactionThreadID: string, movedToReportID: string): OptimisticModifiedExpenseReportAction {
+ return {
+ actionName: CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE,
+ actorAccountID: currentUserAccountID,
+ automatic: false,
+ avatar: getCurrentUserAvatarOrDefault(),
+ created: DateUtils.getDBTime(),
+ isAttachment: false,
+ message: [
+ {
+ // Currently we are composing the message from the originalMessage and message is only used in OldDot and not in the App
+ text: 'You',
+ style: 'strong',
+ type: CONST.REPORT.MESSAGE.TYPE.TEXT,
+ },
+ ],
+ originalMessage: {
+ movedToReportID,
+ },
+ person: [
+ {
+ style: 'strong',
+ text: currentUserPersonalDetails?.displayName ?? String(currentUserAccountID),
+ type: 'TEXT',
+ },
+ ],
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+ reportActionID: NumberUtils.rand64(),
+ reportID: transactionThreadID,
+ shouldShow: true,
+ };
+}
+
/**
* Updates a report preview action that exists for an IOU report.
*
@@ -3812,7 +3888,7 @@ function updateReportPreview(iouReport: OnyxEntry, reportPreviewAction:
...previousTransactions,
}
: recentReceiptTransactions,
- // As soon as we add a transaction without a receipt to the report, it will have ready money requests,
+ // As soon as we add a transaction without a receipt to the report, it will have ready expenses,
// so we remove the whisper
whisperedToAccountIDs: hasReceipt ? reportPreviewAction?.whisperedToAccountIDs : [],
};
@@ -4030,7 +4106,7 @@ function buildOptimisticHoldReportAction(created = DateUtils.getDBTime()): Optim
{
type: CONST.REPORT.MESSAGE.TYPE.TEXT,
style: 'normal',
- text: Localize.translateLocal('iou.heldRequest'),
+ text: Localize.translateLocal('iou.heldExpense'),
},
],
person: [
@@ -4054,7 +4130,7 @@ function buildOptimisticHoldReportAction(created = DateUtils.getDBTime()): Optim
function buildOptimisticHoldReportActionComment(comment: string, created = DateUtils.getDBTime()): OptimisticHoldReportAction {
return {
reportActionID: NumberUtils.rand64(),
- actionName: CONST.REPORT.ACTIONS.TYPE.HOLDCOMMENT,
+ actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
actorAccountID: currentUserAccountID,
message: [
@@ -4092,7 +4168,7 @@ function buildOptimisticUnHoldReportAction(created = DateUtils.getDBTime()): Opt
{
type: CONST.REPORT.MESSAGE.TYPE.TEXT,
style: 'normal',
- text: Localize.translateLocal('iou.unheldRequest'),
+ text: Localize.translateLocal('iou.unheldExpense'),
},
],
person: [
@@ -4330,8 +4406,25 @@ function buildOptimisticTaskReport(
* @param reportAction - the parent IOU report action from which to create the thread
* @param moneyRequestReport - the report which the report action belongs to
*/
-function buildTransactionThread(reportAction: OnyxEntry, moneyRequestReport: OnyxEntry): OptimisticChatReport {
+function buildTransactionThread(
+ reportAction: OnyxEntry,
+ moneyRequestReport: OnyxEntry,
+ existingTransactionThreadReportID?: string,
+): OptimisticChatReport {
const participantAccountIDs = [...new Set([currentUserAccountID, Number(reportAction?.actorAccountID)])].filter(Boolean) as number[];
+ const existingTransactionThreadReport = getReport(existingTransactionThreadReportID);
+
+ if (existingTransactionThreadReportID && existingTransactionThreadReport) {
+ return {
+ ...existingTransactionThreadReport,
+ isOptimisticReport: true,
+ parentReportActionID: reportAction?.reportActionID,
+ parentReportID: moneyRequestReport?.reportID,
+ reportName: getTransactionReportName(reportAction),
+ policyID: moneyRequestReport?.policyID,
+ };
+ }
+
return buildOptimisticChatReport(
participantAccountIDs,
getTransactionReportName(reportAction),
@@ -4345,11 +4438,13 @@ function buildTransactionThread(reportAction: OnyxEntry, policies: OnyxCollection, currentReportId: string): boolean {
const currentReport = getReport(currentReportId);
const parentReport = getParentReport(!isEmptyObject(currentReport) ? currentReport : null);
- const reportActions = reportActionsByReport?.[report?.reportID ?? ''] ?? {};
+ const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.reportID}`] ?? {};
const isChildReportHasComment = Object.values(reportActions ?? {})?.some((reportAction) => (reportAction?.childVisibleActionCount ?? 0) > 0);
return parentReport?.reportID !== report?.reportID && !isChildReportHasComment;
}
/**
- * Checks to see if a report's parentAction is a money request that contains a violation
+ * Checks to see if a report's parentAction is an expense that contains a violation
*/
function doesTransactionThreadHaveViolations(report: OnyxEntry, transactionViolations: OnyxCollection, parentReportAction: OnyxEntry): boolean {
if (parentReportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) {
@@ -4514,7 +4613,7 @@ function doesTransactionThreadHaveViolations(report: OnyxEntry, transact
}
/**
- * Checks if we should display violation - we display violations when the money request has violation and it is not settled
+ * Checks if we should display violation - we display violations when the expense has violation and it is not settled
*/
function shouldDisplayTransactionThreadViolations(
report: OnyxEntry,
@@ -4662,7 +4761,7 @@ function shouldReportBeInOptionList({
}
/**
- * Attempts to find a report in onyx with the provided list of participants. Does not include threads, task, money request, room, and policy expense chat.
+ * Attempts to find a report in onyx with the provided list of participants. Does not include threads, task, expense, room, and policy expense chat.
*/
function getChatByParticipants(newParticipantList: number[], reports: OnyxCollection = allReports): OnyxEntry {
const sortedNewParticipantList = newParticipantList.sort();
@@ -4883,20 +4982,20 @@ function hasIOUWaitingOnCurrentUserBankAccount(chatReport: OnyxEntry): b
}
/**
- * Users can request money:
+ * Users can submit an expense:
* - in policy expense chats only if they are in a role of a member in the chat (in other words, if it's their policy expense chat)
* - in an open or submitted expense report tied to a policy expense chat the user owns
- * - employee can request money in submitted expense report only if the policy has Instant Submit settings turned on
+ * - employee can submit expenses in a submitted expense report only if the policy has Instant Submit settings turned on
* - in an IOU report, which is not settled yet
* - in a 1:1 DM chat
*/
function canRequestMoney(report: OnyxEntry, policy: OnyxEntry, otherParticipants: number[]): boolean {
- // User cannot request money in chat thread or in task report or in chat room
+ // User cannot submit expenses in a chat thread, task report or in a chat room
if (isChatThread(report) || isTaskReport(report) || isChatRoom(report) || isSelfDM(report) || isGroupChat(report)) {
return false;
}
- // Users can only request money in DMs if they are a 1:1 DM
+ // Users can only submit expenses in DMs if they are a 1:1 DM
if (isDM(report)) {
return otherParticipants.length === 1;
}
@@ -4911,19 +5010,19 @@ function canRequestMoney(report: OnyxEntry, policy: OnyxEntry, o
isOwnPolicyExpenseChat = Boolean(getParentReport(report)?.isOwnPolicyExpenseChat);
}
- // In case there are no other participants than the current user and it's not user's own policy expense chat, they can't request money from such report
+ // In case there are no other participants than the current user and it's not user's own policy expense chat, they can't submit expenses from such report
if (otherParticipants.length === 0 && !isOwnPolicyExpenseChat) {
return false;
}
- // User can request money in any IOU report, unless paid, but user can only request money in an expense report
+ // User can submit expenses in any IOU report, unless paid, but the user can only submit expenses in an expense report
// which is tied to their workspace chat.
if (isMoneyRequestReport(report)) {
const canAddTransactions = canAddOrDeleteTransactions(report);
return isGroupPolicy(report) ? isOwnPolicyExpenseChat && canAddTransactions : canAddTransactions;
}
- // In case of policy expense chat, users can only request money from their own policy expense chat
+ // In the case of policy expense chat, users can only submit expenses from their own policy expense chat
return !isPolicyExpenseChat(report) || isOwnPolicyExpenseChat;
}
@@ -4938,19 +5037,19 @@ function isGroupChatAdmin(report: OnyxEntry, accountID: number) {
}
/**
- * Helper method to define what money request options we want to show for particular method.
- * There are 4 money request options: Request, Split, Send and Track expense:
- * - Request option should show for:
+ * Helper method to define what expense options we want to show for particular method.
+ * There are 4 expense options: Submit, Split, Pay and Track expense:
+ * - Submit option should show for:
* - DMs
* - own policy expense chats
* - open and processing expense reports tied to own policy expense chat
* - unsettled IOU reports
- * - Send option should show for:
+ * - Pay option should show for:
* - DMs
* - Split options should show for:
* - DMs
* - chat/policy rooms with more than 1 participant
- * - groups chats with 3 and more participants
+ * - groups chats with 2 and more participants
* - corporate workspace chats
* - Track expense option should show for:
* - Self DMs
@@ -4961,7 +5060,7 @@ function isGroupChatAdmin(report: OnyxEntry, accountID: number) {
* as a participant of the report.
*/
function getMoneyRequestOptions(report: OnyxEntry, policy: OnyxEntry, reportParticipants: number[], canUseTrackExpense = true): Array> {
- // In any thread or task report, we do not allow any new money requests yet
+ // In any thread or task report, we do not allow any new expenses yet
if (isChatThread(report) || isTaskReport(report) || (!canUseTrackExpense && isSelfDM(report))) {
return [];
}
@@ -4981,10 +5080,10 @@ function getMoneyRequestOptions(report: OnyxEntry, policy: OnyxEntry 0) ||
(isDM(report) && otherParticipants.length > 0) ||
@@ -5003,7 +5102,7 @@ function getMoneyRequestOptions(report: OnyxEntry, policy: OnyxEntry, policy: OnyxEntry, isPolicyMember: boolean): boolean {
+function canLeaveRoom(report: OnyxEntry, isPolicyEmployee: boolean): boolean {
if (!report?.visibility) {
if (
report?.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ADMINS ||
@@ -5036,7 +5135,7 @@ function canLeaveRoom(report: OnyxEntry, isPolicyMember: boolean): boole
// DM chats don't have a chatType
return false;
}
- } else if (isPublicAnnounceRoom(report) && isPolicyMember) {
+ } else if (isPublicAnnounceRoom(report) && isPolicyEmployee) {
return false;
}
return true;
@@ -5120,7 +5219,7 @@ function getAddWorkspaceRoomOrChatReportErrors(report: OnyxEntry): Error
}
/**
- * Return true if the Money Request report is marked for deletion.
+ * Return true if the expense report is marked for deletion.
*/
function isMoneyRequestReportPendingDeletion(report: OnyxEntry | EmptyObject): boolean {
if (!isMoneyRequestReport(report)) {
@@ -5134,7 +5233,7 @@ function isMoneyRequestReportPendingDeletion(report: OnyxEntry | EmptyOb
function canUserPerformWriteAction(report: OnyxEntry) {
const reportErrors = getAddWorkspaceRoomOrChatReportErrors(report);
- // If the Money Request report is marked for deletion, let us prevent any further write action.
+ // If the expense report is marked for deletion, let us prevent any further write action.
if (isMoneyRequestReportPendingDeletion(report)) {
return false;
}
@@ -5146,7 +5245,7 @@ function canUserPerformWriteAction(report: OnyxEntry) {
* Returns ID of the original report from which the given reportAction is first created.
*/
function getOriginalReportID(reportID: string, reportAction: OnyxEntry): string | undefined {
- const reportActions = reportActionsByReport?.[reportID];
+ const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`];
const currentReportAction = reportActions?.[reportAction?.reportActionID ?? ''] ?? null;
const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(reportID, reportActions ?? ([] as ReportAction[]));
if (transactionThreadReportID !== null) {
@@ -5162,7 +5261,7 @@ function getOriginalReportID(reportID: string, reportAction: OnyxEntry): Repo
}
/**
- * Check if the report can create the request with type is iouType
+ * Check if the report can create the expense with type is iouType
*/
function canCreateRequest(report: OnyxEntry, policy: OnyxEntry, iouType: (typeof CONST.IOU.TYPE)[keyof typeof CONST.IOU.TYPE]): boolean {
const participantAccountIDs = report?.participantAccountIDs ?? [];
@@ -5370,7 +5469,7 @@ function getIOUReportActionDisplayMessage(reportAction: OnyxEntry,
if (originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY) {
// The `REPORT_ACTION_TYPE.PAY` action type is used for both fulfilling existing requests and sending money. To
// differentiate between these two scenarios, we check if the `originalMessage` contains the `IOUDetails`
- // property. If it does, it indicates that this is a 'Send money' action.
+ // property. If it does, it indicates that this is a 'Pay someone' action.
const {amount, currency} = originalMessage.IOUDetails ?? originalMessage;
const formattedAmount = CurrencyUtils.convertToDisplayString(Math.abs(amount), currency) ?? '';
@@ -5417,7 +5516,7 @@ function getIOUReportActionDisplayMessage(reportAction: OnyxEntry,
} else if (ReportActionsUtils.isTrackExpenseAction(reportAction)) {
translationKey = 'iou.trackedAmount';
} else {
- translationKey = 'iou.requestedAmount';
+ translationKey = 'iou.submittedAmount';
}
return Localize.translateLocal(translationKey, {
formattedAmount,
@@ -5431,7 +5530,7 @@ function getIOUReportActionDisplayMessage(reportAction: OnyxEntry,
* A report is a group chat if it meets the following conditions:
* - Not a chat thread.
* - Not a task report.
- * - Not a money request / IOU report.
+ * - Not an expense / IOU report.
* - Not an archived room.
* - Not a public / admin / announce chat room (chat type doesn't match any of the specified types).
* - More than 2 participants.
@@ -5635,7 +5734,7 @@ function getNonHeldAndFullAmount(iouReport: OnyxEntry, policy: OnyxEntry
* Disable reply in thread action if:
*
* - The action is listed in the thread-disabled list
- * - The action is a split bill action
+ * - The action is a split expense action
* - The action is deleted and is not threaded
* - The report is archived and the action is not threaded
* - The action is a whisper action and it's neither a report preview nor IOU action
@@ -5646,11 +5745,12 @@ function shouldDisableThread(reportAction: OnyxEntry, reportID: st
const isDeletedAction = ReportActionsUtils.isDeletedAction(reportAction);
const isReportPreviewAction = ReportActionsUtils.isReportPreviewAction(reportAction);
const isIOUAction = ReportActionsUtils.isMoneyRequestAction(reportAction);
- const isWhisperAction = ReportActionsUtils.isWhisperAction(reportAction);
+ const isWhisperAction = ReportActionsUtils.isWhisperAction(reportAction) || ReportActionsUtils.isActionableTrackExpense(reportAction);
const isArchivedReport = isArchivedRoom(getReport(reportID));
+ const isActionDisabled = CONST.REPORT.ACTIONS.THREAD_DISABLED.some((action: string) => action === reportAction?.actionName);
return (
- CONST.REPORT.ACTIONS.THREAD_DISABLED.some((action: string) => action === reportAction?.actionName) ||
+ isActionDisabled ||
isSplitBillAction ||
(isDeletedAction && !reportAction?.childVisibleActionCount) ||
(isArchivedReport && !reportAction?.childVisibleActionCount) ||
@@ -5823,12 +5923,12 @@ function getIndicatedMissingPaymentMethod(userWallet: OnyxEntry, rep
* Checks if report chat contains missing payment method
*/
function hasMissingPaymentMethod(userWallet: OnyxEntry, iouReportID: string): boolean {
- const reportActions = reportActionsByReport?.[iouReportID] ?? {};
+ const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`] ?? {};
return Object.values(reportActions).some((action) => getIndicatedMissingPaymentMethod(userWallet, iouReportID, action) !== undefined);
}
/**
- * Used from money request actions to decide if we need to build an optimistic money request report.
+ * Used from expense actions to decide if we need to build an optimistic expense report.
Create a new report if:
- we don't have an iouReport set in the chatReport
- we have one, but it's waiting on the payee adding a bank account
@@ -5842,7 +5942,7 @@ function shouldCreateNewMoneyRequestReport(existingIOUReport: OnyxEntry
* Checks if report contains actions with errors
*/
function hasActionsWithErrors(reportID: string): boolean {
- const reportActions = reportActionsByReport?.[reportID ?? ''] ?? {};
+ const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? {};
return Object.values(reportActions).some((action) => !isEmptyObject(action.errors));
}
@@ -5863,6 +5963,31 @@ function getReportActionActorAccountID(reportAction: OnyxEntry, io
}
}
+function createDraftTransactionAndNavigateToParticipantSelector(transactionID: string, reportID: string, actionName: ValueOf, reportActionID: string): void {
+ const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction);
+ const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? ([] as ReportAction[]);
+
+ if (!transaction || !reportActions) {
+ return;
+ }
+
+ const linkedTrackedExpenseReportAction = Object.values(reportActions).find((action) => (action.originalMessage as IOUMessage)?.IOUTransactionID === transactionID);
+
+ const transactionAmount = Math.abs(transaction.amount);
+ const transactionCreated = format(new Date(transaction.created), CONST.DATE.FNS_FORMAT_STRING);
+
+ IOU.createDraftTransaction({
+ ...transaction,
+ actionableWhisperReportActionID: reportActionID,
+ linkedTrackedExpenseReportAction,
+ linkedTrackedExpenseReportID: reportID,
+ amount: transactionAmount,
+ created: transactionCreated,
+ } as Transaction);
+
+ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.getRoute(CONST.IOU.TYPE.REQUEST, transactionID, reportID, undefined, actionName));
+}
+
/**
* @returns the object to update `report.hasOutstandingChildRequest`
*/
@@ -5981,6 +6106,7 @@ export {
buildOptimisticMoneyRequestEntities,
buildOptimisticReportPreview,
buildOptimisticModifiedExpenseReportAction,
+ buildOptimisticMovedTrackedExpenseModifiedReportAction,
buildOptimisticCancelPaymentReportAction,
updateReportPreview,
buildOptimisticTaskReportAction,
@@ -6017,6 +6143,7 @@ export {
getDefaultWorkspaceAvatarTestID,
getCommentLength,
getParsedComment,
+ addDomainToShortMention,
getMoneyRequestOptions,
canCreateRequest,
hasIOUWaitingOnCurrentUserBankAccount,
@@ -6123,6 +6250,7 @@ export {
isGroupChat,
isTrackExpenseReport,
hasActionsWithErrors,
+ createDraftTransactionAndNavigateToParticipantSelector,
getReportActionActorAccountID,
getGroupChatName,
canLeavePolicyExpenseChat,
@@ -6134,6 +6262,7 @@ export {
buildParticipantsFromAccountIDs,
canReportBeMentionedWithinPolicy,
getAllHeldTransactions,
+ getPayeeName,
};
export type {
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index 3f1b354d70fc..9fc83d50f1e1 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -23,18 +23,14 @@ import * as ReportUtils from './ReportUtils';
import * as TaskUtils from './TaskUtils';
import * as UserUtils from './UserUtils';
-const reportActionsByReport: OnyxCollection = {};
const visibleReportActionItems: ReportActions = {};
-
Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
callback: (actions, key) => {
- if (!key || !actions) {
+ if (!actions || !key) {
return;
}
-
const reportID = CollectionUtils.extractCollectionItemID(key);
- reportActionsByReport[reportID] = actions;
const actionsArray: ReportAction[] = ReportActionsUtils.getSortedReportActions(Object.values(actions));
@@ -47,6 +43,7 @@ Onyx.connect({
reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED &&
reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
);
+
visibleReportActionItems[reportID] = reportActionsForDisplay[reportActionsForDisplay.length - 1];
},
});
@@ -87,7 +84,7 @@ function getOrderedReportIDs(
const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`;
const parentReportActions = allReportActions?.[parentReportActionsKey];
- const reportActions = reportActionsByReport?.[report.reportID] ?? {};
+ const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`] ?? {};
const parentReportAction = parentReportActions?.find((action) => action && action?.reportActionID === report.parentReportActionID);
const doesReportHaveViolations = !!(
betas?.includes(CONST.BETAS.VIOLATIONS) &&
@@ -398,7 +395,7 @@ function getOptionData({
result.isIOUReportOwner = ReportUtils.isIOUOwnedByCurrentUser(result as Report);
- if (ReportActionsUtils.isActionableJoinRequestPending(report.reportID)) {
+ if (ReportUtils.isJoinRequestInAdminRoom(report)) {
result.isPinned = true;
result.isUnread = true;
result.brickRoadIndicator = CONST.BRICK_ROAD_INDICATOR_STATUS.INFO;
diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts
index 95e2ec8a11b5..a5b85b87e37e 100644
--- a/src/libs/TransactionUtils.ts
+++ b/src/libs/TransactionUtils.ts
@@ -36,7 +36,7 @@ Onyx.connect({
});
function isDistanceRequest(transaction: OnyxEntry): boolean {
- // This is used during the request creation flow before the transaction has been saved to the server
+ // This is used during the expense creation flow before the transaction has been saved to the server
if (lodashHas(transaction, 'iouRequestType')) {
return transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE;
}
@@ -48,7 +48,7 @@ function isDistanceRequest(transaction: OnyxEntry