diff --git a/.github/workflows/reassurePerformanceTests.yml b/.github/workflows/reassurePerformanceTests.yml
index 72774dc5ef07..a58745b742ad 100644
--- a/.github/workflows/reassurePerformanceTests.yml
+++ b/.github/workflows/reassurePerformanceTests.yml
@@ -22,13 +22,11 @@ jobs:
run: |
set -e
BASELINE_BRANCH=${BASELINE_BRANCH:="main"}
- CURRENT_BRANCH=$(git branch --show-current)
git fetch origin "$BASELINE_BRANCH" --no-tags --depth=1
git switch "$BASELINE_BRANCH"
npm install --force
npx reassure --baseline
- git switch "$CURRENT_BRANCH"
- git merge --no-commit --no-ff "$BASELINE_BRANCH"
+ git switch --force --detach -
npm install --force
npx reassure --branch
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 242584ef04ad..e11f938a9a8e 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -91,8 +91,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001040302
- versionName "1.4.3-2"
+ versionCode 1001040306
+ versionName "1.4.3-6"
}
flavorDimensions "default"
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 26e97aceb8aa..2d89178b271b 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.4.3.2
+ 1.4.3.6
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 854f911a582b..2f053575ff3b 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 1.4.3.2
+ 1.4.3.6
diff --git a/package-lock.json b/package-lock.json
index ac172bdef99b..9dd6974b12c5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.4.3-2",
+ "version": "1.4.3-6",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.4.3-2",
+ "version": "1.4.3-6",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 042ab7ca6e83..eb1b725eea10 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.4.3-2",
+ "version": "1.4.3-6",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js
index 4ab81ae462c9..d346f271b36d 100755
--- a/src/components/AttachmentModal.js
+++ b/src/components/AttachmentModal.js
@@ -128,6 +128,8 @@ function AttachmentModal(props) {
const [isDownloadButtonReadyToBeShown, setIsDownloadButtonReadyToBeShown] = React.useState(true);
const {windowWidth} = useWindowDimensions();
+ const isOverlayModalVisible = (isAttachmentReceipt && isDeleteReceiptConfirmModalVisible) || (!isAttachmentReceipt && isAttachmentInvalid);
+
const [file, setFile] = useState(
props.originalFileName
? {
@@ -406,7 +408,7 @@ function AttachmentModal(props) {
{
diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx
similarity index 77%
rename from src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js
rename to src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx
index ec53507d4d8e..3e5e7b4fdd9a 100644
--- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js
+++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx
@@ -1,5 +1,6 @@
import {FlashList} from '@shopify/flash-list';
-import React, {useCallback, useEffect, useRef} from 'react';
+import React, {ForwardedRef, forwardRef, ReactElement, useCallback, useEffect, useRef} from 'react';
+import {View} from 'react-native';
// We take ScrollView from this package to properly handle the scrolling of AutoCompleteSuggestions in chats since one scroll is nested inside another
import {ScrollView} from 'react-native-gesture-handler';
import Animated, {Easing, FadeOutDown, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
@@ -7,14 +8,10 @@ import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import * as StyleUtils from '@styles/StyleUtils';
import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
-import {propTypes} from './autoCompleteSuggestionsPropTypes';
+import viewForwardedRef from '@src/types/utils/viewForwardedRef';
+import type {AutoCompleteSuggestionsProps, RenderSuggestionMenuItemProps} from './types';
-/**
- * @param {Number} numRows
- * @param {Boolean} isSuggestionPickerLarge
- * @returns {Number}
- */
-const measureHeightOfSuggestionRows = (numRows, isSuggestionPickerLarge) => {
+const measureHeightOfSuggestionRows = (numRows: number, isSuggestionPickerLarge: boolean): number => {
if (isSuggestionPickerLarge) {
if (numRows > CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER) {
// On large screens, if there are more than 5 suggestions, we display a scrollable window with a height of 5 items, indicating that there are more items available
@@ -29,28 +26,26 @@ const measureHeightOfSuggestionRows = (numRows, isSuggestionPickerLarge) => {
return numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT;
};
-function BaseAutoCompleteSuggestions({
- highlightedSuggestionIndex,
- onSelect,
- renderSuggestionMenuItem,
- suggestions,
- accessibilityLabelExtractor,
- keyExtractor,
- isSuggestionPickerLarge,
- forwardedRef,
-}) {
+function BaseAutoCompleteSuggestions(
+ {
+ highlightedSuggestionIndex,
+ onSelect,
+ accessibilityLabelExtractor,
+ renderSuggestionMenuItem,
+ suggestions,
+ isSuggestionPickerLarge,
+ keyExtractor,
+ }: AutoCompleteSuggestionsProps,
+ ref: ForwardedRef,
+) {
const styles = useThemeStyles();
const rowHeight = useSharedValue(0);
- const scrollRef = useRef(null);
+ const scrollRef = useRef>(null);
/**
* Render a suggestion menu item component.
- * @param {Object} params
- * @param {Object} params.item
- * @param {Number} params.index
- * @returns {JSX.Element}
*/
const renderItem = useCallback(
- ({item, index}) => (
+ ({item, index}: RenderSuggestionMenuItemProps): ReactElement => (
StyleUtils.getAutoCompleteSuggestionItemStyle(highlightedSuggestionIndex, CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT, hovered, index)}
hoverDimmingValue={1}
@@ -84,7 +79,7 @@ function BaseAutoCompleteSuggestions({
return (
@@ -104,17 +99,6 @@ function BaseAutoCompleteSuggestions({
);
}
-BaseAutoCompleteSuggestions.propTypes = propTypes;
BaseAutoCompleteSuggestions.displayName = 'BaseAutoCompleteSuggestions';
-const BaseAutoCompleteSuggestionsWithRef = React.forwardRef((props, ref) => (
-
-));
-
-BaseAutoCompleteSuggestionsWithRef.displayName = 'BaseAutoCompleteSuggestionsWithRef';
-
-export default BaseAutoCompleteSuggestionsWithRef;
+export default forwardRef(BaseAutoCompleteSuggestions);
diff --git a/src/components/AutoCompleteSuggestions/autoCompleteSuggestionsPropTypes.js b/src/components/AutoCompleteSuggestions/autoCompleteSuggestionsPropTypes.js
deleted file mode 100644
index 8c6dca1902c5..000000000000
--- a/src/components/AutoCompleteSuggestions/autoCompleteSuggestionsPropTypes.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import PropTypes from 'prop-types';
-
-const propTypes = {
- /** Array of suggestions */
- // eslint-disable-next-line react/forbid-prop-types
- suggestions: PropTypes.arrayOf(PropTypes.object).isRequired,
-
- /** Function used to render each suggestion, returned JSX will be enclosed inside a Pressable component */
- renderSuggestionMenuItem: PropTypes.func.isRequired,
-
- /** Create unique keys for each suggestion item */
- keyExtractor: PropTypes.func.isRequired,
-
- /** The index of the highlighted suggestion */
- highlightedSuggestionIndex: PropTypes.number.isRequired,
-
- /** Fired when the user selects a suggestion */
- onSelect: PropTypes.func.isRequired,
-
- /** Show that we can use large auto-complete suggestion picker.
- * Depending on available space and whether the input is expanded, we can have a small or large mention suggester.
- * When this value is false, the suggester will have a height of 2.5 items. When this value is true, the height can be up to 5 items. */
- isSuggestionPickerLarge: PropTypes.bool.isRequired,
-
- /** create accessibility label for each item */
- accessibilityLabelExtractor: PropTypes.func.isRequired,
-
- /** Meaures the parent container's position and dimensions. */
- measureParentContainer: PropTypes.func,
-};
-
-const defaultProps = {
- measureParentContainer: () => {},
-};
-
-export {propTypes, defaultProps};
diff --git a/src/components/AutoCompleteSuggestions/index.native.js b/src/components/AutoCompleteSuggestions/index.native.tsx
similarity index 61%
rename from src/components/AutoCompleteSuggestions/index.native.js
rename to src/components/AutoCompleteSuggestions/index.native.tsx
index 439fa45eae78..fbfa7d953581 100644
--- a/src/components/AutoCompleteSuggestions/index.native.js
+++ b/src/components/AutoCompleteSuggestions/index.native.tsx
@@ -1,18 +1,17 @@
import {Portal} from '@gorhom/portal';
import React from 'react';
-import {propTypes} from './autoCompleteSuggestionsPropTypes';
import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions';
+import type {AutoCompleteSuggestionsProps} from './types';
-function AutoCompleteSuggestions({measureParentContainer, ...props}) {
+function AutoCompleteSuggestions({measureParentContainer, ...props}: AutoCompleteSuggestionsProps) {
return (
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
-
+ {...props} />
);
}
-AutoCompleteSuggestions.propTypes = propTypes;
AutoCompleteSuggestions.displayName = 'AutoCompleteSuggestions';
export default AutoCompleteSuggestions;
diff --git a/src/components/AutoCompleteSuggestions/index.js b/src/components/AutoCompleteSuggestions/index.tsx
similarity index 76%
rename from src/components/AutoCompleteSuggestions/index.js
rename to src/components/AutoCompleteSuggestions/index.tsx
index 30654caf5708..24b846c265a9 100644
--- a/src/components/AutoCompleteSuggestions/index.js
+++ b/src/components/AutoCompleteSuggestions/index.tsx
@@ -4,8 +4,8 @@ import {View} from 'react-native';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import * as StyleUtils from '@styles/StyleUtils';
-import {propTypes} from './autoCompleteSuggestionsPropTypes';
import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions';
+import type {AutoCompleteSuggestionsProps} from './types';
/**
* On the mobile-web platform, when long-pressing on auto-complete suggestions,
@@ -14,8 +14,8 @@ import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions';
* On the native platform, tapping on auto-complete suggestions will not blur the main input.
*/
-function AutoCompleteSuggestions({measureParentContainer, ...props}) {
- const containerRef = React.useRef(null);
+function AutoCompleteSuggestions({measureParentContainer = () => {}, ...props}: AutoCompleteSuggestionsProps) {
+ const containerRef = React.useRef(null);
const {windowHeight, windowWidth} = useWindowDimensions();
const [{width, left, bottom}, setContainerState] = React.useState({
width: 0,
@@ -25,7 +25,7 @@ function AutoCompleteSuggestions({measureParentContainer, ...props}) {
React.useEffect(() => {
const container = containerRef.current;
if (!container) {
- return;
+ return () => {};
}
container.onpointerdown = (e) => {
if (DeviceCapabilities.hasHoverSupport()) {
@@ -44,20 +44,20 @@ function AutoCompleteSuggestions({measureParentContainer, ...props}) {
}, [measureParentContainer, windowHeight, windowWidth]);
const componentToRender = (
-
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
ref={containerRef}
/>
);
+ const bodyElement = document.querySelector('body');
+
return (
- Boolean(width) &&
- ReactDOM.createPortal({componentToRender}, document.querySelector('body'))
+ !!width && bodyElement && ReactDOM.createPortal({componentToRender}, bodyElement)
);
}
-AutoCompleteSuggestions.propTypes = propTypes;
AutoCompleteSuggestions.displayName = 'AutoCompleteSuggestions';
export default AutoCompleteSuggestions;
diff --git a/src/components/AutoCompleteSuggestions/types.ts b/src/components/AutoCompleteSuggestions/types.ts
new file mode 100644
index 000000000000..9130f5139d71
--- /dev/null
+++ b/src/components/AutoCompleteSuggestions/types.ts
@@ -0,0 +1,38 @@
+import {ReactElement} from 'react';
+
+type MeasureParentContainerCallback = (x: number, y: number, width: number) => void;
+
+type RenderSuggestionMenuItemProps = {
+ item: TSuggestion;
+ index: number;
+};
+
+type AutoCompleteSuggestionsProps = {
+ /** Array of suggestions */
+ suggestions: TSuggestion[];
+
+ /** Function used to render each suggestion, returned JSX will be enclosed inside a Pressable component */
+ renderSuggestionMenuItem: (item: TSuggestion, index: number) => ReactElement;
+
+ /** Create unique keys for each suggestion item */
+ keyExtractor: (item: TSuggestion, index: number) => string;
+
+ /** The index of the highlighted suggestion */
+ highlightedSuggestionIndex: number;
+
+ /** Fired when the user selects a suggestion */
+ onSelect: (index: number) => void;
+
+ /** Show that we can use large auto-complete suggestion picker.
+ * Depending on available space and whether the input is expanded, we can have a small or large mention suggester.
+ * When this value is false, the suggester will have a height of 2.5 items. When this value is true, the height can be up to 5 items. */
+ isSuggestionPickerLarge: boolean;
+
+ /** create accessibility label for each item */
+ accessibilityLabelExtractor: (item: TSuggestion, index: number) => string;
+
+ /** Meaures the parent container's position and dimensions. */
+ measureParentContainer?: (callback: MeasureParentContainerCallback) => void;
+};
+
+export type {AutoCompleteSuggestionsProps, RenderSuggestionMenuItemProps};
diff --git a/src/components/Button/index.js b/src/components/Button/index.tsx
similarity index 63%
rename from src/components/Button/index.js
rename to src/components/Button/index.tsx
index b9aaf8868924..02f743b6a1b6 100644
--- a/src/components/Button/index.js
+++ b/src/components/Button/index.tsx
@@ -1,212 +1,167 @@
-import {useIsFocused} from '@react-navigation/native';
-import PropTypes from 'prop-types';
-import React, {useCallback} from 'react';
-import {ActivityIndicator, View} from 'react-native';
+import React, {ForwardedRef, useCallback} from 'react';
+import {ActivityIndicator, GestureResponderEvent, StyleProp, TextStyle, View, ViewStyle} from 'react-native';
+import {SvgProps} from 'react-native-svg';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
-import refPropTypes from '@components/refPropTypes';
import Text from '@components/Text';
import withNavigationFallback from '@components/withNavigationFallback';
import useKeyboardShortcut from '@hooks/useKeyboardShortcut';
import HapticFeedback from '@libs/HapticFeedback';
-import * as StyleUtils from '@styles/StyleUtils';
+import themeColors from '@styles/themes/default';
import useTheme from '@styles/themes/useTheme';
import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
+import ChildrenProps from '@src/types/utils/ChildrenProps';
import validateSubmitShortcut from './validateSubmitShortcut';
-const propTypes = {
- /** Should the press event bubble across multiple instances when Enter key triggers it. */
- allowBubble: PropTypes.bool,
-
+type ButtonWithText = {
/** The text for the button label */
- text: PropTypes.string,
+ text: string;
/** Boolean whether to display the right icon */
- shouldShowRightIcon: PropTypes.bool,
+ shouldShowRightIcon?: boolean;
/** The icon asset to display to the left of the text */
- icon: PropTypes.func,
+ icon?: React.FC | null;
+};
+
+type ButtonProps = (ButtonWithText | ChildrenProps) & {
+ /** Should the press event bubble across multiple instances when Enter key triggers it. */
+ allowBubble?: boolean;
/** The icon asset to display to the right of the text */
- iconRight: PropTypes.func,
+ iconRight?: React.FC;
/** The fill color to pass into the icon. */
- iconFill: PropTypes.string,
+ iconFill?: string;
/** Any additional styles to pass to the left icon container. */
- // eslint-disable-next-line react/forbid-prop-types
- iconStyles: PropTypes.arrayOf(PropTypes.object),
+ iconStyles?: StyleProp;
/** Any additional styles to pass to the right icon container. */
- // eslint-disable-next-line react/forbid-prop-types
- iconRightStyles: PropTypes.arrayOf(PropTypes.object),
+ iconRightStyles?: StyleProp;
/** Small sized button */
- small: PropTypes.bool,
+ small?: boolean;
/** Large sized button */
- large: PropTypes.bool,
+ large?: boolean;
- /** medium sized button */
- medium: PropTypes.bool,
+ /** Medium sized button */
+ medium?: boolean;
/** Indicates whether the button should be disabled and in the loading state */
- isLoading: PropTypes.bool,
+ isLoading?: boolean;
/** Indicates whether the button should be disabled */
- isDisabled: PropTypes.bool,
+ isDisabled?: boolean;
/** A function that is called when the button is clicked on */
- onPress: PropTypes.func,
+ onPress?: (event?: GestureResponderEvent | KeyboardEvent) => void;
/** A function that is called when the button is long pressed */
- onLongPress: PropTypes.func,
+ onLongPress?: (event?: GestureResponderEvent) => void;
/** A function that is called when the button is pressed */
- onPressIn: PropTypes.func,
+ onPressIn?: () => void;
/** A function that is called when the button is released */
- onPressOut: PropTypes.func,
+ onPressOut?: () => void;
/** Callback that is called when mousedown is triggered. */
- onMouseDown: PropTypes.func,
+ onMouseDown?: () => void;
/** Call the onPress function when Enter key is pressed */
- pressOnEnter: PropTypes.bool,
+ pressOnEnter?: boolean;
/** The priority to assign the enter key event listener. 0 is the highest priority. */
- enterKeyEventListenerPriority: PropTypes.number,
+ enterKeyEventListenerPriority?: number;
/** Additional styles to add after local styles. Applied to Pressable portion of button */
- style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]),
+ style?: StyleProp;
- /** Additional button styles. Specific to the OpacityView of button */
- // eslint-disable-next-line react/forbid-prop-types
- innerStyles: PropTypes.arrayOf(PropTypes.object),
+ /** Additional button styles. Specific to the OpacityView of the button */
+ innerStyles?: StyleProp;
/** Additional text styles */
- // eslint-disable-next-line react/forbid-prop-types
- textStyles: PropTypes.arrayOf(PropTypes.object),
+ textStyles?: StyleProp;
/** Whether we should use the default hover style */
- shouldUseDefaultHover: PropTypes.bool,
+ shouldUseDefaultHover?: boolean;
/** Whether we should use the success theme color */
- success: PropTypes.bool,
+ success?: boolean;
/** Whether we should use the danger theme color */
- danger: PropTypes.bool,
-
- /** Children to replace all inner contents of button */
- children: PropTypes.node,
+ danger?: boolean;
/** Should we remove the right border radius top + bottom? */
- shouldRemoveRightBorderRadius: PropTypes.bool,
+ shouldRemoveRightBorderRadius?: boolean;
/** Should we remove the left border radius top + bottom? */
- shouldRemoveLeftBorderRadius: PropTypes.bool,
+ shouldRemoveLeftBorderRadius?: boolean;
/** Should enable the haptic feedback? */
- shouldEnableHapticFeedback: PropTypes.bool,
+ shouldEnableHapticFeedback?: boolean;
/** Id to use for this button */
- id: PropTypes.string,
+ id?: string;
/** Accessibility label for the component */
- accessibilityLabel: PropTypes.string,
-
- /** A ref to forward the button */
- forwardedRef: refPropTypes,
-};
-
-const defaultProps = {
- allowBubble: false,
- text: '',
- shouldShowRightIcon: false,
- icon: null,
- iconRight: Expensicons.ArrowRight,
- iconFill: undefined,
- iconStyles: [],
- iconRightStyles: [],
- isLoading: false,
- isDisabled: false,
- small: false,
- large: false,
- medium: false,
- onPress: () => {},
- onLongPress: () => {},
- onPressIn: () => {},
- onPressOut: () => {},
- onMouseDown: undefined,
- pressOnEnter: false,
- enterKeyEventListenerPriority: 0,
- style: [],
- innerStyles: [],
- textStyles: [],
- shouldUseDefaultHover: true,
- success: false,
- danger: false,
- children: null,
- shouldRemoveRightBorderRadius: false,
- shouldRemoveLeftBorderRadius: false,
- shouldEnableHapticFeedback: false,
- id: '',
- accessibilityLabel: '',
- forwardedRef: undefined,
+ accessibilityLabel?: string;
+ isFocused: boolean;
};
-function Button({
- allowBubble,
- text,
- shouldShowRightIcon,
-
- icon,
- iconRight,
- iconFill,
- iconStyles,
- iconRightStyles,
-
- small,
- large,
- medium,
-
- isLoading,
- isDisabled,
-
- onPress,
- onLongPress,
- onPressIn,
- onPressOut,
- onMouseDown,
-
- pressOnEnter,
- enterKeyEventListenerPriority,
-
- style,
- innerStyles,
- textStyles,
-
- shouldUseDefaultHover,
- success,
- danger,
- children,
-
- shouldRemoveRightBorderRadius,
- shouldRemoveLeftBorderRadius,
- shouldEnableHapticFeedback,
-
- id,
- accessibilityLabel,
- forwardedRef,
-}) {
+function Button(
+ {
+ allowBubble = false,
+
+ iconRight = Expensicons.ArrowRight,
+ iconFill = themeColors.textLight,
+ iconStyles = [],
+ iconRightStyles = [],
+
+ small = false,
+ large = false,
+ medium = false,
+
+ isLoading = false,
+ isDisabled = false,
+
+ onPress = () => {},
+ onLongPress = () => {},
+ onPressIn = () => {},
+ onPressOut = () => {},
+ onMouseDown = undefined,
+
+ pressOnEnter = false,
+ enterKeyEventListenerPriority = 0,
+
+ style = [],
+ innerStyles = [],
+ textStyles = [],
+
+ shouldUseDefaultHover = true,
+ success = false,
+ danger = false,
+
+ shouldRemoveRightBorderRadius = false,
+ shouldRemoveLeftBorderRadius = false,
+ shouldEnableHapticFeedback = false,
+ isFocused,
+
+ id = '',
+ accessibilityLabel = '',
+ ...rest
+ }: ButtonProps,
+ ref: ForwardedRef,
+) {
const theme = useTheme();
const styles = useThemeStyles();
- const isFocused = useIsFocused();
const keyboardShortcutCallback = useCallback(
- (event) => {
+ (event?: GestureResponderEvent | KeyboardEvent) => {
if (!validateSubmitShortcut(isFocused, isDisabled, isLoading, event)) {
return;
}
@@ -223,10 +178,12 @@ function Button({
});
const renderContent = () => {
- if (children) {
- return children;
+ if ('children' in rest) {
+ return rest.children;
}
+ const {text = '', icon = null, shouldShowRightIcon = false} = rest;
+
const textComponent = (
@@ -248,12 +205,13 @@ function Button({
);
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
if (icon || shouldShowRightIcon) {
return (
{icon && (
-
+
{shouldShowRightIcon && (
-
+
{
- if (event && event.type === 'click') {
- event.currentTarget.blur();
+ if (event?.type === 'click') {
+ const currentTarget = event?.currentTarget as HTMLElement;
+ currentTarget?.blur();
}
if (shouldEnableHapticFeedback) {
@@ -307,7 +266,7 @@ function Button({
styles.buttonContainer,
shouldRemoveRightBorderRadius ? styles.noRightBorderRadius : undefined,
shouldRemoveLeftBorderRadius ? styles.noLeftBorderRadius : undefined,
- ...StyleUtils.parseStyleAsArray(style),
+ style,
]}
style={[
styles.button,
@@ -320,8 +279,9 @@ function Button({
isDisabled && !danger && !success ? styles.buttonDisabled : undefined,
shouldRemoveRightBorderRadius ? styles.noRightBorderRadius : undefined,
shouldRemoveLeftBorderRadius ? styles.noLeftBorderRadius : undefined,
- icon || shouldShowRightIcon ? styles.alignItemsStretch : undefined,
- ...innerStyles,
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ 'text' in rest && (rest?.icon || rest?.shouldShowRightIcon) ? styles.alignItemsStretch : undefined,
+ innerStyles,
]}
hoverStyle={[
shouldUseDefaultHover && !isDisabled ? styles.buttonDefaultHovered : undefined,
@@ -344,18 +304,6 @@ function Button({
);
}
-Button.propTypes = propTypes;
-Button.defaultProps = defaultProps;
Button.displayName = 'Button';
-const ButtonWithRef = React.forwardRef((props, ref) => (
-
-));
-
-ButtonWithRef.displayName = 'ButtonWithRef';
-
-export default withNavigationFallback(ButtonWithRef);
+export default withNavigationFallback(React.forwardRef(Button));
diff --git a/src/components/Button/validateSubmitShortcut/index.js b/src/components/Button/validateSubmitShortcut/index.js
deleted file mode 100644
index bfe5c79483fa..000000000000
--- a/src/components/Button/validateSubmitShortcut/index.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Validate if the submit shortcut should be triggered depending on the button state
- *
- * @param {boolean} isFocused Whether Button is on active screen
- * @param {boolean} isDisabled Indicates whether the button should be disabled
- * @param {boolean} isLoading Indicates whether the button should be disabled and in the loading state
- * @param {Object} event Focused input event
- * @returns {boolean} Returns `true` if the shortcut should be triggered
- */
-function validateSubmitShortcut(isFocused, isDisabled, isLoading, event) {
- if (!isFocused || isDisabled || isLoading || (event && event.target.nodeName === 'TEXTAREA')) {
- return false;
- }
-
- event.preventDefault();
- return true;
-}
-
-export default validateSubmitShortcut;
diff --git a/src/components/Button/validateSubmitShortcut/index.native.js b/src/components/Button/validateSubmitShortcut/index.native.js
deleted file mode 100644
index 2822fa56d590..000000000000
--- a/src/components/Button/validateSubmitShortcut/index.native.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * Validate if the submit shortcut should be triggered depending on the button state
- *
- * @param {boolean} isFocused Whether Button is on active screen
- * @param {boolean} isDisabled Indicates whether the button should be disabled
- * @param {boolean} isLoading Indicates whether the button should be disabled and in the loading state
- * @returns {boolean} Returns `true` if the shortcut should be triggered
- */
-function validateSubmitShortcut(isFocused, isDisabled, isLoading) {
- if (!isFocused || isDisabled || isLoading) {
- return false;
- }
-
- return true;
-}
-
-export default validateSubmitShortcut;
diff --git a/src/components/Button/validateSubmitShortcut/index.native.ts b/src/components/Button/validateSubmitShortcut/index.native.ts
new file mode 100644
index 000000000000..7687855f109b
--- /dev/null
+++ b/src/components/Button/validateSubmitShortcut/index.native.ts
@@ -0,0 +1,20 @@
+import ValidateSubmitShortcut from './types';
+
+/**
+ * Validate if the submit shortcut should be triggered depending on the button state
+ *
+ * @param isFocused Whether Button is on active screen
+ * @param isDisabled Indicates whether the button should be disabled
+ * @param isLoading Indicates whether the button should be disabled and in the loading state
+ * @return Returns `true` if the shortcut should be triggered
+ */
+
+const validateSubmitShortcut: ValidateSubmitShortcut = (isFocused, isDisabled, isLoading) => {
+ if (!isFocused || isDisabled || isLoading) {
+ return false;
+ }
+
+ return true;
+};
+
+export default validateSubmitShortcut;
diff --git a/src/components/Button/validateSubmitShortcut/index.ts b/src/components/Button/validateSubmitShortcut/index.ts
new file mode 100644
index 000000000000..55b3e44192e4
--- /dev/null
+++ b/src/components/Button/validateSubmitShortcut/index.ts
@@ -0,0 +1,23 @@
+import ValidateSubmitShortcut from './types';
+
+/**
+ * Validate if the submit shortcut should be triggered depending on the button state
+ *
+ * @param isFocused Whether Button is on active screen
+ * @param isDisabled Indicates whether the button should be disabled
+ * @param isLoading Indicates whether the button should be disabled and in the loading state
+ * @param event Focused input event
+ * @returns Returns `true` if the shortcut should be triggered
+ */
+
+const validateSubmitShortcut: ValidateSubmitShortcut = (isFocused, isDisabled, isLoading, event) => {
+ const eventTarget = event?.target as HTMLElement;
+ if (!isFocused || isDisabled || isLoading || eventTarget.nodeName === 'TEXTAREA') {
+ return false;
+ }
+
+ event?.preventDefault();
+ return true;
+};
+
+export default validateSubmitShortcut;
diff --git a/src/components/Button/validateSubmitShortcut/types.ts b/src/components/Button/validateSubmitShortcut/types.ts
new file mode 100644
index 000000000000..9970e1478a4c
--- /dev/null
+++ b/src/components/Button/validateSubmitShortcut/types.ts
@@ -0,0 +1,5 @@
+import {GestureResponderEvent} from 'react-native';
+
+type ValidateSubmitShortcut = (isFocused: boolean, isDisabled: boolean, isLoading: boolean, event?: GestureResponderEvent | KeyboardEvent) => boolean;
+
+export default ValidateSubmitShortcut;
diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx
index 54a178db1cdd..95a7f3adc279 100644
--- a/src/components/Modal/BaseModal.tsx
+++ b/src/components/Modal/BaseModal.tsx
@@ -56,6 +56,7 @@ function BaseModal(
*/
const hideModal = useCallback(
(callHideCallback = true) => {
+ Modal.willAlertModalBecomeVisible(false);
if (shouldSetModalVisibility) {
Modal.setModalVisibility(false);
}
@@ -77,8 +78,6 @@ function BaseModal(
Modal.willAlertModalBecomeVisible(true);
// To handle closing any modal already visible when this modal is mounted, i.e. PopoverReportActionContextMenu
removeOnCloseListener = Modal.setCloseModal(onClose);
- } else if (wasVisible && !isVisible) {
- Modal.willAlertModalBecomeVisible(false);
}
return () => {
@@ -96,7 +95,6 @@ function BaseModal(
return;
}
hideModal(true);
- Modal.willAlertModalBecomeVisible(false);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js
index 33ad99f32326..b10b8d87cabd 100644
--- a/src/components/ReportActionItem/MoneyRequestView.js
+++ b/src/components/ReportActionItem/MoneyRequestView.js
@@ -104,12 +104,13 @@ function MoneyRequestView({report, parentReport, policyCategories, shouldShowHor
formattedTransactionAmount = translate('common.tbd');
}
const formattedOriginalAmount = transactionOriginalAmount && transactionOriginalCurrency && CurrencyUtils.convertToDisplayString(transactionOriginalAmount, transactionOriginalCurrency);
- const isExpensifyCardTransaction = TransactionUtils.isExpensifyCardTransaction(transaction);
- const cardProgramName = isExpensifyCardTransaction ? CardUtils.getCardDescription(transactionCardID) : '';
+ const isCardTransaction = TransactionUtils.isCardTransaction(transaction);
+ const cardProgramName = isCardTransaction ? CardUtils.getCardDescription(transactionCardID) : '';
// Flags for allowing or disallowing editing a money request
const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID);
- const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction) && !isExpensifyCardTransaction;
+ const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction);
+ const canEditAmount = canEdit && !isSettled && !isCardTransaction;
// A flag for verifying that the current report is a sub-report of a workspace chat
const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]);
@@ -125,7 +126,7 @@ function MoneyRequestView({report, parentReport, policyCategories, shouldShowHor
let amountDescription = `${translate('iou.amount')}`;
- if (isExpensifyCardTransaction) {
+ if (isCardTransaction) {
if (formattedOriginalAmount) {
amountDescription += ` • ${translate('iou.original')} ${formattedOriginalAmount}`;
}
@@ -190,8 +191,8 @@ function MoneyRequestView({report, parentReport, policyCategories, shouldShowHor
titleIcon={Expensicons.Checkmark}
description={amountDescription}
titleStyle={styles.newKansasLarge}
- interactive={canEdit && !isSettled}
- shouldShowRightIcon={canEdit && !isSettled}
+ interactive={canEditAmount}
+ shouldShowRightIcon={canEditAmount}
onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.AMOUNT))}
brickRoadIndicator={hasErrors && transactionAmount === 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''}
error={hasErrors && transactionAmount === 0 ? translate('common.error.enterAmount') : ''}
@@ -271,13 +272,12 @@ function MoneyRequestView({report, parentReport, policyCategories, shouldShowHor
/>
)}
- {isExpensifyCardTransaction && (
+ {isCardTransaction && (
)}
diff --git a/src/components/SettlementButton.js b/src/components/SettlementButton.js
index 8cf9655d34dc..c7342b0d36ac 100644
--- a/src/components/SettlementButton.js
+++ b/src/components/SettlementButton.js
@@ -157,7 +157,9 @@ function SettlementButton({
if (canUseWallet) {
buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.EXPENSIFY]);
}
- buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.VBBA]);
+ if (isExpenseReport) {
+ buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.VBBA]);
+ }
buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.ELSEWHERE]);
// Put the preferred payment method to the front of the array so its shown as default
diff --git a/src/hooks/useKeyboardShortcut.ts b/src/hooks/useKeyboardShortcut.ts
index e4a7a16f4cfc..a5921ba37e4a 100644
--- a/src/hooks/useKeyboardShortcut.ts
+++ b/src/hooks/useKeyboardShortcut.ts
@@ -1,4 +1,5 @@
import {useEffect} from 'react';
+import {GestureResponderEvent} from 'react-native';
import {ValueOf} from 'type-fest';
import KeyboardShortcut from '@libs/KeyboardShortcut';
import CONST from '@src/CONST';
@@ -23,7 +24,7 @@ type KeyboardShortcutConfig = {
* Register a keyboard shortcut handler.
* Recommendation: To ensure stability, wrap the `callback` function with the useCallback hook before using it with this hook.
*/
-export default function useKeyboardShortcut(shortcut: Shortcut, callback: () => void, config: KeyboardShortcutConfig | Record = {}) {
+export default function useKeyboardShortcut(shortcut: Shortcut, callback: (e?: GestureResponderEvent | KeyboardEvent) => void, config: KeyboardShortcutConfig | Record = {}) {
const {
captureOnInputs = true,
shouldBubble = false,
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 68af6ec2341d..96e2e99824cd 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -561,19 +561,19 @@ export default {
splitAmount: ({amount}: SplitAmountParams) => `split ${amount}`,
didSplitAmount: ({formattedAmount, comment}: DidSplitAmountMessageParams) => `split ${formattedAmount}${comment ? ` for ${comment}` : ''}`,
amountEach: ({amount}: AmountEachParams) => `${amount} each`,
- payerOwesAmount: ({payer, amount}: PayerOwesAmountParams) => `${payer ? `${payer} ` : ''}owes ${amount}`,
+ payerOwesAmount: ({payer, amount}: PayerOwesAmountParams) => `${payer} owes ${amount}`,
payerOwes: ({payer}: PayerOwesParams) => `${payer} owes: `,
- payerPaidAmount: ({payer, amount}: PayerPaidAmountParams): string => `${payer ? `${payer} ` : ''}paid ${amount}`,
+ payerPaidAmount: ({payer, amount}: PayerPaidAmountParams): string => `${payer} paid ${amount}`,
payerPaid: ({payer}: PayerPaidParams) => `${payer} paid: `,
- payerSpentAmount: ({payer, amount}: PayerPaidAmountParams): string => `${payer ? `${payer} ` : ''}spent ${amount}`,
+ payerSpentAmount: ({payer, amount}: PayerPaidAmountParams): string => `${payer} spent ${amount}`,
payerSpent: ({payer}: PayerPaidParams) => `${payer} spent: `,
managerApproved: ({manager}: ManagerApprovedParams) => `${manager} approved:`,
payerSettled: ({amount}: PayerSettledParams) => `paid ${amount}`,
waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `started settling up, payment is held until ${submitterDisplayName} adds a bank account`,
settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) =>
`${submitterDisplayName} added a bank account. The ${amount} payment has been made.`,
- paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer ? `${payer} ` : ''}paid ${amount} elsewhere`,
- paidWithExpensifyWithAmount: ({payer, amount}: PaidWithExpensifyWithAmountParams) => `${payer ? `${payer} ` : ''}paid ${amount} using Expensify`,
+ paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer} paid ${amount} elsewhere`,
+ paidWithExpensifyWithAmount: ({payer, amount}: PaidWithExpensifyWithAmountParams) => `${payer} paid ${amount} using Expensify`,
noReimbursableExpenses: 'This report has an invalid amount',
pendingConversionMessage: "Total will update when you're back online",
changedTheRequest: 'changed the request',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index f298839b05b8..3f8f68977549 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -553,19 +553,19 @@ export default {
splitAmount: ({amount}: SplitAmountParams) => `dividir ${amount}`,
didSplitAmount: ({formattedAmount, comment}: DidSplitAmountMessageParams) => `dividió ${formattedAmount}${comment ? ` para ${comment}` : ''}`,
amountEach: ({amount}: AmountEachParams) => `${amount} cada uno`,
- payerOwesAmount: ({payer, amount}: PayerOwesAmountParams) => `${payer ? `${payer} ` : ''}debe ${amount}`,
+ payerOwesAmount: ({payer, amount}: PayerOwesAmountParams) => `${payer} debe ${amount}`,
payerOwes: ({payer}: PayerOwesParams) => `${payer} debe: `,
- payerPaidAmount: ({payer, amount}: PayerPaidAmountParams) => `${payer ? `${payer} ` : ''}pagó ${amount}`,
+ payerPaidAmount: ({payer, amount}: PayerPaidAmountParams) => `${payer} pagó ${amount}`,
payerPaid: ({payer}: PayerPaidParams) => `${payer} pagó: `,
- payerSpentAmount: ({payer, amount}: PayerPaidAmountParams): string => `${payer ? `${payer} ` : ''}gastó ${amount}`,
+ payerSpentAmount: ({payer, amount}: PayerPaidAmountParams): string => `${payer} gastó ${amount}`,
payerSpent: ({payer}: PayerPaidParams) => `${payer} gastó: `,
managerApproved: ({manager}: ManagerApprovedParams) => `${manager} aprobó:`,
payerSettled: ({amount}: PayerSettledParams) => `pagó ${amount}`,
waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `inicio el pago, pero no se procesará hasta que ${submitterDisplayName} añada una cuenta bancaria`,
settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) =>
`${submitterDisplayName} añadió una cuenta bancaria. El pago de ${amount} se ha realizado.`,
- paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer ? `${payer} ` : ''}pagó ${amount} de otra forma`,
- paidWithExpensifyWithAmount: ({payer, amount}: PaidWithExpensifyWithAmountParams) => `${payer ? `${payer} ` : ''}pagó ${amount} con Expensify`,
+ paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer} pagó ${amount} de otra forma`,
+ paidWithExpensifyWithAmount: ({payer, amount}: PaidWithExpensifyWithAmountParams) => `${payer} pagó ${amount} con Expensify`,
noReimbursableExpenses: 'El importe de este informe no es válido',
pendingConversionMessage: 'El total se actualizará cuando estés online',
changedTheRequest: 'cambió la solicitud',
diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js
index 14bee6e79776..b97ae6daed11 100644
--- a/src/libs/OptionsListUtils.js
+++ b/src/libs/OptionsListUtils.js
@@ -9,7 +9,6 @@ import _ from 'underscore';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import * as CollectionUtils from './CollectionUtils';
-import * as CurrencyUtils from './CurrencyUtils';
import * as ErrorUtils from './ErrorUtils';
import * as LocalePhoneNumber from './LocalePhoneNumber';
import * as Localize from './Localize';
@@ -373,40 +372,6 @@ function getAllReportErrors(report, reportActions) {
return allReportErrors;
}
-/**
- * Get the preview message to be displayed in the option list.
- *
- * @param {Object} report
- * @param {Object} reportAction
- * @param {Boolean} [isPreviewMessageForParentChatReport]
- * @returns {String}
- */
-function getReportPreviewMessageForOptionList(report, reportAction, isPreviewMessageForParentChatReport = false) {
- // For the request action preview we want to show the requestor instead of the user who owes the money
- if (!isPreviewMessageForParentChatReport && reportAction.originalMessage && reportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE) {
- const amount = Math.abs(reportAction.originalMessage.amount);
- const formattedAmount = CurrencyUtils.convertToDisplayString(amount, report.currency);
- const shouldShowActorName = currentUserAccountID !== reportAction.actorAccountID;
- const actorDisplayName = shouldShowActorName ? `${ReportUtils.getDisplayNameForParticipant(reportAction.actorAccountID, true)}: ` : '';
-
- return `${actorDisplayName}${Localize.translateLocal('iou.requestedAmount', {formattedAmount})}`;
- }
-
- const shouldShowWorkspaceName = ReportUtils.isExpenseReport(report) && isPreviewMessageForParentChatReport;
- const actorID = report.managerID || reportAction.actorAccountID;
- const actor = ReportUtils.getActorNameForPreviewMessage({
- report,
- shouldShowWorkspaceName,
- actorID,
- shouldUseShortForm: !isPreviewMessageForParentChatReport,
- });
- const shouldShowActorName = shouldShowWorkspaceName || isPreviewMessageForParentChatReport || currentUserAccountID !== actorID;
- const actorDisplayName = shouldShowActorName && actor ? `${actor}${isPreviewMessageForParentChatReport ? ' ' : ': '}` : '';
- const message = ReportUtils.getReportPreviewMessage(report, reportAction, true, isPreviewMessageForParentChatReport, true);
-
- return `${actorDisplayName}${message}`;
-}
-
/**
* Get the last message text from the report directly or from other sources for special cases.
* @param {Object} report
@@ -418,7 +383,7 @@ function getLastMessageTextForReport(report) {
const lastActionName = lodashGet(lastReportAction, 'actionName', '');
if (ReportActionUtils.isMoneyRequestAction(lastReportAction)) {
- const properSchemaForMoneyRequestMessage = getReportPreviewMessageForOptionList(report, lastReportAction, false);
+ const properSchemaForMoneyRequestMessage = ReportUtils.getReportPreviewMessage(report, lastReportAction, true);
lastMessageTextFromReport = ReportUtils.formatReportLastMessageText(properSchemaForMoneyRequestMessage);
} else if (ReportActionUtils.isReportPreviewAction(lastReportAction)) {
const iouReport = ReportUtils.getReport(ReportActionUtils.getIOUReportIDFromReportActionPreview(lastReportAction));
@@ -429,7 +394,7 @@ function getLastMessageTextForReport(report) {
reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE &&
ReportActionUtils.isMoneyRequestAction(reportAction),
);
- lastMessageTextFromReport = getReportPreviewMessageForOptionList(iouReport, lastIOUMoneyReport, ReportUtils.isChatReport(report));
+ lastMessageTextFromReport = ReportUtils.getReportPreviewMessage(iouReport, lastIOUMoneyReport, true, ReportUtils.isChatReport(report));
} else if (ReportActionUtils.isReimbursementQueuedAction(lastReportAction)) {
lastMessageTextFromReport = ReportUtils.getReimbursementQueuedActionMessage(lastReportAction, report);
} else if (ReportActionUtils.isDeletedParentAction(lastReportAction) && ReportUtils.isChatReport(report)) {
@@ -446,10 +411,7 @@ function getLastMessageTextForReport(report) {
) {
lastMessageTextFromReport = lodashGet(lastReportAction, 'message[0].text', '');
} else {
- const shouldShowLastActor =
- ReportUtils.isThread(report) && (ReportUtils.isExpenseReport(report) || ReportUtils.isIOUReport(report)) && currentUserAccountID !== report.lastActorAccountID;
- const lastActorDisplayName = shouldShowLastActor ? `${ReportUtils.getDisplayNameForParticipant(report.lastActorAccountID, true)}: ` : '';
- lastMessageTextFromReport = report ? `${lastActorDisplayName}${report.lastMessageText}` : '';
+ lastMessageTextFromReport = report ? report.lastMessageText || '' : '';
}
return lastMessageTextFromReport;
}
diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js
index 3deed6ec6a70..2e91a93af7e1 100644
--- a/src/libs/ReportUtils.js
+++ b/src/libs/ReportUtils.js
@@ -1831,39 +1831,17 @@ function getTransactionReportName(reportAction) {
});
}
-/**
- * Get actor name to display in the message preview
- *
- * @param {Object} report
- * @param {Number} actorID
- * @param {Boolean} [shouldShowWorkspaceName]
- * @param {Boolean} [shouldUseShortForm]
- * @param {Object|undefined} [policy]
- * @returns {String}
- */
-function getActorNameForPreviewMessage({report, actorID, shouldShowWorkspaceName = false, shouldUseShortForm = false, policy = undefined}) {
- return shouldShowWorkspaceName ? getPolicyName(report, false, policy) : getDisplayNameForParticipant(actorID, shouldUseShortForm);
-}
-
/**
* Get money request message for an IOU report
*
* @param {Object} report
* @param {Object} [reportAction={}] This can be either a report preview action or the IOU action
* @param {Boolean} [shouldConsiderReceiptBeingScanned=false]
- * @param {Boolean} [isPreviewMessageForParentChatReport]
- * @param {Boolean} [shouldHideParticipantName]
+ * @param {Boolean} isPreviewMessageForParentChatReport
* @param {Object} [policy]
* @returns {String}
*/
-function getReportPreviewMessage(
- report,
- reportAction = {},
- shouldConsiderReceiptBeingScanned = false,
- isPreviewMessageForParentChatReport = false,
- shouldHideParticipantName = false,
- policy = undefined,
-) {
+function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceiptBeingScanned = false, isPreviewMessageForParentChatReport = false, policy = undefined) {
const reportActionMessage = lodashGet(reportAction, 'message[0].html', '');
if (_.isEmpty(report) || !report.reportID) {
@@ -1887,14 +1865,7 @@ function getReportPreviewMessage(
}
const totalAmount = getMoneyRequestReimbursableTotal(report);
- const payerDisplayName = getActorNameForPreviewMessage({
- report,
- actorID: report.managerID,
- shouldUseShortForm: true,
- shouldShowWorkspaceName: isExpenseReport(report),
- policy,
- });
- const payerName = shouldHideParticipantName ? '' : payerDisplayName;
+ const payerName = isExpenseReport(report) ? getPolicyName(report, false, policy) : getDisplayNameForParticipant(report.managerID, true);
const formattedAmount = CurrencyUtils.convertToDisplayString(totalAmount, report.currency);
if (isReportApproved(report) && getPolicyType(report, allPolicies) === CONST.POLICY.TYPE.CORPORATE) {
@@ -1954,11 +1925,7 @@ function getProperSchemaForModifiedExpenseMessage(newValue, oldValue, valueName,
if (!newValue) {
return Localize.translateLocal('iou.removedTheRequest', {valueName: displayValueName, oldValueToDisplay});
}
- return Localize.translateLocal('iou.updatedTheRequest', {
- valueName: displayValueName,
- newValueToDisplay,
- oldValueToDisplay,
- });
+ return Localize.translateLocal('iou.updatedTheRequest', {valueName: displayValueName, newValueToDisplay, oldValueToDisplay});
}
/**
@@ -1973,10 +1940,7 @@ function getProperSchemaForModifiedExpenseMessage(newValue, oldValue, valueName,
function getProperSchemaForModifiedDistanceMessage(newDistance, oldDistance, newAmount, oldAmount) {
if (!oldDistance) {
- return Localize.translateLocal('iou.setTheDistance', {
- newDistanceToDisplay: newDistance,
- newAmountToDisplay: newAmount,
- });
+ return Localize.translateLocal('iou.setTheDistance', {newDistanceToDisplay: newDistance, newAmountToDisplay: newAmount});
}
return Localize.translateLocal('iou.updatedTheDistance', {
newDistanceToDisplay: newDistance,
@@ -3427,7 +3391,6 @@ function canAccessReport(report, policies, betas, allReportActions) {
return true;
}
-
/**
* Check if the report is the parent report of the currently viewed report or at least one child report has report action
* @param {Object} report
@@ -4568,7 +4531,6 @@ export {
getPersonalDetailsForAccountID,
getChannelLogMemberMessage,
getRoom,
- getActorNameForPreviewMessage,
shouldDisableWelcomeMessage,
canEditWriteCapability,
};
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index c3312ebebae9..763a0000ba35 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -406,8 +406,7 @@ function getOptionData(
}
: null;
}
- const lastActorDisplayName =
- hasMultipleParticipants && lastActorDetails?.accountID && Number(lastActorDetails.accountID) !== currentUserAccountID ? lastActorDetails.displayName?.split(' ')[0] : '';
+ const lastActorDisplayName = hasMultipleParticipants && lastActorDetails?.accountID && Number(lastActorDetails.accountID) !== currentUserAccountID ? lastActorDetails.displayName : '';
let lastMessageText = lastMessageTextFromReport;
const reportAction = lastReportActions?.[report.reportID];
diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts
index 00ce8c55dbd7..baf4ba6fb2f8 100644
--- a/src/libs/TransactionUtils.ts
+++ b/src/libs/TransactionUtils.ts
@@ -330,6 +330,13 @@ function isExpensifyCardTransaction(transaction: Transaction): boolean {
return isExpensifyCard(transaction.cardID);
}
+/**
+ * Determine whether a transaction is made with a card.
+ */
+function isCardTransaction(transaction: Transaction): boolean {
+ return (transaction?.cardID ?? 0) > 0;
+}
+
/**
* Check if the transaction status is set to Pending.
*/
@@ -475,6 +482,7 @@ export {
getValidWaypoints,
isDistanceRequest,
isExpensifyCardTransaction,
+ isCardTransaction,
isPending,
isPosted,
getWaypoints,
diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index aa75b88859f1..ebc1cdf9a2e1 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -1800,7 +1800,7 @@ function createWorkspaceFromIOUPayment(iouReport) {
value: {
[reportPreview.reportActionID]: {
...reportPreview,
- message: ReportUtils.getReportPreviewMessage(expenseReport, {}, false, false, false, newWorkspace),
+ message: ReportUtils.getReportPreviewMessage(expenseReport, {}, false, false, newWorkspace),
created: DateUtils.getDBTime(),
},
},
diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js
index aa5706e3ba88..4f35926c5957 100644
--- a/src/pages/home/report/ContextMenu/ContextMenuActions.js
+++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js
@@ -286,11 +286,11 @@ export default [
Clipboard.setString(logMessage);
} else if (content) {
const parser = new ExpensiMark();
- const markdown = parser.htmlToMarkdown(content);
if (!Clipboard.canSetHtml()) {
- Clipboard.setString(markdown);
+ Clipboard.setString(parser.htmlToMarkdown(content));
} else {
- Clipboard.setHtml(content, markdown);
+ const plainText = parser.htmlToText(content);
+ Clipboard.setHtml(content, plainText);
}
}
}
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js
index 6e69f77d0649..663db82a6067 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js
@@ -506,7 +506,7 @@ function ComposerWithSuggestions({
InputFocus.inputFocusChange(false);
return;
}
- focus();
+ focus(true);
}, [focus, prevIsFocused, editFocused, prevIsModalVisible, isFocused, modal.isVisible, isNextModalWillOpenRef]);
useEffect(() => {
// Scrolls the composer to the bottom and sets the selection to the end, so that longer drafts are easier to edit
diff --git a/src/pages/home/report/ReportAttachments.js b/src/pages/home/report/ReportAttachments.js
index 173a4b5637be..c580da7887a2 100644
--- a/src/pages/home/report/ReportAttachments.js
+++ b/src/pages/home/report/ReportAttachments.js
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, {useCallback} from 'react';
import _ from 'underscore';
import AttachmentModal from '@components/AttachmentModal';
+import ComposerFocusManager from '@libs/ComposerFocusManager';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
import ROUTES from '@src/ROUTES';
@@ -38,7 +39,11 @@ function ReportAttachments(props) {
defaultOpen
report={report}
source={source}
- onModalHide={() => Navigation.dismissModal()}
+ onModalHide={() => {
+ Navigation.dismissModal();
+ // This enables Composer refocus when the attachments modal is closed by the browser navigation
+ ComposerFocusManager.setReadyToFocus();
+ }}
onCarouselAttachmentChange={onCarouselAttachmentChange}
/>
);
diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx
index 8db093ec24d0..8e6a67ce5ee7 100644
--- a/src/pages/workspace/withPolicy.tsx
+++ b/src/pages/workspace/withPolicy.tsx
@@ -116,3 +116,4 @@ export default function (WrappedComponent:
}
export {policyPropTypes, policyDefaultProps};
+export type {WithPolicyOnyxProps, WithPolicyProps};
diff --git a/src/pages/workspace/withPolicyAndFullscreenLoading.js b/src/pages/workspace/withPolicyAndFullscreenLoading.js
deleted file mode 100644
index 2911eadccf5f..000000000000
--- a/src/pages/workspace/withPolicyAndFullscreenLoading.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import isEmpty from 'lodash/isEmpty';
-import omit from 'lodash/omit';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {withOnyx} from 'react-native-onyx';
-import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
-import compose from '@libs/compose';
-import getComponentDisplayName from '@libs/getComponentDisplayName';
-import ONYXKEYS from '@src/ONYXKEYS';
-import withPolicy, {policyDefaultProps, policyPropTypes} from './withPolicy';
-
-export default function (WrappedComponent) {
- const propTypes = {
- /** The HOC takes an optional ref as a prop and passes it as a ref to the wrapped component.
- * That way, if a ref is passed to a component wrapped in the HOC, the ref is a reference to the wrapped component, not the HOC. */
- forwardedRef: PropTypes.func,
-
- /** Indicated whether the report data is loading */
- isLoadingReportData: PropTypes.bool,
-
- ...policyPropTypes,
- };
-
- const defaultProps = {
- forwardedRef: () => {},
- isLoadingReportData: true,
- ...policyDefaultProps,
- };
-
- function WithPolicyAndFullscreenLoading(props) {
- if (props.isLoadingReportData && isEmpty(props.policy) && isEmpty(props.policyDraft)) {
- return ;
- }
-
- const rest = omit(props, ['forwardedRef']);
- return (
-
- );
- }
-
- WithPolicyAndFullscreenLoading.propTypes = propTypes;
- WithPolicyAndFullscreenLoading.defaultProps = defaultProps;
- WithPolicyAndFullscreenLoading.displayName = `WithPolicyAndFullscreenLoading(${getComponentDisplayName(WrappedComponent)})`;
-
- const WithPolicyAndFullscreenLoadingWithRef = React.forwardRef((props, ref) => (
-
- ));
-
- WithPolicyAndFullscreenLoadingWithRef.displayName = 'WithPolicyAndFullscreenLoadingWithRef';
-
- return compose(
- withPolicy,
- withOnyx({
- isLoadingReportData: {
- key: ONYXKEYS.IS_LOADING_REPORT_DATA,
- },
- }),
- )(WithPolicyAndFullscreenLoadingWithRef);
-}
diff --git a/src/pages/workspace/withPolicyAndFullscreenLoading.tsx b/src/pages/workspace/withPolicyAndFullscreenLoading.tsx
new file mode 100644
index 000000000000..3e8bd3bdddb4
--- /dev/null
+++ b/src/pages/workspace/withPolicyAndFullscreenLoading.tsx
@@ -0,0 +1,62 @@
+import isEmpty from 'lodash/isEmpty';
+import React, {ComponentType, ForwardedRef, forwardRef, RefAttributes} from 'react';
+import {OnyxEntry, withOnyx} from 'react-native-onyx';
+import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
+import compose from '@libs/compose';
+import ONYXKEYS from '@src/ONYXKEYS';
+import withPolicy, {policyDefaultProps, WithPolicyOnyxProps, WithPolicyProps} from './withPolicy';
+
+type WithPolicyAndFullscreenLoadingOnyxProps = {
+ /** Indicated whether the report data is loading */
+ isLoadingReportData: OnyxEntry;
+};
+
+type WithPolicyAndFullscreenLoadingProps = WithPolicyProps & WithPolicyAndFullscreenLoadingOnyxProps;
+
+type ComponentWithPolicyAndFullscreenLoading = ComponentType<
+ Omit, keyof WithPolicyAndFullscreenLoadingOnyxProps>, keyof WithPolicyOnyxProps>
+>;
+
+export default function withPolicyAndFullscreenLoading(
+ WrappedComponent: ComponentType>,
+): ComponentWithPolicyAndFullscreenLoading {
+ function WithPolicyAndFullscreenLoading(
+ {
+ isLoadingReportData = true,
+ policy = policyDefaultProps.policy,
+ policyDraft = policyDefaultProps.policyDraft,
+ policyMembers = policyDefaultProps.policyMembers,
+ policyMembersDraft = policyDefaultProps.policyMembersDraft,
+ ...rest
+ }: TProps,
+ ref: ForwardedRef,
+ ) {
+ if (isLoadingReportData && isEmpty(policy) && isEmpty(policyDraft)) {
+ return ;
+ }
+
+ return (
+
+ );
+ }
+
+ WithPolicyAndFullscreenLoading.displayName = `WithPolicyAndFullscreenLoading`;
+
+ return compose(
+ withOnyx, WithPolicyAndFullscreenLoadingOnyxProps>({
+ isLoadingReportData: {
+ key: ONYXKEYS.IS_LOADING_REPORT_DATA,
+ },
+ }),
+ withPolicy,
+ )(forwardRef(WithPolicyAndFullscreenLoading));
+}
diff --git a/src/types/utils/viewForwardedRef.ts b/src/types/utils/viewForwardedRef.ts
new file mode 100644
index 000000000000..3bc18495b153
--- /dev/null
+++ b/src/types/utils/viewForwardedRef.ts
@@ -0,0 +1,6 @@
+import {ForwardedRef} from 'react';
+import {View} from 'react-native';
+
+const viewForwardedRef = (ref: ForwardedRef) => ref as ForwardedRef;
+
+export default viewForwardedRef;
diff --git a/tests/e2e/compare/math.js b/tests/e2e/compare/math.js
index c3edc6ba0495..a7dbe1c95a86 100644
--- a/tests/e2e/compare/math.js
+++ b/tests/e2e/compare/math.js
@@ -31,57 +31,57 @@ const computeZ = (baselineMean, baselineStdev, currentMean, runs) => {
*/
const computeProbability = (z) => {
// p 0.005: two sided < 0.01
- if (z > 2.575_829) {
+ if (z > 2.575829) {
return 0;
}
// p 0.010
- if (z > 2.326_348) {
+ if (z > 2.326348) {
return 0.01;
}
// p 0.015
- if (z > 2.170_091) {
+ if (z > 2.170091) {
return 0.02;
}
// p 0.020
- if (z > 2.053_749) {
+ if (z > 2.053749) {
return 0.03;
}
// p 0.025: two sided < 0.05
- if (z > 1.959_964) {
+ if (z > 1.959964) {
return 0.04;
}
// p 0.030
- if (z > 1.880_793) {
+ if (z > 1.880793) {
return 0.05;
}
// p 0.035
- if (z > 1.811_91) {
+ if (z > 1.81191) {
return 0.06;
}
// p 0.040
- if (z > 1.750_686) {
+ if (z > 1.750686) {
return 0.07;
}
// p 0.045
- if (z > 1.695_397) {
+ if (z > 1.695397) {
return 0.08;
}
// p 0.050: two sided < 0.10
- if (z > 1.644_853) {
+ if (z > 1.644853) {
return 0.09;
}
// p 0.100: two sided < 0.20
- if (z > 1.281_551) {
+ if (z > 1.281551) {
return 0.1;
}