diff --git a/.eslintrc.js b/.eslintrc.js index d24ea9766e19..9f839e45ce75 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -55,6 +55,11 @@ const restrictedImportPaths = [ name: 'date-fns/locale', message: "Do not import 'date-fns/locale' directly. Please use the submodule import instead, like 'date-fns/locale/en-GB'.", }, + { + name: 'expensify-common', + importNames: ['Device'], + message: "Do not import Device directly, it's known to make VSCode's IntelliSense crash. Please import the desired module from `expensify-common/dist/Device` instead.", + }, ]; const restrictedImportPatterns = [ diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml index 4ad6d54e2f24..20bf0d257a9b 100644 --- a/.github/workflows/platformDeploy.yml +++ b/.github/workflows/platformDeploy.yml @@ -97,7 +97,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: android-sourcemap - path: android/app/build/generated/sourcemaps/react/release/*.map + path: android/app/build/generated/sourcemaps/react/productionRelease/index.android.bundle.map - name: Upload Android version to GitHub artifacts if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} @@ -134,7 +134,7 @@ jobs: name: Build and deploy Desktop needs: validateActor if: ${{ fromJSON(needs.validateActor.outputs.IS_DEPLOYER) }} - runs-on: macos-13-large + runs-on: macos-14-large steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml index fe6ea5bfc016..10912aaeb436 100644 --- a/.github/workflows/testBuild.yml +++ b/.github/workflows/testBuild.yml @@ -228,7 +228,7 @@ jobs: if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} env: PULL_REQUEST_NUMBER: ${{ github.event.number || github.event.inputs.PULL_REQUEST_NUMBER }} - runs-on: macos-13-large + runs-on: macos-14-large steps: - name: Checkout uses: actions/checkout@v4 diff --git a/android/app/build.gradle b/android/app/build.gradle index 402cd5a61bd6..9e0fcd2e0bc2 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 1001048204 - versionName "1.4.82-4" + versionCode 1001048402 + versionName "1.4.84-2" // 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/android/build.gradle b/android/build.gradle index 52c998998ba0..9fc585ab9f05 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -29,7 +29,7 @@ buildscript { classpath("com.google.firebase:firebase-crashlytics-gradle:2.7.1") classpath("com.google.firebase:perf-plugin:1.4.1") // Fullstory integration - classpath ("com.fullstory:gradle-plugin-local:1.47.0") + classpath ("com.fullstory:gradle-plugin-local:1.49.0") // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/assets/animations/MagicCode.lottie b/assets/animations/MagicCode.lottie deleted file mode 100644 index ea94f1138f97..000000000000 Binary files a/assets/animations/MagicCode.lottie and /dev/null differ diff --git a/babel.config.js b/babel.config.js index 060bc0313950..8eb0d102db01 100644 --- a/babel.config.js +++ b/babel.config.js @@ -10,8 +10,13 @@ const defaultPlugins = [ '@babel/transform-runtime', '@babel/plugin-proposal-class-properties', - // This will serve to map the classes correctly in FullStory - '@fullstory/babel-plugin-annotate-react', + [ + '@fullstory/babel-plugin-annotate-react', + { + 'react-native-web': true, + native: true, + }, + ], // We use `transform-class-properties` for transforming ReactNative libraries and do not use it for our own // source code transformation as we do not use class property assignment. @@ -45,7 +50,6 @@ const metro = { '@fullstory/babel-plugin-annotate-react', { native: true, - setFSTagName: true, }, ], diff --git a/config/electronBuilder.config.js b/config/electronBuilder.config.js index e4ed685f65fe..5a995fb5de91 100644 --- a/config/electronBuilder.config.js +++ b/config/electronBuilder.config.js @@ -45,6 +45,12 @@ module.exports = { notarize: { teamId: '368M544MTT', }, + target: [ + { + target: 'dmg', + arch: ['universal'], + }, + ], }, dmg: { title: 'New Expensify', diff --git a/contributingGuides/STYLE.md b/contributingGuides/STYLE.md index f3e928da8cb0..6af3a82c2ff6 100644 --- a/contributingGuides/STYLE.md +++ b/contributingGuides/STYLE.md @@ -23,6 +23,7 @@ - [Type imports/exports](#type-importsexports) - [Refs](#refs) - [Other Expensify Resources on TypeScript](#other-expensify-resources-on-typescript) + - [Default value for inexistent IDs](#default-value-for-inexistent-IDs) - [Naming Conventions](#naming-conventions) - [Type names](#type-names) - [Prop callbacks](#prop-callbacks) @@ -47,7 +48,6 @@ - [Forwarding refs](#forwarding-refs) - [Hooks and HOCs](#hooks-and-hocs) - [Stateless components vs Pure Components vs Class based components vs Render Props](#stateless-components-vs-pure-components-vs-class-based-components-vs-render-props---when-to-use-what) - - [Composition](#composition) - [Use Refs Appropriately](#use-refs-appropriately) - [Are we allowed to use [insert brand new React feature]?](#are-we-allowed-to-use-insert-brand-new-react-feature-why-or-why-not) - [React Hooks: Frequently Asked Questions](#react-hooks-frequently-asked-questions) @@ -471,6 +471,24 @@ if (ref.current && 'getBoundingClientRect' in ref.current) { - [Expensify TypeScript React Native CheatSheet](./TS_CHEATSHEET.md) +### Default value for inexistent IDs + + Use `'-1'` or `-1` when there is a possibility that the ID property of an Onyx value could be `null` or `undefined`. + +``` ts +// BAD +const foo = report?.reportID ?? ''; +const bar = report?.reportID ?? '0'; + +report ? report.reportID : '0'; +report ? report.reportID : ''; + +// GOOD +const foo = report?.reportID ?? '-1'; + +report ? report.reportID : '-1'; +``` + ## Naming Conventions ### Type names @@ -1075,51 +1093,6 @@ Class components are DEPRECATED. Use function components and React hooks. [https://react.dev/reference/react/Component#migrating-a-component-with-lifecycle-methods-from-a-class-to-a-function](https://react.dev/reference/react/Component#migrating-a-component-with-lifecycle-methods-from-a-class-to-a-function) -### Composition - -Avoid the usage of `compose` function to compose HOCs in TypeScript files. Use nesting instead. - -> Why? `compose` function doesn't work well with TypeScript when dealing with several HOCs being used in a component, many times resulting in wrong types and errors. Instead, nesting can be used to allow a seamless use of multiple HOCs and result in a correct return type of the compoment. Also, you can use [hooks instead of HOCs](#hooks-instead-of-hocs) whenever possible to minimize or even remove the need of HOCs in the component. - -From React's documentation - ->Props and composition give you all the flexibility you need to customize a component’s look and behavior in an explicit and safe way. Remember that components may accept arbitrary props, including primitive values, React elements, or functions. ->If you want to reuse non-UI functionality between components, we suggest extracting it into a separate JavaScript module. The components may import it and use that function, object, or a class, without extending it. - - ```ts - // BAD - export default compose( - withCurrentUserPersonalDetails, - withReportOrNotFound(), - withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - }), - )(Component); - - // GOOD - export default withCurrentUserPersonalDetails( - withReportOrNotFound()( - withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - })(Component), - ), - ); - - // GOOD - alternative to HOC nesting - const ComponentWithOnyx = withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - })(Component); - const ComponentWithReportOrNotFound = withReportOrNotFound()(ComponentWithOnyx); - export default withCurrentUserPersonalDetails(ComponentWithReportOrNotFound); - ``` - -**Note:** If you find that none of these approaches work for you, please ask an Expensify engineer for guidance via Slack or GitHub. - ### Use Refs Appropriately React's documentation explains refs in [detail](https://reactjs.org/docs/refs-and-the-dom.html). It's important to understand when to use them and how to use them to avoid bugs and hard to maintain code. diff --git a/ios/.xcode.env b/ios/.xcode.env index 3d5782c71568..0ef3f59d606a 100644 --- a/ios/.xcode.env +++ b/ios/.xcode.env @@ -9,3 +9,8 @@ # For example, to use nvm with brew, add the following line # . "$(brew --prefix nvm)/nvm.sh" --no-use export NODE_BINARY=$(command -v node) + +# Provide a sourcemap path so that in release builds a source map file will be +# created at the specified location. +# (RN's default behaviour on iOS is to skip creating a sourcemap file): +export SOURCEMAP_FILE="$(pwd)/../main.jsbundle.map"; diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index dae86af11b18..69ef5f90dd5c 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.82 + 1.4.84 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.82.4 + 1.4.84.2 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 1d3f54796afd..534bd3625870 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.82 + 1.4.84 CFBundleSignature ???? CFBundleVersion - 1.4.82.4 + 1.4.84.2 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 064b395be9c7..5f083aea5ba8 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 1.4.82 + 1.4.84 CFBundleVersion - 1.4.82.4 + 1.4.84.2 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile b/ios/Podfile index d72086d4c07b..6330bb3d8d52 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -118,4 +118,4 @@ target 'NotificationServiceExtension' do pod 'AirshipServiceExtension' end -pod 'FullStory', :http => 'https://ios-releases.fullstory.com/fullstory-1.48.0-xcframework.tar.gz' \ No newline at end of file +pod 'FullStory', :http => 'https://ios-releases.fullstory.com/fullstory-1.49.0-xcframework.tar.gz' \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 3a6dd59c40ab..4857cab9bb24 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -138,7 +138,7 @@ PODS: - GoogleUtilities/Environment (~> 7.7) - "GoogleUtilities/NSData+zlib (~> 7.7)" - fmt (6.2.1) - - FullStory (1.48.0) + - FullStory (1.49.0) - fullstory_react-native (1.4.2): - FullStory (~> 1.14) - glog @@ -2098,7 +2098,7 @@ DEPENDENCIES: - ExpoImageManipulator (from `../node_modules/expo-image-manipulator/ios`) - ExpoModulesCore (from `../node_modules/expo-modules-core`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - - "FullStory (from `{:http=>\"https://ios-releases.fullstory.com/fullstory-1.48.0-xcframework.tar.gz\"}`)" + - "FullStory (from `{:http=>\"https://ios-releases.fullstory.com/fullstory-1.49.0-xcframework.tar.gz\"}`)" - "fullstory_react-native (from `../node_modules/@fullstory/react-native`)" - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) @@ -2263,7 +2263,7 @@ EXTERNAL SOURCES: FBLazyVector: :path: "../node_modules/react-native/Libraries/FBLazyVector" FullStory: - :http: https://ios-releases.fullstory.com/fullstory-1.48.0-xcframework.tar.gz + :http: https://ios-releases.fullstory.com/fullstory-1.49.0-xcframework.tar.gz fullstory_react-native: :path: "../node_modules/@fullstory/react-native" glog: @@ -2458,7 +2458,7 @@ EXTERNAL SOURCES: CHECKOUT OPTIONS: FullStory: - :http: https://ios-releases.fullstory.com/fullstory-1.48.0-xcframework.tar.gz + :http: https://ios-releases.fullstory.com/fullstory-1.49.0-xcframework.tar.gz SPEC CHECKSUMS: Airship: 5a6d3f8a982398940b0d48423bb9b8736717c123 @@ -2485,7 +2485,7 @@ SPEC CHECKSUMS: FirebasePerformance: 0c01a7a496657d7cea86d40c0b1725259d164c6c FirebaseRemoteConfig: 2d6e2cfdb49af79535c8af8a80a4a5009038ec2b fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - FullStory: 097347c823c21c655ca25fd8d5e6355a9326ec54 + FullStory: c95f74445f871bc344cdc4a4e4ece61b5554e55d fullstory_react-native: 6cba8a2c054374a24a44dc4310407d9435459cae glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 GoogleAppMeasurement: 5ba1164e3c844ba84272555e916d0a6d3d977e91 @@ -2606,8 +2606,8 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 1394a316c7add37e619c48d7aa40b38b954bf055 - Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 + Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 -PODFILE CHECKSUM: 66a5c97ae1059e4da1993a4ad95abe5d819f555b +PODFILE CHECKSUM: d5e281e5370cb0211a104efd90eb5fa7af936e14 COCOAPODS: 1.13.0 diff --git a/package-lock.json b/package-lock.json index 2732c681ed2c..e1c8cee95d34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.82-4", + "version": "1.4.84-2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.82-4", + "version": "1.4.84-2", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -20,6 +20,7 @@ "@formatjs/intl-locale": "^3.3.0", "@formatjs/intl-numberformat": "^8.5.0", "@formatjs/intl-pluralrules": "^5.2.2", + "@fullstory/babel-plugin-annotate-react": "github:fullstorydev/fullstory-babel-plugin-annotate-react#ryanwang/react-native-web-demo", "@fullstory/babel-plugin-react-native": "^1.2.1", "@fullstory/browser": "^2.0.3", "@fullstory/react-native": "^1.4.2", @@ -103,7 +104,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.48", + "react-native-onyx": "2.0.49", "react-native-pager-view": "6.2.3", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -5587,8 +5588,7 @@ }, "node_modules/@fullstory/babel-plugin-annotate-react": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@fullstory/babel-plugin-annotate-react/-/babel-plugin-annotate-react-2.3.0.tgz", - "integrity": "sha512-gYLUL6Tu0exbvTIhK9nSCaztmqBlQAm07Fvtl/nKTc+lxwFkcX9vR8RrdTbyjJZKbPaA5EMlExQ6GeLCXkfm5g==" + "resolved": "git+ssh://git@github.com/fullstorydev/fullstory-babel-plugin-annotate-react.git#25c26dadb644d5355e381a4ea4ca1cd05af4a8f6" }, "node_modules/@fullstory/babel-plugin-react-native": { "version": "1.2.1", @@ -31939,9 +31939,9 @@ } }, "node_modules/react-native-onyx": { - "version": "2.0.48", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.48.tgz", - "integrity": "sha512-qJQTWMzhLD7zy5/9vBZJSlb3//fYVx3obTdsw1tXZDVOZXUcBmd6evA2tzGe5KT8H2sIbvFR1UyvwE03oOqYYg==", + "version": "2.0.49", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.49.tgz", + "integrity": "sha512-cmFc7OZcVRuegb86c0tOCa8GGAXIraOfnLgtSxnNOA7DV/PMrbSetyFry2tzEDnGwORsgVWaunV78Jw1Em5rwA==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", diff --git a/package.json b/package.json index 32c42431d064..dd594ddbc8c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.82-4", + "version": "1.4.84-2", "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.", @@ -72,6 +72,7 @@ "@formatjs/intl-locale": "^3.3.0", "@formatjs/intl-numberformat": "^8.5.0", "@formatjs/intl-pluralrules": "^5.2.2", + "@fullstory/babel-plugin-annotate-react": "github:fullstorydev/fullstory-babel-plugin-annotate-react#ryanwang/react-native-web-demo", "@fullstory/babel-plugin-react-native": "^1.2.1", "@fullstory/browser": "^2.0.3", "@fullstory/react-native": "^1.4.2", @@ -155,7 +156,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.48", + "react-native-onyx": "2.0.49", "react-native-pager-view": "6.2.3", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", diff --git a/src/CONST.ts b/src/CONST.ts index 6a936bc97087..9311816c38a2 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -523,6 +523,16 @@ const CONST = { shortcutKey: 'Tab', modifiers: [], }, + DEBUG: { + descriptionKey: 'openDebug', + shortcutKey: 'D', + modifiers: ['CTRL'], + trigger: { + DEFAULT: {input: 'd', modifierFlags: keyModifierControl}, + [PLATFORM_OS_MACOS]: {input: 'd', modifierFlags: keyModifierCommand}, + [PLATFORM_IOS]: {input: 'd', modifierFlags: keyModifierCommand}, + }, + }, }, KEYBOARD_SHORTCUTS_TYPES: { NAVIGATION_SHORTCUT: KEYBOARD_SHORTCUT_NAVIGATION_TYPE, @@ -3309,6 +3319,11 @@ const CONST = { CONCIERGE_TRAVEL_URL: 'https://community.expensify.com/discussion/7066/introducing-concierge-travel', BOOK_TRAVEL_DEMO_URL: 'https://calendly.com/d/ck2z-xsh-q97/expensify-travel-demo-travel-page', + TRAVEL_DOT_URL: 'https://travel.expensify.com', + STAGING_TRAVEL_DOT_URL: 'https://staging.travel.expensify.com', + TRIP_ID_PATH: (tripID: string) => `trips/${tripID}`, + SPOTNANA_TMC_ID: '8e8e7258-1cf3-48c0-9cd1-fe78a6e31eed', + STAGING_SPOTNANA_TMC_ID: '7a290c6e-5328-4107-aff6-e48765845b81', SCREEN_READER_STATES: { ALL: 'all', ACTIVE: 'active', diff --git a/src/ROUTES.ts b/src/ROUTES.ts index eb3b439ea1ff..c1fdd68951fa 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -102,7 +102,10 @@ const ROUTES = { SETTINGS_PRONOUNS: 'settings/profile/pronouns', SETTINGS_PREFERENCES: 'settings/preferences', SETTINGS_SUBSCRIPTION: 'settings/subscription', - SETTINGS_SUBSCRIPTION_SIZE: 'settings/subscription/subscription-size', + SETTINGS_SUBSCRIPTION_SIZE: { + route: 'settings/subscription/subscription-size', + getRoute: (canChangeSize: 0 | 1) => `settings/subscription/subscription-size?canChangeSize=${canChangeSize}` as const, + }, SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD: 'settings/subscription/add-payment-card', SETTINGS_SUBSCRIPTION_DISABLE_AUTO_RENEW_SURVEY: 'settings/subscription/disable-auto-renew-survey', SETTINGS_PRIORITY_MODE: 'settings/preferences/priority-mode', diff --git a/src/components/AddPaymentMethodMenu.tsx b/src/components/AddPaymentMethodMenu.tsx index ac9657694500..734e8affa9ea 100644 --- a/src/components/AddPaymentMethodMenu.tsx +++ b/src/components/AddPaymentMethodMenu.tsx @@ -67,7 +67,7 @@ function AddPaymentMethodMenu({ // which then starts a bottom up flow and creates a Collect workspace where the payer is an admin and payee is an employee. const isIOUReport = ReportUtils.isIOUReport(iouReport ?? {}); const canUseBusinessBankAccount = - ReportUtils.isExpenseReport(iouReport ?? {}) || (isIOUReport && !ReportActionsUtils.hasRequestFromCurrentAccount(iouReport?.reportID ?? '', session?.accountID ?? 0)); + ReportUtils.isExpenseReport(iouReport ?? {}) || (isIOUReport && !ReportActionsUtils.hasRequestFromCurrentAccount(iouReport?.reportID ?? '-1', session?.accountID ?? -1)); const canUsePersonalBankAccount = shouldShowPersonalBankAccountOption || isIOUReport; diff --git a/src/components/AddPlaidBankAccount.tsx b/src/components/AddPlaidBankAccount.tsx index eaefa3c5581c..a1430615e37b 100644 --- a/src/components/AddPlaidBankAccount.tsx +++ b/src/components/AddPlaidBankAccount.tsx @@ -88,7 +88,7 @@ function AddPlaidBankAccount({ const styles = useThemeStyles(); const plaidBankAccounts = plaidData?.bankAccounts ?? []; const defaultSelectedPlaidAccount = plaidBankAccounts.find((account) => account.plaidAccountID === selectedPlaidAccountID); - const defaultSelectedPlaidAccountID = defaultSelectedPlaidAccount?.plaidAccountID ?? ''; + const defaultSelectedPlaidAccountID = defaultSelectedPlaidAccount?.plaidAccountID ?? '-1'; const defaultSelectedPlaidAccountMask = plaidBankAccounts.find((account) => account.plaidAccountID === selectedPlaidAccountID)?.mask ?? ''; const subscribedKeyboardShortcuts = useRef void>>([]); const previousNetworkState = useRef(); diff --git a/src/components/AmountPicker/index.tsx b/src/components/AmountPicker/index.tsx index 014932f7736b..b84ec19e2ffd 100644 --- a/src/components/AmountPicker/index.tsx +++ b/src/components/AmountPicker/index.tsx @@ -2,15 +2,12 @@ import React, {forwardRef, useState} from 'react'; import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; -import useStyleUtils from '@hooks/useStyleUtils'; -import variables from '@styles/variables'; import CONST from '@src/CONST'; import callOrReturn from '@src/types/utils/callOrReturn'; import AmountSelectorModal from './AmountSelectorModal'; import type {AmountPickerProps} from './types'; function AmountPicker({value, description, title, errorText = '', onInputChange, furtherDetails, rightLabel, ...rest}: AmountPickerProps, forwardedRef: ForwardedRef) { - const StyleUtils = useStyleUtils(); const [isPickerVisible, setIsPickerVisible] = useState(false); const showPickerModal = () => { @@ -29,15 +26,12 @@ function AmountPicker({value, description, title, errorText = '', onInputChange, hidePickerModal(); }; - const descStyle = !value || value.length === 0 ? StyleUtils.getFontSizeStyle(variables.fontSizeLabel) : null; - return ( showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} + onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} shouldUseHapticsOnLongPress accessibilityLabel={displayName} role={CONST.ROLE.BUTTON} diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx index 173f84d392e5..bcc5acf83653 100644 --- a/src/components/ArchivedReportFooter.tsx +++ b/src/components/ArchivedReportFooter.tsx @@ -32,15 +32,15 @@ function ArchivedReportFooter({report, reportClosedAction, personalDetails = {}} const originalMessage = reportClosedAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED ? reportClosedAction.originalMessage : null; const archiveReason = originalMessage?.reason ?? CONST.REPORT.ARCHIVE_REASON.DEFAULT; - const actorPersonalDetails = personalDetails?.[reportClosedAction?.actorAccountID ?? 0]; + const actorPersonalDetails = personalDetails?.[reportClosedAction?.actorAccountID ?? -1]; let displayName = PersonalDetailsUtils.getDisplayNameOrDefault(actorPersonalDetails); let oldDisplayName: string | undefined; if (archiveReason === CONST.REPORT.ARCHIVE_REASON.ACCOUNT_MERGED) { const newAccountID = originalMessage?.newAccountID; const oldAccountID = originalMessage?.oldAccountID; - displayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[newAccountID ?? 0]); - oldDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[oldAccountID ?? 0]); + displayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[newAccountID ?? -1]); + oldDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[oldAccountID ?? -1]); } const shouldRenderHTML = archiveReason !== CONST.REPORT.ARCHIVE_REASON.DEFAULT; diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index f09b7c217ac5..3db946ce387e 100644 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -282,9 +282,9 @@ function AttachmentModal({ * Detach the receipt and close the modal. */ const deleteAndCloseModal = useCallback(() => { - IOU.detachReceipt(transaction?.transactionID ?? ''); + IOU.detachReceipt(transaction?.transactionID ?? '-1'); setIsDeleteReceiptConfirmModalVisible(false); - Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report?.reportID ?? '')); + Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report?.reportID ?? '-1')); }, [transaction, report]); const isValidFile = useCallback((fileObject: FileObject) => { @@ -427,8 +427,8 @@ function AttachmentModal({ ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute( CONST.IOU.ACTION.EDIT, iouType, - transaction?.transactionID ?? '', - report?.reportID ?? '', + transaction?.transactionID ?? '-1', + report?.reportID ?? '-1', Navigation.getActiveRouteWithoutParams(), ), ); @@ -616,8 +616,8 @@ AttachmentModal.displayName = 'AttachmentModal'; export default withOnyx({ transaction: { key: ({report}) => { - const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '', report?.parentReportActionID ?? ''); - const transactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction?.originalMessage.IOUTransactionID ?? '0' : '0'; + const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '-1', report?.parentReportActionID ?? '-1'); + const transactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction?.originalMessage.IOUTransactionID ?? '-1' : '-1'; return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`; }, }, diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index c0d89f4acf1e..2fbe69a120a0 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -45,12 +45,12 @@ type AvatarProps = { /** Used to locate fallback icon in end-to-end tests. */ fallbackIconTestID?: string; - /** Denotes whether it is an avatar or a workspace avatar */ - type?: AvatarType; - /** Owner of the avatar. If user, displayName. If workspace, policy name */ name?: string; + /** Denotes whether it is an avatar or a workspace avatar */ + type: AvatarType; + /** Optional account id if it's user avatar or policy id if it's workspace avatar */ avatarID?: number | string; }; @@ -64,7 +64,7 @@ function Avatar({ fill, fallbackIcon = Expensicons.FallbackAvatar, fallbackIconTestID = '', - type = CONST.ICON_TYPE_AVATAR, + type, name = '', avatarID, }: AvatarProps) { @@ -80,9 +80,9 @@ function Avatar({ }, [originalSource]); const isWorkspace = type === CONST.ICON_TYPE_WORKSPACE; + const userAccountID = isWorkspace ? undefined : (avatarID as number); - // If it's user avatar then accountID will be a number - const source = isWorkspace ? originalSource : UserUtils.getAvatar(originalSource, avatarID as number); + const source = isWorkspace ? originalSource : UserUtils.getAvatar(originalSource, userAccountID); const useFallBackAvatar = imageError || !source || source === Expensicons.FallbackAvatar; const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(name) : fallbackIcon || Expensicons.FallbackAvatar; const fallbackAvatarTestID = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatarTestID(name) : fallbackIconTestID || 'SvgFallbackAvatar Icon'; diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index 61a07a50736b..38bf3912ae4b 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -71,7 +71,7 @@ function AvatarWithDisplayName({ const actorAccountID = useRef(null); useEffect(() => { - const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '']; + const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '-1']; actorAccountID.current = parentReportAction?.actorAccountID ?? -1; }, [parentReportActions, report]); @@ -182,7 +182,7 @@ AvatarWithDisplayName.displayName = 'AvatarWithDisplayName'; export default withOnyx({ parentReportActions: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`, + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '-1'}`, canEvict: false, }, personalDetails: { diff --git a/src/components/AvatarWithIndicator.tsx b/src/components/AvatarWithIndicator.tsx index f024a1239f4e..bf8fe93b8b21 100644 --- a/src/components/AvatarWithIndicator.tsx +++ b/src/components/AvatarWithIndicator.tsx @@ -41,6 +41,7 @@ function AvatarWithIndicator({source, accountID, tooltipText = '', fallbackIcon source={UserUtils.getSmallSizeAvatar(source, accountID)} fallbackIcon={fallbackIcon} avatarID={accountID} + type={CONST.ICON_TYPE_AVATAR} /> diff --git a/src/components/Banner.tsx b/src/components/Banner.tsx index 4e5c238fd692..404a92093119 100644 --- a/src/components/Banner.tsx +++ b/src/components/Banner.tsx @@ -93,17 +93,18 @@ function Banner({ )} {content && content} - {shouldRenderHTML && text ? ( - - ) : ( - - {text} - - )} + {text && + (shouldRenderHTML ? ( + + ) : ( + + {text} + + ))} {shouldShowCloseButton && !!onClose && ( diff --git a/src/components/ClientSideLoggingToolMenu/index.android.tsx b/src/components/ClientSideLoggingToolMenu/index.android.tsx index aa1bc215b719..298299e37fb9 100644 --- a/src/components/ClientSideLoggingToolMenu/index.android.tsx +++ b/src/components/ClientSideLoggingToolMenu/index.android.tsx @@ -1,47 +1,10 @@ -import React, {useState} from 'react'; -import RNFetchBlob from 'react-native-blob-util'; -import Share from 'react-native-share'; -import type {Log} from '@libs/Console'; -import localFileCreate from '@libs/localFileCreate'; -import CONST from '@src/CONST'; -import BaseClientSideLoggingToolMenu from './BaseClientSideLoggingToolMenu'; - +/** + * Since client-side logging is currently supported on web and desktop natively right now, + * this menu will be hidden in iOS and Android. + * See comment here: https://github.com/Expensify/App/issues/43256#issuecomment-2154610196 + */ function ClientSideLoggingToolMenu() { - const [file, setFile] = useState<{path: string; newFileName: string; size: number}>(); - - const createAndSaveFile = (logs: Log[]) => { - localFileCreate('logs', JSON.stringify(logs, null, 2)).then((localFile) => { - RNFetchBlob.MediaCollection.copyToMediaStore( - { - name: localFile.newFileName, - parentFolder: '', - mimeType: 'text/plain', - }, - 'Download', - localFile.path, - ); - setFile(localFile); - }); - }; - - const shareLogs = () => { - if (!file) { - return; - } - Share.open({ - url: `file://${file.path}`, - }); - }; - - return ( - setFile(undefined)} - onDisableLogging={createAndSaveFile} - onShareLogs={shareLogs} - displayPath={`${CONST.DOWNLOADS_PATH}/${file?.newFileName ?? ''}`} - /> - ); + return null; } ClientSideLoggingToolMenu.displayName = 'ClientSideLoggingToolMenu'; diff --git a/src/components/ClientSideLoggingToolMenu/index.ios.tsx b/src/components/ClientSideLoggingToolMenu/index.ios.tsx index 78ffccf612a2..298299e37fb9 100644 --- a/src/components/ClientSideLoggingToolMenu/index.ios.tsx +++ b/src/components/ClientSideLoggingToolMenu/index.ios.tsx @@ -1,40 +1,10 @@ -import React, {useState} from 'react'; -import Share from 'react-native-share'; -import useEnvironment from '@hooks/useEnvironment'; -import type {Log} from '@libs/Console'; -import getDownloadFolderPathSuffixForIOS from '@libs/getDownloadFolderPathSuffixForIOS'; -import localFileCreate from '@libs/localFileCreate'; -import CONST from '@src/CONST'; -import BaseClientSideLoggingToolMenu from './BaseClientSideLoggingToolMenu'; - +/** + * Since client-side logging is currently supported on web and desktop natively right now, + * this menu will be hidden in iOS and Android. + * See comment here: https://github.com/Expensify/App/issues/43256#issuecomment-2154610196 + */ function ClientSideLoggingToolMenu() { - const [file, setFile] = useState<{path: string; newFileName: string; size: number}>(); - const {environment} = useEnvironment(); - - const createFile = (logs: Log[]) => { - localFileCreate('logs', JSON.stringify(logs, null, 2)).then((localFile) => { - setFile(localFile); - }); - }; - - const shareLogs = () => { - if (!file) { - return; - } - Share.open({ - url: `file://${file.path}`, - }); - }; - - return ( - setFile(undefined)} - onDisableLogging={createFile} - onShareLogs={shareLogs} - displayPath={`${CONST.NEW_EXPENSIFY_PATH}${getDownloadFolderPathSuffixForIOS(environment)}/${file?.newFileName ?? ''}`} - /> - ); + return null; } ClientSideLoggingToolMenu.displayName = 'ClientSideLoggingToolMenu'; diff --git a/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx b/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx index 1776a0401403..fbca4fbb5dae 100644 --- a/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx +++ b/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx @@ -77,7 +77,7 @@ function ConnectToQuickbooksOnlineButton({ onClose={() => setWebViewOpen(false)} fullscreen isVisible - type={CONST.MODAL.MODAL_TYPE.CENTERED} + type={CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE} > setWebViewOpen(false)} fullscreen isVisible={isWebViewOpen} - type={CONST.MODAL.MODAL_TYPE.CENTERED} + type={CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE} > { - updateStatusBarAppearance({backgroundColor: theme.appBG}); - // eslint-disable-next-line react-hooks/exhaustive-deps -- we only want this to run on first render - }, []); - useEffect(() => { didForceUpdateStatusBarRef.current = false; }, [isRootStatusBarEnabled]); diff --git a/src/components/FocusTrap/FocusTrapForModal/index.web.tsx b/src/components/FocusTrap/FocusTrapForModal/index.web.tsx index 161e3f1b7f84..b06044404ee2 100644 --- a/src/components/FocusTrap/FocusTrapForModal/index.web.tsx +++ b/src/components/FocusTrap/FocusTrapForModal/index.web.tsx @@ -10,7 +10,7 @@ function FocusTrapForModal({children, active}: FocusTrapForModalProps) { focusTrapOptions={{ trapStack: sharedTrapStack, allowOutsideClick: true, - fallbackFocus: document.body, + initialFocus: false, }} > {children} diff --git a/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts b/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts index 2d0c51edbba9..5f10a7293457 100644 --- a/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts +++ b/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts @@ -13,6 +13,7 @@ const WIDE_LAYOUT_INACTIVE_SCREENS: string[] = [ SCREENS.SETTINGS.WALLET.ROOT, SCREENS.SETTINGS.ABOUT, SCREENS.SETTINGS.WORKSPACES, + SCREENS.SETTINGS.SUBSCRIPTION.ROOT, SCREENS.WORKSPACE.INITIAL, SCREENS.WORKSPACE.PROFILE, SCREENS.WORKSPACE.CARD, diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index aa68687379ac..67bbc7986bc7 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -93,7 +93,7 @@ function ImageRenderer({tnode}: ImageRendererProps) { Navigation.navigate(route); } }} - onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} + onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} shouldUseHapticsOnLongPress accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} accessibilityLabel={translate('accessibilityHints.viewAttachment')} diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx index 9c033674c399..e5481c5d9094 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx @@ -86,7 +86,7 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona {({anchor, report, action, checkIfContextMenuActive}) => ( showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} + onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} onPress={(event) => { event.preventDefault(); Navigation.navigate(navigationRoute); diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx index 39a1993c2334..4d1e58a42830 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx @@ -39,7 +39,7 @@ function PreRenderer({TDefaultRenderer, onPressIn, onPressOut, onLongPress, ...d onPress={onPressIn ?? (() => {})} onPressIn={onPressIn} onPressOut={onPressOut} - onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} + onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} shouldUseHapticsOnLongPress role={CONST.ROLE.PRESENTATION} accessibilityLabel={translate('accessibilityHints.prestyledText')} diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx index 52d14df46471..599455f6d7d9 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx @@ -29,13 +29,13 @@ function VideoRenderer({tnode, key}: VideoRendererProps) { { - const route = ROUTES.ATTACHMENTS.getRoute(report?.reportID ?? '', CONST.ATTACHMENT_TYPE.REPORT, sourceURL); + const route = ROUTES.ATTACHMENTS.getRoute(report?.reportID ?? '-1', CONST.ATTACHMENT_TYPE.REPORT, sourceURL); Navigation.navigate(route); }} /> diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 02f301f52845..60d5bf7034cc 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -10,7 +10,7 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import LottieAnimations from '@components/LottieAnimations'; import {ScrollOffsetContext} from '@components/ScrollOffsetContextProvider'; -import Text from '@components/Text'; +import TextBlock from '@components/TextBlock'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; import usePrevious from '@hooks/usePrevious'; @@ -61,36 +61,52 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio const emptyLHNSubtitle = useMemo( () => ( - - - {translate('common.emptyLHN.subtitleText1')} - - {translate('common.emptyLHN.subtitleText2')} - - {translate('common.emptyLHN.subtitleText3')} - + + + + + + ), - [theme, styles.alignItemsCenter, styles.textAlignCenter, translate], + [ + styles.alignItemsCenter, + styles.flexRow, + styles.justifyContentCenter, + styles.flexWrap, + styles.textAlignCenter, + styles.mh1, + theme.icon, + theme.textSupporting, + styles.textNormal, + translate, + ], ); /** @@ -101,9 +117,9 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio const itemFullReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]; const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`]; - const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? '']; + const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? '-1']; const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`]; - const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID ?? '' : ''; + const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID ?? '-1' : '-1'; const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; const hasDraftComment = DraftCommentUtils.isValidDraftComment(draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`]); const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(itemReportActions); @@ -113,7 +129,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio let lastReportActionTransactionID = ''; if (lastReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { - lastReportActionTransactionID = lastReportAction.originalMessage?.IOUTransactionID ?? ''; + lastReportActionTransactionID = lastReportAction.originalMessage?.IOUTransactionID ?? '-1'; } const lastReportActionTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${lastReportActionTransactionID}`] ?? {}; diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index de7ffabe035e..49c5ee0568db 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,5 +1,4 @@ import {useFocusEffect} from '@react-navigation/native'; -import {ExpensiMark} from 'expensify-common'; import React, {useCallback, useRef, useState} from 'react'; import type {GestureResponderEvent, ViewStyle} from 'react-native'; import {StyleSheet, View} from 'react-native'; @@ -20,6 +19,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; import DomUtils from '@libs/DomUtils'; +import {parseHtmlToText} from '@libs/OnyxAwareParser'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import Performance from '@libs/Performance'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; @@ -29,8 +29,6 @@ import CONST from '@src/CONST'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {OptionRowLHNProps} from './types'; -const parser = new ExpensiMark(); - function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style, onLayout = () => {}, hasDraftComment}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -105,7 +103,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti '', popoverAnchor.current, reportID, - '0', + '-1', reportID, undefined, () => {}, @@ -122,12 +120,12 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti const statusClearAfterDate = optionItem.status?.clearAfter ?? ''; const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate); const statusContent = formattedDate ? `${statusText ? `${statusText} ` : ''}(${formattedDate})` : statusText; - const report = ReportUtils.getReport(optionItem.reportID ?? ''); + const report = ReportUtils.getReport(optionItem.reportID ?? '-1'); const isStatusVisible = !!emojiCode && ReportUtils.isOneOnOneChat(!isEmptyObject(report) ? report : undefined); const isGroupChat = ReportUtils.isGroupChat(optionItem) || ReportUtils.isDeprecatedGroupDM(optionItem); - const fullTitle = isGroupChat ? ReportUtils.getGroupChatName(undefined, false, optionItem.reportID ?? '') : optionItem.text; + const fullTitle = isGroupChat ? ReportUtils.getGroupChatName(undefined, false, optionItem.reportID ?? '-1') : optionItem.text; const subscriptAvatarBorderColor = isFocused ? focusedBackgroundColor : theme.sidebar; return ( {}, opti > - {(optionItem.icons?.length ?? 0) > 0 && + {!!optionItem.icons?.length && (optionItem.shouldShowSubscript ? ( ) : ( {}, opti numberOfLines={1} accessibilityLabel={translate('accessibilityHints.lastChatMessagePreview')} > - {parser.htmlToText(optionItem.alternateText)} + {parseHtmlToText(optionItem.alternateText)} ) : null} diff --git a/src/components/LocaleContextProvider.tsx b/src/components/LocaleContextProvider.tsx index eb7d9324d2ab..e0e30d14d2a2 100644 --- a/src/components/LocaleContextProvider.tsx +++ b/src/components/LocaleContextProvider.tsx @@ -2,7 +2,6 @@ import React, {createContext, useMemo} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; -import compose from '@libs/compose'; import DateUtils from '@libs/DateUtils'; import * as LocaleDigitUtils from '@libs/LocaleDigitUtils'; import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; @@ -125,18 +124,17 @@ function LocaleContextProvider({preferredLocale, currentUserPersonalDetails = {} return {children}; } -const Provider = compose( +const Provider = withCurrentUserPersonalDetails( withOnyx({ preferredLocale: { key: ONYXKEYS.NVP_PREFERRED_LOCALE, selector: (preferredLocale) => preferredLocale, }, - }), - withCurrentUserPersonalDetails, -)(LocaleContextProvider); + })(LocaleContextProvider), +); Provider.displayName = 'withOnyx(LocaleContextProvider)'; -export {Provider as LocaleContextProvider, LocaleContext}; +export {LocaleContext, Provider as LocaleContextProvider}; -export type {LocaleContextProps, Locale}; +export type {Locale, LocaleContextProps}; diff --git a/src/components/LottieAnimations/index.tsx b/src/components/LottieAnimations/index.tsx index 00089f8094f1..657fe79b401f 100644 --- a/src/components/LottieAnimations/index.tsx +++ b/src/components/LottieAnimations/index.tsx @@ -51,11 +51,6 @@ const DotLottieAnimations = { h: 400, backgroundColor: colors.ice500, }, - MagicCode: { - file: require('@assets/animations/MagicCode.lottie'), - w: 200, - h: 164, - }, Magician: { file: require('@assets/animations/Magician.lottie'), w: 853, diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index 8168cef99cd3..06128c0d06b7 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -10,7 +10,6 @@ import {PressableWithoutFeedback} from '@components/Pressable'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as UserLocation from '@libs/actions/UserLocation'; -import compose from '@libs/compose'; import getCurrentPosition from '@libs/getCurrentPosition'; import type {GeolocationErrorCallback} from '@libs/getCurrentPosition/getCurrentPosition.types'; import {GeolocationErrorCode} from '@libs/getCurrentPosition/getCurrentPosition.types'; @@ -265,11 +264,8 @@ const MapView = forwardRef( }, ); -export default compose( - withOnyx({ - userLocation: { - key: ONYXKEYS.USER_LOCATION, - }, - }), - memo, -)(MapView); +export default withOnyx({ + userLocation: { + key: ONYXKEYS.USER_LOCATION, + }, +})(memo(MapView)); diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index 6a0ca0c9f5e3..0ad729e13662 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -565,6 +565,7 @@ function MenuItem( avatarID={avatarID} fallbackIcon={fallbackIcon} size={avatarSize} + type={CONST.ICON_TYPE_AVATAR} /> )} diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index f76fc94dbf89..2eb073bb39be 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -252,18 +252,16 @@ function BaseModal( avoidKeyboard={avoidKeyboard} customBackdrop={shouldUseCustomBackdrop ? : undefined} > - - - - - {children} - - - - + + + + {children} + + + diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index d3cf50827cec..90952157f179 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -3,6 +3,7 @@ import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; @@ -64,6 +65,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const theme = useTheme(); const [isDeleteRequestModalVisible, setIsDeleteRequestModalVisible] = useState(false); const {translate} = useLocalize(); + const {isOffline} = useNetwork(); const {windowWidth} = useWindowDimensions(); const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(moneyRequestReport); const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); @@ -149,9 +151,9 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const deleteTransaction = useCallback(() => { if (requestParentReportAction) { - const iouTransactionID = requestParentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? requestParentReportAction.originalMessage?.IOUTransactionID ?? '' : ''; + const iouTransactionID = requestParentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? requestParentReportAction.originalMessage?.IOUTransactionID ?? '-1' : '-1'; if (ReportActionsUtils.isTrackExpenseAction(requestParentReportAction)) { - navigateBackToAfterDelete.current = IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, true); + navigateBackToAfterDelete.current = IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '-1', iouTransactionID, requestParentReportAction, true); } else { navigateBackToAfterDelete.current = IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, true); } @@ -164,8 +166,8 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea if (!requestParentReportAction) { return; } - const iouTransactionID = requestParentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? requestParentReportAction.originalMessage?.IOUTransactionID ?? '' : ''; - const reportID = transactionThreadReport?.reportID ?? ''; + const iouTransactionID = requestParentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? requestParentReportAction.originalMessage?.IOUTransactionID ?? '-1' : '-1'; + const reportID = transactionThreadReport?.reportID ?? '-1'; TransactionActions.markAsCash(iouTransactionID, reportID); }, [requestParentReportAction, transactionThreadReport?.reportID]); @@ -234,7 +236,8 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea shouldDisableApproveButton={shouldDisableApproveButton} style={[styles.pv2]} formattedAmount={!ReportUtils.hasOnlyHeldExpenses(moneyRequestReport.reportID) ? displayedAmount : ''} - isDisabled={!canAllowSettlement} + isDisabled={isOffline && !canAllowSettlement} + isLoading={!isOffline && !canAllowSettlement} /> )} @@ -305,7 +308,8 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea shouldShowApproveButton={shouldShowApproveButton} formattedAmount={!ReportUtils.hasOnlyHeldExpenses(moneyRequestReport.reportID) ? displayedAmount : ''} shouldDisableApproveButton={shouldDisableApproveButton} - isDisabled={!canAllowSettlement} + isDisabled={isOffline && !canAllowSettlement} + isLoading={!isOffline && !canAllowSettlement} /> )} diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index cdb3aa8da1e1..3e7f8a65846f 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -240,8 +240,8 @@ function MoneyRequestConfirmationList({ const isTypeInvoice = iouType === CONST.IOU.TYPE.INVOICE; const isScanRequest = useMemo(() => TransactionUtils.isScanRequest(transaction), [transaction]); - const transactionID = transaction?.transactionID ?? ''; - const customUnitRateID = TransactionUtils.getRateID(transaction) ?? ''; + const transactionID = transaction?.transactionID ?? '-1'; + const customUnitRateID = TransactionUtils.getRateID(transaction) ?? '-1'; useEffect(() => { if (customUnitRateID || !canUseP2PDistanceRequests) { @@ -348,14 +348,14 @@ function MoneyRequestConfirmationList({ const isCategoryRequired = !!policy?.requiresCategory; useEffect(() => { - if (shouldDisplayFieldError && hasSmartScanFailed) { - setFormError('iou.receiptScanningFailed'); - return; - } if (shouldDisplayFieldError && didConfirmSplit) { setFormError('iou.error.genericSmartscanFailureMessage'); return; } + if (shouldDisplayFieldError && hasSmartScanFailed) { + setFormError('iou.receiptScanningFailed'); + return; + } // reset the form error whenever the screen gains or loses focus setFormError(''); @@ -510,7 +510,7 @@ function MoneyRequestConfirmationList({ rightElement: ( onSplitShareChange(participantOption.accountID ?? 0, Number(value))} + onAmountChange={(value: string) => onSplitShareChange(participantOption.accountID ?? -1, Number(value))} maxLength={formattedTotalAmount.length} /> ), @@ -718,20 +718,7 @@ function MoneyRequestConfirmationList({ return; } - if (formError) { - return; - } - - if (iouType === CONST.IOU.TYPE.PAY) { - if (!paymentMethod) { - return; - } - - setDidConfirm(true); - - Log.info(`[IOU] Sending money via: ${paymentMethod}`); - onSendMoney?.(paymentMethod); - } else { + if (iouType !== CONST.IOU.TYPE.PAY) { // validate the amount for distance expenses const decimals = CurrencyUtils.getCurrencyDecimals(iouCurrencyCode); if (isDistanceRequest && !isDistanceRequestWithPendingRoute && !MoneyRequestUtils.validateAmount(String(iouAmount), decimals)) { @@ -745,9 +732,25 @@ function MoneyRequestConfirmationList({ return; } + if (formError) { + return; + } + playSound(SOUNDS.DONE); setDidConfirm(true); onConfirm?.(selectedParticipants); + } else { + if (!paymentMethod) { + return; + } + if (formError) { + return; + } + + setDidConfirm(true); + + Log.info(`[IOU] Sending money via: ${paymentMethod}`); + onSendMoney?.(paymentMethod); } }, [ @@ -1186,7 +1189,9 @@ function MoneyRequestConfirmationList({ isLabelHoverable={false} interactive={!isReadOnly && canUpdateSenderWorkspace} onPress={() => { - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_SEND_FROM.getRoute(iouType, transaction?.transactionID ?? '', reportID, Navigation.getActiveRouteWithoutParams())); + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_SEND_FROM.getRoute(iouType, transaction?.transactionID ?? '-1', reportID, Navigation.getActiveRouteWithoutParams()), + ); }} style={styles.moneyRequestMenuItem} labelStyle={styles.mt2} diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 5d2395d4a8ec..a1f214ae847a 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -22,6 +22,7 @@ import type {Route} from '@src/ROUTES'; import type {Policy, Report, ReportAction} from '@src/types/onyx'; import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import type IconAsset from '@src/types/utils/IconAsset'; +import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import Button from './Button'; import ConfirmModal from './ConfirmModal'; import HeaderWithBackButton from './HeaderWithBackButton'; @@ -50,10 +51,11 @@ type MoneyRequestHeaderProps = { function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrowLayout = false, onBackButtonPress}: MoneyRequestHeaderProps) { const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`); - const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${(parentReportAction as ReportAction & OriginalMessageIOU)?.originalMessage?.IOUTransactionID ?? 0}`); + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${(parentReportAction as ReportAction & OriginalMessageIOU)?.originalMessage?.IOUTransactionID ?? -1}`); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const [session] = useOnyx(ONYXKEYS.SESSION); - const [shownHoldUseExplanation] = useOnyx(ONYXKEYS.NVP_HOLD_USE_EXPLAINED, {initWithStoredValues: false}); + const [holdUseExplained, holdUseExplainedResult] = useOnyx(ONYXKEYS.NVP_HOLD_USE_EXPLAINED); + const isLoadingHoldUseExplained = isLoadingOnyxValue(holdUseExplainedResult); const styles = useThemeStyles(); const theme = useTheme(); @@ -74,14 +76,14 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow 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) && moneyRequestReport?.managerID !== null && session?.accountID === moneyRequestReport?.managerID; - const hasAllPendingRTERViolations = TransactionUtils.allHavePendingRTERViolation([transaction?.transactionID ?? '']); + const hasAllPendingRTERViolations = TransactionUtils.allHavePendingRTERViolation([transaction?.transactionID ?? '-1']); const shouldShowMarkAsCashButton = isDraft && hasAllPendingRTERViolations; const deleteTransaction = useCallback(() => { if (parentReportAction) { - const iouTransactionID = parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage?.IOUTransactionID ?? '' : ''; + const iouTransactionID = parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage?.IOUTransactionID ?? '-1' : '-1'; if (ReportActionsUtils.isTrackExpenseAction(parentReportAction)) { - navigateBackToAfterDelete.current = IOU.deleteTrackExpense(parentReport?.reportID ?? '', iouTransactionID, parentReportAction, true); + navigateBackToAfterDelete.current = IOU.deleteTrackExpense(parentReport?.reportID ?? '-1', iouTransactionID, parentReportAction, true); } else { navigateBackToAfterDelete.current = IOU.deleteMoneyRequest(iouTransactionID, parentReportAction, true); } @@ -91,7 +93,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow }, [parentReport?.reportID, parentReportAction, setIsDeleteModalVisible]); const markAsCash = useCallback(() => { - TransactionActions.markAsCash(transaction?.transactionID ?? '', report.reportID); + TransactionActions.markAsCash(transaction?.transactionID ?? '-1', report.reportID); }, [report.reportID, transaction?.transactionID]); const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); @@ -103,7 +105,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow const canDeleteRequest = isActionOwner && (ReportUtils.canAddOrDeleteTransactions(moneyRequestReport) || ReportUtils.isTrackExpenseReport(report)) && !isDeletedParentAction; const changeMoneyRequestStatus = () => { - const iouTransactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage?.IOUTransactionID ?? '' : ''; + const iouTransactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage?.IOUTransactionID ?? '-1' : '-1'; if (isOnHold) { IOU.unholdRequest(iouTransactionID, report?.reportID); @@ -130,7 +132,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow if (TransactionUtils.isExpensifyCardTransaction(transaction) && TransactionUtils.isPending(transaction)) { return {title: getStatusIcon(Expensicons.CreditCardHourglass), description: translate('iou.transactionPendingDescription'), shouldShowBorderBottom: true}; } - if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) { + if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1', transactionViolations))) { return { title: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription'), @@ -175,8 +177,11 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow } useEffect(() => { - setShouldShowHoldMenu(isOnHold && !shownHoldUseExplanation); - }, [isOnHold, shownHoldUseExplanation]); + if (isLoadingHoldUseExplained) { + return; + } + setShouldShowHoldMenu(isOnHold && !holdUseExplained); + }, [isOnHold, holdUseExplained, isLoadingHoldUseExplained]); useEffect(() => { if (!shouldShowHoldMenu) { diff --git a/src/components/ParentNavigationSubtitle.tsx b/src/components/ParentNavigationSubtitle.tsx index b3b59fcb856a..3d4216704234 100644 --- a/src/components/ParentNavigationSubtitle.tsx +++ b/src/components/ParentNavigationSubtitle.tsx @@ -38,8 +38,8 @@ function ParentNavigationSubtitle({parentNavigationSubtitleData, parentReportAct return ( { - const parentAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? ''); - const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(parentAction, parentAction?.reportActionID ?? ''); + const parentAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? '-1'); + const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(parentAction, parentAction?.reportActionID ?? '-1'); // Pop the thread report screen before navigating to the chat report. Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(parentReportID)); if (isVisibleAction && !isOffline) { diff --git a/src/components/ProcessMoneyReportHoldMenu.tsx b/src/components/ProcessMoneyReportHoldMenu.tsx index 01896fb0a3cb..46ec51994d90 100644 --- a/src/components/ProcessMoneyReportHoldMenu.tsx +++ b/src/components/ProcessMoneyReportHoldMenu.tsx @@ -59,7 +59,7 @@ function ProcessMoneyReportHoldMenu({ const onSubmit = (full: boolean) => { if (isApprove) { IOU.approveMoneyRequest(moneyRequestReport, full); - if (!full && isLinkedTransactionHeld(Navigation.getTopmostReportActionId() ?? '', moneyRequestReport.reportID)) { + if (!full && isLinkedTransactionHeld(Navigation.getTopmostReportActionId() ?? '-1', moneyRequestReport.reportID)) { Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(moneyRequestReport.reportID)); } } else if (chatReport && paymentType) { diff --git a/src/components/ReceiptImage.tsx b/src/components/ReceiptImage.tsx index 946856ecec37..a520693cff57 100644 --- a/src/components/ReceiptImage.tsx +++ b/src/components/ReceiptImage.tsx @@ -110,7 +110,7 @@ function ReceiptImage({ return ( Navigation.navigate(ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute(report.reportID, report.policyID ?? '', reportField.fieldID))} + onPress={() => Navigation.navigate(ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute(report.reportID, report.policyID ?? '-1', reportField.fieldID))} shouldShowRightIcon disabled={isFieldDisabled} wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index fd82e723c6b9..b8f02f83d1cd 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -85,12 +85,12 @@ function MoneyRequestAction({ const onMoneyRequestPreviewPressed = () => { if (isSplitBillAction) { - const reportActionID = action.reportActionID ?? '0'; + const reportActionID = action.reportActionID ?? '-1'; Navigation.navigate(ROUTES.SPLIT_BILL_DETAILS.getRoute(chatReportID, reportActionID)); return; } - const childReportID = action?.childReportID ?? '0'; + const childReportID = action?.childReportID ?? '-1'; Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(childReportID)); }; diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index ccf1c61ac7b2..83fb8cd25292 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -28,6 +28,7 @@ import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import type {TransactionDetails} from '@libs/ReportUtils'; import StringUtils from '@libs/StringUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; @@ -83,7 +84,13 @@ function MoneyRequestPreviewContent({ // Pay button should only be visible to the manager of the report. const isCurrentUserManager = managerID === sessionAccountID; - const {amount: requestAmount, currency: requestCurrency, comment: requestComment, merchant} = ReportUtils.getTransactionDetails(transaction) ?? {}; + const { + amount: requestAmount, + currency: requestCurrency, + comment: requestComment, + merchant, + } = useMemo>(() => ReportUtils.getTransactionDetails(transaction) ?? {}, [transaction]); + const description = truncate(StringUtils.lineBreaksToSpaces(requestComment), {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); const hasReceipt = TransactionUtils.hasReceipt(transaction); @@ -91,9 +98,9 @@ function MoneyRequestPreviewContent({ const isOnHold = TransactionUtils.isOnHold(transaction); const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; const isPartialHold = isSettlementOrApprovalPartial && isOnHold; - const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '', transactionViolations); + const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '-1', transactionViolations); const hasNoticeTypeViolations = !!( - TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID ?? '', transactionViolations) && + TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID ?? '-1', transactionViolations) && ReportUtils.isPaidGroupPolicy(iouReport) && canUseViolations ); @@ -197,7 +204,7 @@ function MoneyRequestPreviewContent({ if (TransactionUtils.isPending(transaction)) { return {shouldShow: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPending')}; } - if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) { + if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1', transactionViolations))) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; } return {shouldShow: false}; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index cc99a3f6e108..58d56b005f0b 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -1,8 +1,9 @@ import React, {useCallback, useMemo} from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx, withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; +import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {useSession} from '@components/OnyxProvider'; @@ -28,11 +29,13 @@ import {isTaxTrackingEnabled} from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import type {TransactionDetails} from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import Navigation from '@navigation/Navigation'; import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground'; import * as IOU from '@userActions/IOU'; +import * as Link from '@userActions/Link'; import * as Transaction from '@userActions/Transaction'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; @@ -98,7 +101,9 @@ function MoneyRequestView({ const session = useSession(); const {isOffline} = useNetwork(); const {translate, toLocaleDigit} = useLocalize(); - const parentReportAction = parentReportActions?.[report.parentReportActionID ?? ''] ?? null; + const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); + + const parentReportAction = parentReportActions?.[report.parentReportActionID ?? '-1'] ?? null; const isTrackExpense = ReportUtils.isTrackExpenseReport(report); const {canUseViolations, canUseP2PDistanceRequests} = usePermissions(isTrackExpense ? CONST.IOU.TYPE.TRACK : undefined); const moneyRequestReport = parentReport; @@ -115,7 +120,7 @@ function MoneyRequestView({ originalAmount: transactionOriginalAmount, originalCurrency: transactionOriginalCurrency, cardID: transactionCardID, - } = ReportUtils.getTransactionDetails(transaction) ?? {}; + } = useMemo>(() => ReportUtils.getTransactionDetails(transaction) ?? {}, [transaction]); const isEmptyMerchant = transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); const formattedTransactionAmount = transactionAmount ? CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency) : ''; @@ -168,6 +173,8 @@ function MoneyRequestView({ const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true)); const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest); + const tripID = ReportUtils.getTripIDFromTransactionParentReport(parentReport); + const shouldShowViewTripDetails = TransactionUtils.hasReservationList(transaction) && !!tripID; const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback( @@ -179,7 +186,7 @@ function MoneyRequestView({ let amountDescription = `${translate('iou.amount')}`; const hasRoute = TransactionUtils.hasRoute(transaction, isDistanceRequest); - const rateID = transaction?.comment.customUnit?.customUnitRateID ?? '0'; + const rateID = transaction?.comment.customUnit?.customUnitRateID ?? '-1'; const currency = policy ? policy.outputCurrency : PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; @@ -203,7 +210,7 @@ function MoneyRequestView({ Navigation.dismissModal(); return; } - IOU.updateMoneyRequestBillable(transaction?.transactionID ?? '', report?.reportID, newBillable, policy, policyTagList, policyCategories); + IOU.updateMoneyRequestBillable(transaction?.transactionID ?? '-1', report?.reportID, newBillable, policy, policyTagList, policyCategories); Navigation.dismissModal(); }, [transaction, report, policy, policyTagList, policyCategories], @@ -284,7 +291,7 @@ function MoneyRequestView({ interactive={canEditDistance} shouldShowRightIcon={canEditDistance} titleStyle={styles.flex1} - onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '', report.reportID))} + onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report.reportID))} /> {/* TODO: correct the pending field action https://github.com/Expensify/App/issues/36987 */} @@ -309,7 +316,7 @@ function MoneyRequestView({ interactive={canEditDistance} shouldShowRightIcon={canEditDistance} titleStyle={styles.flex1} - onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '', report.reportID))} + onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report.reportID))} /> ); @@ -413,7 +420,7 @@ function MoneyRequestView({ ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute( CONST.IOU.ACTION.EDIT, iouType, - transaction?.transactionID ?? '', + transaction?.transactionID ?? '-1', report.reportID, Navigation.getActiveRouteWithoutParams(), ), @@ -432,7 +439,7 @@ function MoneyRequestView({ titleStyle={styles.textHeadlineH2} interactive={canEditAmount} shouldShowRightIcon={canEditAmount} - onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '', report.reportID))} + onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report.reportID))} brickRoadIndicator={getErrorForField('amount') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('amount')} /> @@ -445,7 +452,9 @@ function MoneyRequestView({ interactive={canEdit} shouldShowRightIcon={canEdit} titleStyle={styles.flex1} - onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '', report.reportID))} + onPress={() => + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report.reportID)) + } wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} brickRoadIndicator={getErrorForField('comment') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('comment')} @@ -463,7 +472,7 @@ function MoneyRequestView({ shouldShowRightIcon={canEditMerchant} titleStyle={styles.flex1} onPress={() => - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '', report.reportID)) + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report.reportID)) } wrapperStyle={[styles.taskDescriptionMenuItem]} brickRoadIndicator={getErrorForField('merchant') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} @@ -479,7 +488,7 @@ function MoneyRequestView({ interactive={canEditDate} shouldShowRightIcon={canEditDate} titleStyle={styles.flex1} - onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DATE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '', report.reportID))} + onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DATE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report.reportID))} brickRoadIndicator={getErrorForField('date') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('date')} /> @@ -493,7 +502,7 @@ function MoneyRequestView({ shouldShowRightIcon={canEdit} titleStyle={styles.flex1} onPress={() => - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '', report.reportID)) + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report.reportID)) } brickRoadIndicator={getErrorForField('category') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('category')} @@ -520,7 +529,7 @@ function MoneyRequestView({ shouldShowRightIcon={canEditTaxFields} titleStyle={styles.flex1} onPress={() => - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '', report.reportID)) + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report.reportID)) } brickRoadIndicator={getErrorForField('tax') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('tax')} @@ -537,11 +546,20 @@ function MoneyRequestView({ shouldShowRightIcon={canEditTaxFields} titleStyle={styles.flex1} onPress={() => - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '', report.reportID)) + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report.reportID)) } /> )} + {shouldShowViewTripDetails && ( + Link.openTravelDotLink(activePolicyID, CONST.TRIP_ID_PATH(tripID))} + /> + )} {shouldShowBillable && ( @@ -584,7 +602,7 @@ export default withOnyx `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, }, parentReportActions: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`, + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '-1'}`, canEvict: false, }, distanceRates: { @@ -595,17 +613,17 @@ export default withOnyx({ transaction: { key: ({report, parentReportActions}) => { - const parentReportAction = parentReportActions?.[report.parentReportActionID ?? '']; + const parentReportAction = parentReportActions?.[report.parentReportActionID ?? '-1']; const originalMessage = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage : undefined; - const transactionID = originalMessage?.IOUTransactionID ?? 0; + const transactionID = originalMessage?.IOUTransactionID ?? -1; return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`; }, }, transactionViolations: { key: ({report, parentReportActions}) => { - const parentReportAction = parentReportActions?.[report.parentReportActionID ?? '']; + const parentReportAction = parentReportActions?.[report.parentReportActionID ?? '-1']; const originalMessage = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage : undefined; - const transactionID = originalMessage?.IOUTransactionID ?? 0; + const transactionID = originalMessage?.IOUTransactionID ?? -1; return `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`; }, }, diff --git a/src/components/ReportActionItem/RenameAction.tsx b/src/components/ReportActionItem/RenameAction.tsx index b4b2553d652a..f2a3fead2336 100644 --- a/src/components/ReportActionItem/RenameAction.tsx +++ b/src/components/ReportActionItem/RenameAction.tsx @@ -16,9 +16,9 @@ function RenameAction({currentUserPersonalDetails, action}: RenameActionProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const currentUserAccountID = currentUserPersonalDetails.accountID ?? ''; + const currentUserAccountID = currentUserPersonalDetails.accountID ?? '-1'; const userDisplayName = action.person?.[0]?.text; - const actorAccountID = action.actorAccountID ?? ''; + const actorAccountID = action.actorAccountID ?? '-1'; const displayName = actorAccountID === currentUserAccountID ? `${translate('common.you')}` : `${userDisplayName}`; const originalMessage = action.actionName === CONST.REPORT.ACTIONS.TYPE.RENAMED ? action.originalMessage : null; const oldName = originalMessage?.oldName ?? ''; diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index 9bebef8f75ec..1251be83994b 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -128,7 +128,7 @@ function ReportActionItemImage({ - Navigation.navigate(ROUTES.TRANSACTION_RECEIPT.getRoute(transactionThreadReport?.reportID ?? report?.reportID ?? '', transaction?.transactionID ?? '')) + Navigation.navigate(ROUTES.TRANSACTION_RECEIPT.getRoute(transactionThreadReport?.reportID ?? report?.reportID ?? '-1', transaction?.transactionID ?? '-1')) } accessibilityLabel={translate('accessibilityHints.viewAttachment')} accessibilityRole={CONST.ROLE.BUTTON} diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 00fd3cc8e036..daa7e24709c2 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -15,6 +15,7 @@ import SettlementButton from '@components/SettlementButton'; import {showContextMenuForReport} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import usePermissions from '@hooks/usePermissions'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -109,6 +110,7 @@ function ReportPreview({ const styles = useThemeStyles(); const {translate} = useLocalize(); const {canUseViolations} = usePermissions(); + const {isOffline} = useNetwork(); const {hasMissingSmartscanFields, areAllRequestsBeingSmartScanned, hasOnlyTransactionsWithPendingRoutes, hasNonReimbursableTransactions} = useMemo( () => ({ @@ -155,7 +157,7 @@ function ReportPreview({ const lastThreeReceipts = lastThreeTransactionsWithReceipts.map((transaction) => ({...ReceiptUtils.getThumbnailAndImageURIs(transaction), transaction})); const showRTERViolationMessage = numberOfRequests === 1 && - TransactionUtils.hasPendingUI(allTransactions[0], TransactionUtils.getTransactionViolations(allTransactions[0]?.transactionID ?? '', transactionViolations)); + TransactionUtils.hasPendingUI(allTransactions[0], TransactionUtils.getTransactionViolations(allTransactions[0]?.transactionID ?? '-1', transactionViolations)); let formattedMerchant = numberOfRequests === 1 ? TransactionUtils.getMerchant(allTransactions[0]) : null; const formattedDescription = numberOfRequests === 1 ? TransactionUtils.getDescription(allTransactions[0]) : null; @@ -421,7 +423,8 @@ function ReportPreview({ horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, }} - isDisabled={!canAllowSettlement} + isDisabled={isOffline && !canAllowSettlement} + isLoading={!isOffline && !canAllowSettlement} /> )} {shouldShowSubmitButton && ( diff --git a/src/components/ReportActionItem/TaskPreview.tsx b/src/components/ReportActionItem/TaskPreview.tsx index 553051e07104..be64dfd60f59 100644 --- a/src/components/ReportActionItem/TaskPreview.tsx +++ b/src/components/ReportActionItem/TaskPreview.tsx @@ -72,7 +72,7 @@ function TaskPreview({taskReport, taskReportID, action, contextMenuAnchor, chatR ? taskReport?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && taskReport.statusNum === CONST.REPORT.STATUS_NUM.APPROVED : action?.childStateNum === CONST.REPORT.STATE_NUM.APPROVED && action?.childStatusNum === CONST.REPORT.STATUS_NUM.APPROVED; const taskTitle = Str.htmlEncode(TaskUtils.getTaskTitle(taskReportID, action?.childReportName ?? '')); - const taskAssigneeAccountID = Task.getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? ''; + const taskAssigneeAccountID = Task.getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? '-1'; const htmlForTaskPreview = taskAssigneeAccountID !== 0 ? ` ${taskTitle}` : `${taskTitle}`; const isDeletedParentAction = ReportUtils.isCanceledTaskReport(taskReport, action); diff --git a/src/components/ScreenWrapper.tsx b/src/components/ScreenWrapper.tsx index c597b8c741db..874e61a37713 100644 --- a/src/components/ScreenWrapper.tsx +++ b/src/components/ScreenWrapper.tsx @@ -139,7 +139,7 @@ function ScreenWrapper( const navigationFallback = useNavigation>(); const navigation = navigationProp ?? navigationFallback; const {windowHeight} = useWindowDimensions(shouldUseCachedViewportHeight); - const {isSmallScreenWidth} = useResponsiveLayout(); + const {isSmallScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); const {initialHeight} = useInitialDimensions(); const styles = useThemeStyles(); const keyboardState = useKeyboardState(); @@ -279,7 +279,7 @@ function ScreenWrapper( : children } {isSmallScreenWidth && shouldShowOfflineIndicator && } - {!isSmallScreenWidth && shouldShowOfflineIndicatorInWideScreen && ( + {!shouldUseNarrowLayout && shouldShowOfflineIndicatorInWideScreen && ( customListHeader={ @@ -131,6 +133,7 @@ function Search({query, policyIDs, sortBy, sortOrder}: SearchProps) { sortOrder={sortOrder} isSortingAllowed={isSortingAllowed} sortBy={sortBy} + shouldShowYear={shouldShowYear} /> } // To enhance the smoothness of scrolling and minimize the risk of encountering blank spaces during scrolling, diff --git a/src/components/SelectionList/BaseListItem.tsx b/src/components/SelectionList/BaseListItem.tsx index b1c689b55afa..c9dc773c8818 100644 --- a/src/components/SelectionList/BaseListItem.tsx +++ b/src/components/SelectionList/BaseListItem.tsx @@ -18,7 +18,6 @@ function BaseListItem({ wrapperStyle, containerStyle, isDisabled = false, - shouldPreventDefaultFocusOnSelectRow = false, shouldPreventEnterKeySubmit = false, canSelectMultiple = false, onSelectRow, @@ -88,7 +87,7 @@ function BaseListItem({ hoverDimmingValue={1} hoverStyle={[!item.isDisabled && styles.hoveredComponentBG, hoverStyle]} dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} - onMouseDown={shouldPreventDefaultFocusOnSelectRow ? (e) => e.preventDefault() : undefined} + onMouseDown={(e) => e.preventDefault()} id={keyForList ?? ''} style={pressableStyle} onFocus={onFocus} diff --git a/src/components/SelectionList/InviteMemberListItem.tsx b/src/components/SelectionList/InviteMemberListItem.tsx index 13b0014efb2d..2b3c01c04a69 100644 --- a/src/components/SelectionList/InviteMemberListItem.tsx +++ b/src/components/SelectionList/InviteMemberListItem.tsx @@ -24,7 +24,6 @@ function InviteMemberListItem({ onSelectRow, onCheckboxPress, onDismissError, - shouldPreventDefaultFocusOnSelectRow, rightHandSideComponent, onFocus, shouldSyncFocus, @@ -56,7 +55,6 @@ function InviteMemberListItem({ canSelectMultiple={canSelectMultiple} onSelectRow={onSelectRow} onDismissError={onDismissError} - shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} rightHandSideComponent={rightHandSideComponent} errors={item.errors} pendingAction={item.pendingAction} diff --git a/src/components/SelectionList/RadioListItem.tsx b/src/components/SelectionList/RadioListItem.tsx index c7884690c067..48ca474f6c60 100644 --- a/src/components/SelectionList/RadioListItem.tsx +++ b/src/components/SelectionList/RadioListItem.tsx @@ -13,7 +13,6 @@ function RadioListItem({ isDisabled, onSelectRow, onDismissError, - shouldPreventDefaultFocusOnSelectRow, shouldPreventEnterKeySubmit, rightHandSideComponent, isMultilineSupported = false, @@ -34,7 +33,6 @@ function RadioListItem({ showTooltip={showTooltip} onSelectRow={onSelectRow} onDismissError={onDismissError} - shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} shouldPreventEnterKeySubmit={shouldPreventEnterKeySubmit} rightHandSideComponent={rightHandSideComponent} keyForList={item.keyForList} diff --git a/src/components/SelectionList/Search/ReportListItem.tsx b/src/components/SelectionList/Search/ReportListItem.tsx index 9adff46395e6..2273b80e529d 100644 --- a/src/components/SelectionList/Search/ReportListItem.tsx +++ b/src/components/SelectionList/Search/ReportListItem.tsx @@ -68,7 +68,6 @@ function ReportListItem({ canSelectMultiple, onSelectRow, onDismissError, - shouldPreventDefaultFocusOnSelectRow, onFocus, shouldSyncFocus, }: ReportListItemProps) { @@ -119,7 +118,6 @@ function ReportListItem({ canSelectMultiple={canSelectMultiple} onSelectRow={() => openReportInRHP(transactionItem)} onDismissError={onDismissError} - shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} onFocus={onFocus} shouldSyncFocus={shouldSyncFocus} /> @@ -138,7 +136,6 @@ function ReportListItem({ canSelectMultiple={canSelectMultiple} onSelectRow={onSelectRow} onDismissError={onDismissError} - shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} errors={item.errors} pendingAction={item.pendingAction} keyForList={item.keyForList} diff --git a/src/components/SelectionList/Search/TransactionListItem.tsx b/src/components/SelectionList/Search/TransactionListItem.tsx index ecf9264301c2..23ab549dd495 100644 --- a/src/components/SelectionList/Search/TransactionListItem.tsx +++ b/src/components/SelectionList/Search/TransactionListItem.tsx @@ -13,7 +13,6 @@ function TransactionListItem({ canSelectMultiple, onSelectRow, onDismissError, - shouldPreventDefaultFocusOnSelectRow, onFocus, shouldSyncFocus, }: TransactionListItemProps) { @@ -42,7 +41,6 @@ function TransactionListItem({ canSelectMultiple={canSelectMultiple} onSelectRow={onSelectRow} onDismissError={onDismissError} - shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} errors={item.errors} pendingAction={item.pendingAction} keyForList={item.keyForList} diff --git a/src/components/SelectionList/Search/TransactionListItemRow.tsx b/src/components/SelectionList/Search/TransactionListItemRow.tsx index d17d923a54e1..c0fff452d1e5 100644 --- a/src/components/SelectionList/Search/TransactionListItemRow.tsx +++ b/src/components/SelectionList/Search/TransactionListItemRow.tsx @@ -13,6 +13,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CurrencyUtils from '@libs/CurrencyUtils'; +import DateUtils from '@libs/DateUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import variables from '@styles/variables'; @@ -69,7 +70,14 @@ function ReceiptCell({transactionItem}: TransactionCellProps) { const StyleUtils = useStyleUtils(); return ( - + - + void; + shouldShowYear: boolean; }; -function SearchTableHeader({data, sortBy, sortOrder, isSortingAllowed, onSortPress}: SearchTableHeaderProps) { +function SearchTableHeader({data, sortBy, sortOrder, isSortingAllowed, onSortPress, shouldShowYear}: SearchTableHeaderProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {isSmallScreenWidth, isMediumScreenWidth} = useWindowDimensions(); @@ -123,7 +124,7 @@ function SearchTableHeader({data, sortBy, sortOrder, isSortingAllowed, onSortPre textStyle={textStyle} sortOrder={sortOrder ?? CONST.SORT_ORDER.ASC} isActive={isActive} - containerStyle={[StyleUtils.getSearchTableColumnStyles(columnName)]} + containerStyle={[StyleUtils.getSearchTableColumnStyles(columnName, shouldShowYear)]} isSortable={isSortable} onPress={(order: SortOrder) => onSortPress(columnName, order)} /> diff --git a/src/components/SelectionList/TableListItem.tsx b/src/components/SelectionList/TableListItem.tsx index d07d658f6b12..9fc138254f8b 100644 --- a/src/components/SelectionList/TableListItem.tsx +++ b/src/components/SelectionList/TableListItem.tsx @@ -21,7 +21,6 @@ function TableListItem({ onSelectRow, onCheckboxPress, onDismissError, - shouldPreventDefaultFocusOnSelectRow, rightHandSideComponent, onFocus, shouldSyncFocus, @@ -53,7 +52,6 @@ function TableListItem({ canSelectMultiple={canSelectMultiple} onSelectRow={onSelectRow} onDismissError={onDismissError} - shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} rightHandSideComponent={rightHandSideComponent} errors={item.errors} pendingAction={item.pendingAction} diff --git a/src/components/SelectionList/UserListItem.tsx b/src/components/SelectionList/UserListItem.tsx index d07ac03c00f5..104990cf479c 100644 --- a/src/components/SelectionList/UserListItem.tsx +++ b/src/components/SelectionList/UserListItem.tsx @@ -25,7 +25,6 @@ function UserListItem({ onSelectRow, onCheckboxPress, onDismissError, - shouldPreventDefaultFocusOnSelectRow, shouldPreventEnterKeySubmit, rightHandSideComponent, onFocus, @@ -59,7 +58,6 @@ function UserListItem({ canSelectMultiple={canSelectMultiple} onSelectRow={onSelectRow} onDismissError={onDismissError} - shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} shouldPreventEnterKeySubmit={shouldPreventEnterKeySubmit} rightHandSideComponent={rightHandSideComponent} errors={item.errors} diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 5d5e7fc3891b..f70cac628498 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -168,6 +168,11 @@ type TransactionListItemType = ListItem & /** Whether we should show the tax column */ shouldShowTax: boolean; + + /** Whether we should show the transaction year. + * This is true if at least one transaction in the dataset was created in past years + */ + shouldShowYear: boolean; }; type ReportListItemType = ListItem & diff --git a/src/components/SettlementButton.tsx b/src/components/SettlementButton.tsx index 85954e68c5a9..e1d9ffaeeef8 100644 --- a/src/components/SettlementButton.tsx +++ b/src/components/SettlementButton.tsx @@ -185,7 +185,7 @@ function SettlementButton({ // To achieve the one tap pay experience we need to choose the correct payment type as default. // If the user has previously chosen a specific payment option or paid for some expense, // let's use the last payment method or use default. - const paymentMethod = nvpLastPaymentMethod?.[policyID] ?? ''; + const paymentMethod = nvpLastPaymentMethod?.[policyID] ?? '-1'; if (canUseWallet) { buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.EXPENSIFY]); } diff --git a/src/components/SubscriptAvatar.tsx b/src/components/SubscriptAvatar.tsx index 46a88c54219c..789b4b4957c3 100644 --- a/src/components/SubscriptAvatar.tsx +++ b/src/components/SubscriptAvatar.tsx @@ -27,8 +27,8 @@ type SubIcon = { }; type SubscriptAvatarProps = { - /** Avatar URL or icon */ - mainAvatar?: IconType; + /** Avatar icon */ + mainAvatar: IconType; /** Subscript avatar URL or icon */ secondaryAvatar?: IconType; diff --git a/src/components/TaskHeaderActionButton.tsx b/src/components/TaskHeaderActionButton.tsx index 788734242f7b..0c7e603a4aa2 100644 --- a/src/components/TaskHeaderActionButton.tsx +++ b/src/components/TaskHeaderActionButton.tsx @@ -33,7 +33,7 @@ function TaskHeaderActionButton({report, session}: TaskHeaderActionButtonProps)