diff --git a/.eslintignore b/.eslintignore
index aa8b769dfede..3d966d096add 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -10,3 +10,4 @@ docs/vendor/**
docs/assets/**
web/gtm.js
**/.expo/**
+src/libs/SearchParser/searchParser.js
diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml
index ffce73644263..0d5879217ea0 100644
--- a/.github/workflows/e2ePerformanceTests.yml
+++ b/.github/workflows/e2ePerformanceTests.yml
@@ -257,12 +257,12 @@ jobs:
- name: Check if test failed, if so post the results and add the DeployBlocker label
id: checkIfRegressionDetected
run: |
- if grep -q '🔴' ./output.md; then
+ if grep -q '🔴' "./Host_Machine_Files/\$WORKING_DIRECTORY/output.md"; then
# Create an output to the GH action that the test failed:
echo "performanceRegressionDetected=true" >> "$GITHUB_OUTPUT"
gh pr edit ${{ inputs.PR_NUMBER }} --add-label DeployBlockerCash
- gh pr comment ${{ inputs.PR_NUMBER }} -F ./output.md
+ gh pr comment ${{ inputs.PR_NUMBER }} -F "./Host_Machine_Files/\$WORKING_DIRECTORY/output.md"
gh pr comment ${{ inputs.PR_NUMBER }} -b "@Expensify/mobile-deployers 📣 Please look into this performance regression as it's a deploy blocker."
else
echo "performanceRegressionDetected=false" >> "$GITHUB_OUTPUT"
diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml
index 476b01f87b07..3bfc0ed28d1a 100644
--- a/.github/workflows/typecheck.yml
+++ b/.github/workflows/typecheck.yml
@@ -34,7 +34,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' '__mocks__/*.js' '.storybook/*.js' 'assets/*.js' 'config/*.js' 'desktop/*.js' 'jest/*.js' 'scripts/*.js' 'tests/*.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)
+ 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' 'workflow_tests/*.js' '.github/libs/*.js' '.github/scripts/*.js' ':!src/libs/SearchParser/*.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/.prettierignore b/.prettierignore
index 09de20ba30b0..a9f7e1464529 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -19,3 +19,6 @@ package-lock.json
src/libs/E2E/reactNativeLaunchingTest.ts
# Temporary while we keep react-compiler in our repo
lib/**
+
+# Automatically generated files
+src/libs/SearchParser/searchParser.js
diff --git a/android/app/build.gradle b/android/app/build.gradle
index c53ad2cd3cc7..bf3aa74e537f 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -107,8 +107,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1009000512
- versionName "9.0.5-12"
+ versionCode 1009000601
+ versionName "9.0.6-1"
// Supported language variants must be declared here to avoid from being removed during the compilation.
// This also helps us to not include unnecessary language variants in the APK.
resConfigs "en", "es"
diff --git a/assets/images/simple-illustrations/simple-illustration__empty-state.svg b/assets/images/simple-illustrations/simple-illustration__empty-state.svg
new file mode 100644
index 000000000000..154b2269c285
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__empty-state.svg
@@ -0,0 +1,102 @@
+
+
+
diff --git a/assets/images/simple-illustrations/simple-illustration__receipt-location-marker.svg b/assets/images/simple-illustrations/simple-illustration__receipt-location-marker.svg
new file mode 100644
index 000000000000..01669d07c0f0
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__receipt-location-marker.svg
@@ -0,0 +1,50 @@
+
+
\ No newline at end of file
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 2ccce98e6a21..680e0ed493a1 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 9.0.5
+ 9.0.6
CFBundleSignature
????
CFBundleURLTypes
@@ -40,7 +40,7 @@
CFBundleVersion
- 9.0.5.12
+ 9.0.6.1
FullStory
OrgId
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 8248e7db0454..14d830fc2300 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 9.0.5
+ 9.0.6
CFBundleSignature
????
CFBundleVersion
- 9.0.5.12
+ 9.0.6.1
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index 87cdb420af38..33bae95b8ae4 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -11,9 +11,9 @@
CFBundleName
$(PRODUCT_NAME)
CFBundleShortVersionString
- 9.0.5
+ 9.0.6
CFBundleVersion
- 9.0.5.12
+ 9.0.6.1
NSExtension
NSExtensionPointIdentifier
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 29ab90c4b7db..50dfc65d07b2 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1871,7 +1871,7 @@ PODS:
- RNGoogleSignin (10.0.1):
- GoogleSignIn (~> 7.0)
- React-Core
- - RNLiveMarkdown (0.1.91):
+ - RNLiveMarkdown (0.1.103):
- glog
- hermes-engine
- RCT-Folly (= 2022.05.16.00)
@@ -1889,9 +1889,9 @@ PODS:
- React-utils
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- - RNLiveMarkdown/common (= 0.1.91)
+ - RNLiveMarkdown/common (= 0.1.103)
- Yoga
- - RNLiveMarkdown/common (0.1.91):
+ - RNLiveMarkdown/common (0.1.103):
- glog
- hermes-engine
- RCT-Folly (= 2022.05.16.00)
@@ -1935,7 +1935,7 @@ PODS:
- ReactCommon/turbomodule/core
- Turf
- Yoga
- - RNPermissions (3.9.3):
+ - RNPermissions (3.10.1):
- glog
- hermes-engine
- RCT-Folly (= 2022.05.16.00)
@@ -2614,10 +2614,10 @@ SPEC CHECKSUMS:
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
RNGestureHandler: 74b7b3d06d667ba0bbf41da7718f2607ae0dfe8f
RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0
- RNLiveMarkdown: 24fbb7370eefee2f325fb64cfe904b111ffcd81b
+ RNLiveMarkdown: f12157fc91b72e19705c9cc8c98034c4c1669d5a
RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81
rnmapbox-maps: df8fe93dbd251f25022f4023d31bc04160d4d65c
- RNPermissions: 0b61d30d21acbeafe25baaa47d9bae40a0c65216
+ RNPermissions: d2392b754e67bc14491f5b12588bef2864e783f3
RNReactNativeHapticFeedback: 616c35bdec7d20d4c524a7949ca9829c09e35f37
RNReanimated: 323436b1a5364dca3b5f8b1a13458455e0de9efe
RNScreens: abd354e98519ed267600b7ee64fdcb8e060b1218
diff --git a/package-lock.json b/package-lock.json
index 9a2cea929335..5f0fde749efa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,19 +1,19 @@
{
"name": "new.expensify",
- "version": "9.0.5-12",
+ "version": "9.0.6-1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "9.0.5-12",
+ "version": "9.0.6-1",
"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.91",
+ "@expensify/react-native-live-markdown": "0.1.103",
"@expo/metro-runtime": "~3.1.1",
"@formatjs/intl-datetimeformat": "^6.10.0",
"@formatjs/intl-listformat": "^7.2.2",
@@ -106,7 +106,7 @@
"react-native-pager-view": "6.2.3",
"react-native-pdf": "6.7.3",
"react-native-performance": "^5.1.0",
- "react-native-permissions": "^3.9.3",
+ "react-native-permissions": "^3.10.0",
"react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#da50d2c5c54e268499047f9cc98b8df4196c1ddf",
"react-native-plaid-link-sdk": "11.5.0",
"react-native-qrcode-svg": "^6.2.0",
@@ -233,6 +233,7 @@
"memfs": "^4.6.0",
"onchange": "^7.1.0",
"patch-package": "^8.0.0",
+ "peggy": "^4.0.3",
"portfinder": "^1.0.28",
"prettier": "^2.8.8",
"pusher-js-mock": "^0.3.3",
@@ -3784,9 +3785,9 @@
}
},
"node_modules/@expensify/react-native-live-markdown": {
- "version": "0.1.91",
- "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.91.tgz",
- "integrity": "sha512-6uQTgwhpvLqQKdtNqSgh45sRuQRXzv/WwyhdvQNge6EYtulyGFqT82GIP+LIGW8Xnl73nzFZTuMKwWxFFR/Cow==",
+ "version": "0.1.103",
+ "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.103.tgz",
+ "integrity": "sha512-w9jQoxBE9LghfL8UdYbG+8A+CApmER/XMH8N7/bINn7w57+FnnBa5ckPWx6/UYX7OYsmYxSaHJLQkJEXYlDRZg==",
"workspaces": [
"parser",
"example",
@@ -7879,6 +7880,33 @@
"react-native": ">=0.70.0 <1.0.x"
}
},
+ "node_modules/@peggyjs/from-mem": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@peggyjs/from-mem/-/from-mem-1.3.0.tgz",
+ "integrity": "sha512-kzGoIRJjkg3KuGI4bopz9UvF3KguzfxalHRDEIdqEZUe45xezsQ6cx30e0RKuxPUexojQRBfu89Okn7f4/QXsw==",
+ "dev": true,
+ "dependencies": {
+ "semver": "7.6.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@peggyjs/from-mem/node_modules/semver": {
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+ "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/@perf-profiler/android": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/@perf-profiler/android/-/android-0.12.1.tgz",
@@ -35858,6 +35886,32 @@
"through2": "^2.0.3"
}
},
+ "node_modules/peggy": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/peggy/-/peggy-4.0.3.tgz",
+ "integrity": "sha512-v7/Pt6kGYsfXsCrfb52q7/yg5jaAwiVaUMAPLPvy4DJJU6Wwr72t6nDIqIDkGfzd1B4zeVuTnQT0RGeOhe/uSA==",
+ "dev": true,
+ "dependencies": {
+ "@peggyjs/from-mem": "1.3.0",
+ "commander": "^12.1.0",
+ "source-map-generator": "0.8.0"
+ },
+ "bin": {
+ "peggy": "bin/peggy.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/peggy/node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/pend": {
"version": "1.2.0",
"dev": true,
@@ -37435,8 +37489,9 @@
}
},
"node_modules/react-native-permissions": {
- "version": "3.9.3",
- "license": "MIT",
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/react-native-permissions/-/react-native-permissions-3.10.1.tgz",
+ "integrity": "sha512-Gc5BxxpjZn4QNUDiVeHOO0vXh3AH7ToolmwTJozqC6DsxV7NAf3ttap+8BSmzDR8WxuAM3Cror+YNiBhHJx7/w==",
"peerDependencies": {
"react": ">=16.13.1",
"react-native": ">=0.63.3",
@@ -40226,6 +40281,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/source-map-generator": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/source-map-generator/-/source-map-generator-0.8.0.tgz",
+ "integrity": "sha512-psgxdGMwl5MZM9S3FWee4EgsEaIjahYV5AzGnwUvPhWeITz/j6rKpysQHlQ4USdxvINlb8lKfWGIXwfkrgtqkA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.0.2",
"license": "BSD-3-Clause",
diff --git a/package.json b/package.json
index 0c7631f0c914..d14553213691 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "9.0.5-12",
+ "version": "9.0.6-1",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
@@ -61,13 +61,14 @@
"workflow-test:generate": "ts-node workflow_tests/utils/preGenerateTest.ts",
"setup-https": "mkcert -install && mkcert -cert-file config/webpack/certificate.pem -key-file config/webpack/key.pem dev.new.expensify.com localhost 127.0.0.1",
"e2e-test-runner-build": "ncc build tests/e2e/testRunner.ts -o tests/e2e/dist/",
- "react-compiler-healthcheck": "react-compiler-healthcheck --verbose"
+ "react-compiler-healthcheck": "react-compiler-healthcheck --verbose",
+ "generate-search-parser": "peggy --format es -o src/libs/SearchParser/searchParser.js src/libs/SearchParser/searchParser.peggy "
},
"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.91",
+ "@expensify/react-native-live-markdown": "0.1.103",
"@expo/metro-runtime": "~3.1.1",
"@formatjs/intl-datetimeformat": "^6.10.0",
"@formatjs/intl-listformat": "^7.2.2",
@@ -160,7 +161,7 @@
"react-native-pager-view": "6.2.3",
"react-native-pdf": "6.7.3",
"react-native-performance": "^5.1.0",
- "react-native-permissions": "^3.9.3",
+ "react-native-permissions": "^3.10.0",
"react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#da50d2c5c54e268499047f9cc98b8df4196c1ddf",
"react-native-plaid-link-sdk": "11.5.0",
"react-native-qrcode-svg": "^6.2.0",
@@ -287,6 +288,7 @@
"memfs": "^4.6.0",
"onchange": "^7.1.0",
"patch-package": "^8.0.0",
+ "peggy": "^4.0.3",
"portfinder": "^1.0.28",
"prettier": "^2.8.8",
"pusher-js-mock": "^0.3.3",
diff --git a/patches/@expensify+react-native-live-markdown+0.1.91.patch b/patches/@expensify+react-native-live-markdown+0.1.91.patch
deleted file mode 100644
index c77e46accae3..000000000000
--- a/patches/@expensify+react-native-live-markdown+0.1.91.patch
+++ /dev/null
@@ -1,13 +0,0 @@
-diff --git a/node_modules/@expensify/react-native-live-markdown/src/web/cursorUtils.ts b/node_modules/@expensify/react-native-live-markdown/src/web/cursorUtils.ts
-index 1cda659..ba5c3c3 100644
---- a/node_modules/@expensify/react-native-live-markdown/src/web/cursorUtils.ts
-+++ b/node_modules/@expensify/react-native-live-markdown/src/web/cursorUtils.ts
-@@ -66,7 +66,7 @@ function setCursorPosition(target: HTMLElement, start: number, end: number | nul
- // 3. Caret at the end of whole input, when pressing enter
- // 4. All other placements
- if (prevChar === '\n' && prevTextLength !== undefined && prevTextLength < textCharacters.length) {
-- if (nextChar !== '\n') {
-+ if (nextChar && nextChar !== '\n' && i !== n - 1) {
- range.setStart(textNodes[i + 1] as Node, 0);
- } else if (i !== textNodes.length - 1) {
- range.setStart(textNodes[i] as Node, 1);
diff --git a/scripts/applyPatches.sh b/scripts/applyPatches.sh
index a4be88984561..9145629015ee 100755
--- a/scripts/applyPatches.sh
+++ b/scripts/applyPatches.sh
@@ -8,24 +8,17 @@ SCRIPTS_DIR=$(dirname "${BASH_SOURCE[0]}")
source "$SCRIPTS_DIR/shellUtils.sh"
# Wrapper to run patch-package.
-# We use `script` to preserve colorization when the output of patch-package is piped to tee
-# and we provide /dev/null to discard the output rather than sending it to a file
-# `script` has different syntax on macOS vs linux, so that's why we need a wrapper function
function patchPackage {
OS="$(uname)"
- if [[ "$OS" == "Darwin" ]]; then
- # macOS
- script -q /dev/null npx patch-package --error-on-fail
- elif [[ "$OS" == "Linux" ]]; then
- # Ubuntu/Linux
- script -q -c "npx patch-package --error-on-fail" /dev/null
+ if [[ "$OS" == "Darwin" || "$OS" == "Linux" ]]; then
+ npx patch-package --error-on-fail
else
error "Unsupported OS: $OS"
+ exit 1
fi
}
# Run patch-package and capture its output and exit code, while still displaying the original output to the terminal
-# (we use `script -q /dev/null` to preserve colorization in the output)
TEMP_OUTPUT="$(mktemp)"
patchPackage 2>&1 | tee "$TEMP_OUTPUT"
EXIT_CODE=${PIPESTATUS[0]}
@@ -36,7 +29,7 @@ rm -f "$TEMP_OUTPUT"
echo "$OUTPUT" | grep -q "Warning:"
WARNING_FOUND=$?
-printf "\n";
+printf "\n"
# Determine the final exit code
if [ "$EXIT_CODE" -eq 0 ]; then
diff --git a/src/CONST.ts b/src/CONST.ts
index b809bdaacaf6..2f34b640c815 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -840,6 +840,8 @@ const CONST = {
IOU: 'iou',
TASK: 'task',
INVOICE: 'invoice',
+ PAYCHECK: 'paycheck',
+ BILL: 'bill',
},
CHAT_TYPE: chatTypes,
WORKSPACE_CHAT_ROOMS: {
@@ -1123,8 +1125,6 @@ const CONST = {
// around each header.
EMOJI_NUM_PER_ROW: 8,
- EMOJI_FREQUENT_ROW_COUNT: 3,
-
EMOJI_DEFAULT_SKIN_TONE: -1,
// Amount of emojis to render ahead at the end of the update cycle
@@ -1245,7 +1245,7 @@ const CONST = {
MAX_AMOUNT_OF_SUGGESTIONS: 20,
MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER: 5,
HERE_TEXT: '@here',
- SUGGESTION_BOX_MAX_SAFE_DISTANCE: 38,
+ SUGGESTION_BOX_MAX_SAFE_DISTANCE: 10,
BIG_SCREEN_SUGGESTION_WIDTH: 300,
},
COMPOSER_MAX_HEIGHT: 125,
@@ -5250,6 +5250,13 @@ const CONST = {
},
EXCLUDE_FROM_LAST_VISITED_PATH: [SCREENS.NOT_FOUND, SCREENS.SAML_SIGN_IN, SCREENS.VALIDATE_LOGIN] as string[],
+
+ EMPTY_STATE_MEDIA: {
+ ANIMATION: 'animation',
+ ILLUSTRATION: 'illustration',
+ VIDEO: 'video',
+ },
+
UPGRADE_FEATURE_INTRO_MAPPING: [
{
id: 'reportFields',
@@ -5260,6 +5267,7 @@ const CONST = {
icon: 'Pencil',
},
],
+
REPORT_FIELD_TYPES: {
TEXT: 'text',
DATE: 'date',
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index bd4b294a6d68..8abb7738289c 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -445,6 +445,9 @@ const ONYXKEYS = {
* So for example: card_12345_Expensify Card
*/
WORKSPACE_CARDS_LIST: 'card_',
+
+ /** The bank account that Expensify Card payments will be reconciled against */
+ SHARED_NVP_EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION: 'sharedNVP_expensifyCard_continuousReconciliationConnection_',
},
/** List of Form ids */
@@ -535,8 +538,8 @@ const ONYXKEYS = {
REPORT_VIRTUAL_CARD_FRAUD_DRAFT: 'reportVirtualCardFraudFormDraft',
GET_PHYSICAL_CARD_FORM: 'getPhysicalCardForm',
GET_PHYSICAL_CARD_FORM_DRAFT: 'getPhysicalCardFormDraft',
- REPORT_FIELD_EDIT_FORM: 'reportFieldEditForm',
- REPORT_FIELD_EDIT_FORM_DRAFT: 'reportFieldEditFormDraft',
+ REPORT_FIELDS_EDIT_FORM: 'reportFieldsEditForm',
+ REPORT_FIELDS_EDIT_FORM_DRAFT: 'reportFieldsEditFormDraft',
REIMBURSEMENT_ACCOUNT_FORM: 'reimbursementAccount',
REIMBURSEMENT_ACCOUNT_FORM_DRAFT: 'reimbursementAccountDraft',
PERSONAL_BANK_ACCOUNT_FORM: 'personalBankAccount',
@@ -622,7 +625,7 @@ type OnyxFormValuesMapping = {
[ONYXKEYS.FORMS.REPORT_VIRTUAL_CARD_FRAUD]: FormTypes.ReportVirtualCardFraudForm;
[ONYXKEYS.FORMS.REPORT_PHYSICAL_CARD_FORM]: FormTypes.ReportPhysicalCardForm;
[ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM]: FormTypes.GetPhysicalCardForm;
- [ONYXKEYS.FORMS.REPORT_FIELD_EDIT_FORM]: FormTypes.ReportFieldEditForm;
+ [ONYXKEYS.FORMS.REPORT_FIELDS_EDIT_FORM]: FormTypes.ReportFieldsEditForm;
[ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM]: FormTypes.ReimbursementAccountForm;
[ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT_FORM]: FormTypes.PersonalBankAccountForm;
[ONYXKEYS.FORMS.WORKSPACE_DESCRIPTION_FORM]: FormTypes.WorkspaceDescriptionForm;
@@ -692,6 +695,7 @@ type OnyxCollectionValuesMapping = {
[ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END]: OnyxTypes.BillingGraceEndPeriod;
[ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_EXPENSIFY_CARD_SETTINGS]: OnyxTypes.ExpensifyCardSettings;
[ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST]: OnyxTypes.WorkspaceCardsList;
+ [ONYXKEYS.COLLECTION.SHARED_NVP_EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION]: OnyxTypes.BankAccount;
};
type OnyxValuesMapping = {
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index a54bb4f5cca5..2189522e45ea 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -676,6 +676,10 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/accounting/quickbooks-online/invoice-account-selector',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/invoice-account-selector` as const,
},
+ WORKSPACE_ACCOUNTING_RECONCILIATION_ACCOUNT_SETTINGS: {
+ route: 'settings/workspaces/:policyID/accounting/:connection/card-reconciliation',
+ getRoute: (policyID: string, connection: ValueOf) => `settings/workspaces/${policyID}/accounting/${connection}/card-reconciliation` as const,
+ },
WORKSPACE_CATEGORIES: {
route: 'settings/workspaces/:policyID/categories',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/categories` as const,
@@ -797,30 +801,30 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/reportFields/new',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/reportFields/new` as const,
},
- WORKSPACE_REPORT_FIELD_SETTINGS: {
- route: 'settings/workspaces/:policyID/reportField/:reportFieldID/edit',
- getRoute: (policyID: string, reportFieldID: string) => `settings/workspaces/${policyID}/reportField/${encodeURIComponent(reportFieldID)}/edit` as const,
+ WORKSPACE_REPORT_FIELDS_SETTINGS: {
+ route: 'settings/workspaces/:policyID/reportFields/:reportFieldID/edit',
+ getRoute: (policyID: string, reportFieldID: string) => `settings/workspaces/${policyID}/reportFields/${encodeURIComponent(reportFieldID)}/edit` as const,
},
- WORKSPACE_REPORT_FIELD_LIST_VALUES: {
- route: 'settings/workspaces/:policyID/reportField/listValues/:reportFieldID?',
- getRoute: (policyID: string, reportFieldID?: string) => `settings/workspaces/${policyID}/reportField/listValues/${encodeURIComponent(reportFieldID ?? '')}` as const,
+ WORKSPACE_REPORT_FIELDS_LIST_VALUES: {
+ route: 'settings/workspaces/:policyID/reportFields/listValues/:reportFieldID?',
+ getRoute: (policyID: string, reportFieldID?: string) => `settings/workspaces/${policyID}/reportFields/listValues/${encodeURIComponent(reportFieldID ?? '')}` as const,
},
- WORKSPACE_REPORT_FIELD_ADD_VALUE: {
- route: 'settings/workspaces/:policyID/reportField/addValue/:reportFieldID?',
- getRoute: (policyID: string, reportFieldID?: string) => `settings/workspaces/${policyID}/reportField/addValue/${encodeURIComponent(reportFieldID ?? '')}` as const,
+ WORKSPACE_REPORT_FIELDS_ADD_VALUE: {
+ route: 'settings/workspaces/:policyID/reportFields/addValue/:reportFieldID?',
+ getRoute: (policyID: string, reportFieldID?: string) => `settings/workspaces/${policyID}/reportFields/addValue/${encodeURIComponent(reportFieldID ?? '')}` as const,
},
- WORKSPACE_REPORT_FIELD_VALUE_SETTINGS: {
- route: 'settings/workspaces/:policyID/reportField/:valueIndex/:reportFieldID?',
+ WORKSPACE_REPORT_FIELDS_VALUE_SETTINGS: {
+ route: 'settings/workspaces/:policyID/reportFields/:valueIndex/:reportFieldID?',
getRoute: (policyID: string, valueIndex: number, reportFieldID?: string) =>
- `settings/workspaces/${policyID}/reportField/${valueIndex}/${encodeURIComponent(reportFieldID ?? '')}` as const,
+ `settings/workspaces/${policyID}/reportFields/${valueIndex}/${encodeURIComponent(reportFieldID ?? '')}` as const,
},
- WORKSPACE_REPORT_FIELD_EDIT_VALUE: {
- route: 'settings/workspaces/:policyID/reportField/new/:valueIndex/edit',
- getRoute: (policyID: string, valueIndex: number) => `settings/workspaces/${policyID}/reportField/new/${valueIndex}/edit` as const,
+ WORKSPACE_REPORT_FIELDS_EDIT_VALUE: {
+ route: 'settings/workspaces/:policyID/reportFields/new/:valueIndex/edit',
+ getRoute: (policyID: string, valueIndex: number) => `settings/workspaces/${policyID}/reportFields/new/${valueIndex}/edit` as const,
},
- WORKSPACE_EDIT_REPORT_FIELD_INITIAL_VALUE: {
- route: 'settings/workspaces/:policyID/reportField/:reportFieldID/edit/initialValue',
- getRoute: (policyID: string, reportFieldID: string) => `settings/workspaces/${policyID}/reportField/${encodeURIComponent(reportFieldID)}/edit/initialValue` as const,
+ WORKSPACE_EDIT_REPORT_FIELDS_INITIAL_VALUE: {
+ route: 'settings/workspaces/:policyID/reportFields/:reportFieldID/edit/initialValue',
+ getRoute: (policyID: string, reportFieldID: string) => `settings/workspaces/${policyID}/reportFields/${encodeURIComponent(reportFieldID)}/edit/initialValue` as const,
},
WORKSPACE_EXPENSIFY_CARD: {
route: 'settings/workspaces/:policyID/expensify-card',
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index d2a6b7c19ddd..0768ca8bb291 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -330,6 +330,7 @@ const SCREENS = {
SAGE_INTACCT_NON_REIMBURSABLE_CREDIT_CARD_ACCOUNT: 'Policy_Accounting_Sage_Intacct_Non_Reimbursable_Credit_Card_Account',
SAGE_INTACCT_ADVANCED: 'Policy_Accounting_Sage_Intacct_Advanced',
SAGE_INTACCT_PAYMENT_ACCOUNT: 'Policy_Accounting_Sage_Intacct_Payment_Account',
+ RECONCILIATION_ACCOUNT_SETTINGS: 'Policy_Accounting_Reconciliation_Account_Settings',
},
INITIAL: 'Workspace_Initial',
PROFILE: 'Workspace_Profile',
@@ -353,7 +354,7 @@ const SCREENS = {
TAG_EDIT: 'Tag_Edit',
TAXES: 'Workspace_Taxes',
REPORT_FIELDS: 'Workspace_ReportFields',
- REPORT_FIELD_SETTINGS: 'Workspace_ReportField_Settings',
+ REPORT_FIELDS_SETTINGS: 'Workspace_ReportFields_Settings',
REPORT_FIELDS_CREATE: 'Workspace_ReportFields_Create',
REPORT_FIELDS_LIST_VALUES: 'Workspace_ReportFields_ListValues',
REPORT_FIELDS_ADD_VALUE: 'Workspace_ReportFields_AddValue',
diff --git a/src/components/AccountingListSkeletonView.tsx b/src/components/AccountingListSkeletonView.tsx
index b977903d3adc..dbe8ada6c4b7 100644
--- a/src/components/AccountingListSkeletonView.tsx
+++ b/src/components/AccountingListSkeletonView.tsx
@@ -4,12 +4,14 @@ import ItemListSkeletonView from './Skeletons/ItemListSkeletonView';
type AccountingListSkeletonViewProps = {
shouldAnimate?: boolean;
+ gradientOpacityEnabled?: boolean;
};
-function AccountingListSkeletonView({shouldAnimate = true}: AccountingListSkeletonViewProps) {
+function AccountingListSkeletonView({shouldAnimate = true, gradientOpacityEnabled = false}: AccountingListSkeletonViewProps) {
return (
(
<>
& {
+ contentHeight: number;
+ topInset: number;
+};
+function isEnoughSpaceToRenderMenuAboveCursor({y, cursorCoordinates, scrollValue, contentHeight, topInset}: IsEnoughSpaceToRenderMenuAboveCursor): boolean {
+ return y + (cursorCoordinates.y - scrollValue) > contentHeight + topInset + CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_BOX_MAX_SAFE_DISTANCE;
}
/**
@@ -35,7 +43,7 @@ function isSuggestionRenderedAbove(isEnoughSpaceAboveForBig: boolean, isEnoughSp
function AutoCompleteSuggestions({measureParentContainerAndReportCursor = () => {}, ...props}: AutoCompleteSuggestionsProps) {
const containerRef = React.useRef(null);
const isInitialRender = React.useRef(true);
- const isSuggestionAboveRef = React.useRef(false);
+ const isSuggestionMenuAboveRef = React.useRef(false);
const leftValue = React.useRef(0);
const prevLeftValue = React.useRef(0);
const {windowHeight, windowWidth, isSmallScreenWidth} = useWindowDimensions();
@@ -44,11 +52,12 @@ function AutoCompleteSuggestions({measureParentContainerAndReportCu
width: 0,
left: 0,
bottom: 0,
+ cursorCoordinates: {x: 0, y: 0},
});
const StyleUtils = useStyleUtils();
const insets = useSafeAreaInsets();
const {keyboardHeight} = useKeyboardState();
- const {paddingBottom: bottomInset} = StyleUtils.getSafeAreaPadding(insets ?? undefined);
+ const {paddingBottom: bottomInset, paddingTop: topInset} = StyleUtils.getSafeAreaPadding(insets ?? undefined);
useEffect(() => {
const container = containerRef.current;
@@ -73,51 +82,51 @@ function AutoCompleteSuggestions({measureParentContainerAndReportCu
measureParentContainerAndReportCursor(({x, y, width, scrollValue, cursorCoordinates}: MeasureParentContainerAndCursor) => {
const xCoordinatesOfCursor = x + cursorCoordinates.x;
- const leftValueForBigScreen =
+ const bigScreenLeftOffset =
xCoordinatesOfCursor + CONST.AUTO_COMPLETE_SUGGESTER.BIG_SCREEN_SUGGESTION_WIDTH > windowWidth
? windowWidth - CONST.AUTO_COMPLETE_SUGGESTER.BIG_SCREEN_SUGGESTION_WIDTH
: xCoordinatesOfCursor;
-
- let bottomValue = windowHeight - y - cursorCoordinates.y + scrollValue - (keyboardHeight || bottomInset);
- const widthValue = isSmallScreenWidth ? width : CONST.AUTO_COMPLETE_SUGGESTER.BIG_SCREEN_SUGGESTION_WIDTH;
-
const contentMaxHeight = measureHeightOfSuggestionRows(suggestionsLength, true);
const contentMinHeight = measureHeightOfSuggestionRows(suggestionsLength, false);
- const isEnoughSpaceAboveForBig = windowHeight - bottomValue - contentMaxHeight > CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_BOX_MAX_SAFE_DISTANCE;
- const isEnoughSpaceAboveForSmall = windowHeight - bottomValue - contentMinHeight > CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_BOX_MAX_SAFE_DISTANCE;
+ let bottomValue = windowHeight - (cursorCoordinates.y - scrollValue + y) - keyboardHeight;
+ const widthValue = isSmallScreenWidth ? width : CONST.AUTO_COMPLETE_SUGGESTER.BIG_SCREEN_SUGGESTION_WIDTH;
+
+ const isEnoughSpaceToRenderMenuAboveForBig = isEnoughSpaceToRenderMenuAboveCursor({y, cursorCoordinates, scrollValue, contentHeight: contentMaxHeight, topInset});
+ const isEnoughSpaceToRenderMenuAboveForSmall = isEnoughSpaceToRenderMenuAboveCursor({y, cursorCoordinates, scrollValue, contentHeight: contentMinHeight, topInset});
- const newLeftValue = isSmallScreenWidth ? x : leftValueForBigScreen;
+ const newLeftOffset = isSmallScreenWidth ? x : bigScreenLeftOffset;
// If the suggested word is longer than 150 (approximately half the width of the suggestion popup), then adjust a new position of popup
- const isAdjustmentNeeded = Math.abs(prevLeftValue.current - leftValueForBigScreen) > 150;
+ const isAdjustmentNeeded = Math.abs(prevLeftValue.current - bigScreenLeftOffset) > 150;
if (isInitialRender.current || isAdjustmentNeeded) {
- isSuggestionAboveRef.current = isSuggestionRenderedAbove(isEnoughSpaceAboveForBig, isEnoughSpaceAboveForSmall);
- leftValue.current = newLeftValue;
+ isSuggestionMenuAboveRef.current = isSuggestionMenuRenderedAbove(isEnoughSpaceToRenderMenuAboveForBig, isEnoughSpaceToRenderMenuAboveForSmall);
+ leftValue.current = newLeftOffset;
isInitialRender.current = false;
- prevLeftValue.current = newLeftValue;
+ prevLeftValue.current = newLeftOffset;
}
let measuredHeight = 0;
- if (isSuggestionAboveRef.current && isEnoughSpaceAboveForBig) {
+ if (isSuggestionMenuAboveRef.current && isEnoughSpaceToRenderMenuAboveForBig) {
// calculation for big suggestion box above the cursor
measuredHeight = measureHeightOfSuggestionRows(suggestionsLength, true);
- } else if (isSuggestionAboveRef.current && isEnoughSpaceAboveForSmall) {
+ } else if (isSuggestionMenuAboveRef.current && isEnoughSpaceToRenderMenuAboveForSmall) {
// calculation for small suggestion box above the cursor
measuredHeight = measureHeightOfSuggestionRows(suggestionsLength, false);
} else {
// calculation for big suggestion box below the cursor
measuredHeight = measureHeightOfSuggestionRows(suggestionsLength, true);
- bottomValue = windowHeight - y - cursorCoordinates.y + scrollValue - measuredHeight - CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT;
+ bottomValue = windowHeight - y - cursorCoordinates.y + scrollValue - measuredHeight - CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT - keyboardHeight;
}
setSuggestionHeight(measuredHeight);
setContainerState({
left: leftValue.current,
bottom: bottomValue,
width: widthValue,
+ cursorCoordinates,
});
});
- }, [measureParentContainerAndReportCursor, windowHeight, windowWidth, keyboardHeight, isSmallScreenWidth, suggestionsLength, bottomInset]);
+ }, [measureParentContainerAndReportCursor, windowHeight, windowWidth, keyboardHeight, isSmallScreenWidth, suggestionsLength, bottomInset, topInset]);
- if (containerState.width === 0 && containerState.left === 0 && containerState.bottom === 0) {
+ if ((containerState.width === 0 && containerState.left === 0 && containerState.bottom === 0) || (containerState.cursorCoordinates.x === 0 && containerState.cursorCoordinates.y === 0)) {
return null;
}
return (
diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx
index e641a0c2218a..4cbf85cb0014 100644
--- a/src/components/Breadcrumbs.tsx
+++ b/src/components/Breadcrumbs.tsx
@@ -36,10 +36,11 @@ function Breadcrumbs({breadcrumbs, style}: BreadcrumbsProps) {
const theme = useTheme();
const styles = useThemeStyles();
const [primaryBreadcrumb, secondaryBreadcrumb] = breadcrumbs;
+ const isRootBreadcrumb = primaryBreadcrumb.type === CONST.BREADCRUMB_TYPE.ROOT;
const fontScale = PixelRatio.getFontScale() > CONST.LOGO_MAX_SCALE ? CONST.LOGO_MAX_SCALE : PixelRatio.getFontScale();
return (
- {primaryBreadcrumb.type === CONST.BREADCRUMB_TYPE.ROOT ? (
+ {isRootBreadcrumb ? (
/
{secondaryBreadcrumb.text}
diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx
index 4b3f0f70db24..0fd3cc0728ca 100644
--- a/src/components/Button/index.tsx
+++ b/src/components/Button/index.tsx
@@ -118,6 +118,9 @@ type ButtonProps = Partial & {
/** Whether the button should use split style or not */
isSplitButton?: boolean;
+
+ /** Whether button's content should be centered */
+ isContentCentered?: boolean;
};
type KeyboardShortcutComponentProps = Pick;
@@ -202,6 +205,7 @@ function Button(
id = '',
accessibilityLabel = '',
isSplitButton = false,
+ isContentCentered = false,
...rest
}: ButtonProps,
ref: ForwardedRef,
@@ -239,7 +243,7 @@ function Button(
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
if (icon || shouldShowRightIcon) {
return (
-
+
{icon && (
diff --git a/src/components/ConfirmContent.tsx b/src/components/ConfirmContent.tsx
index 26331f92401c..36f24c2a3477 100644
--- a/src/components/ConfirmContent.tsx
+++ b/src/components/ConfirmContent.tsx
@@ -14,8 +14,11 @@ import type IconAsset from '@src/types/utils/IconAsset';
import Button from './Button';
import Header from './Header';
import Icon from './Icon';
+import {Close} from './Icon/Expensicons';
import ImageSVG from './ImageSVG';
+import {PressableWithoutFeedback} from './Pressable';
import Text from './Text';
+import Tooltip from './Tooltip';
type ConfirmContentProps = {
/** Title of the modal */
@@ -51,15 +54,36 @@ type ConfirmContentProps = {
/** Icon to display above the title */
iconSource?: IconAsset;
+ /** Fill color for the Icon */
+ iconFill?: string | false;
+
+ /** Icon width */
+ iconWidth?: number;
+
+ /** Icon height */
+ iconHeight?: number;
+
+ /** Should the icon be centered? */
+ shouldCenterIcon?: boolean;
+
/** Whether to center the icon / text content */
shouldCenterContent?: boolean;
+ /** Whether to show the dismiss icon */
+ shouldShowDismissIcon?: boolean;
+
/** Whether to stack the buttons */
shouldStackButtons?: boolean;
+ /** Whether to reverse the order of the stacked buttons */
+ shouldReverseStackedButtons?: boolean;
+
/** Styles for title */
titleStyles?: StyleProp;
+ /** Styles for title container */
+ titleContainerStyles?: StyleProp;
+
/** Styles for prompt */
promptStyles?: StyleProp;
@@ -85,13 +109,20 @@ function ConfirmContent({
shouldDisableConfirmButtonWhenOffline = false,
shouldShowCancelButton = false,
iconSource,
+ iconFill,
shouldCenterContent = false,
shouldStackButtons = true,
titleStyles,
promptStyles,
contentStyles,
iconAdditionalStyles,
+ iconWidth = variables.appModalAppIconSize,
+ iconHeight = variables.appModalAppIconSize,
+ shouldCenterIcon = false,
+ shouldShowDismissIcon = false,
image,
+ titleContainerStyles,
+ shouldReverseStackedButtons = false,
}: ConfirmContentProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
@@ -116,19 +147,35 @@ function ConfirmContent({
)}
+ {shouldShowDismissIcon && (
+
+
+
+
+
+
+
+ )}
- {typeof iconSource === 'function' && (
-
+ {iconSource && (
+
)}
-
+
+ {shouldShowCancelButton && shouldReverseStackedButtons && (
+
+ )}
- {shouldShowCancelButton && (
+ {shouldShowCancelButton && !shouldReverseStackedButtons && (