From 1c3c0351eeb13fbf113079a34746a04e8244ecab Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Sat, 9 Nov 2024 00:38:19 +0700 Subject: [PATCH 001/179] feature: Add loading indicator when ReconnectApp is running --- src/CONST.ts | 3 +++ src/pages/home/ReportScreen.tsx | 4 +++- .../sidebar/SidebarScreen/BaseSidebarScreen.tsx | 4 +++- src/styles/index.ts | 14 ++++++++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index e95e3d4a5603..07e5a5085302 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -315,6 +315,9 @@ const CONST = { ANIMATED_HIGHLIGHT_END_DURATION: 2000, ANIMATED_TRANSITION: 300, ANIMATED_TRANSITION_FROM_VALUE: 100, + ANIMATED_PROGRESS_BAR_DELAY: 300, + ANIMATED_PROGRESS_BAR_OPACITY_DURATION: 300, + ANIMATED_PROGRESS_BAR_DURATION: 750, ANIMATION_IN_TIMING: 100, ANIMATION_DIRECTION: { IN: 'in', diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 4c3ed5c705a5..a5c31563f8c3 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -10,6 +10,7 @@ import {useOnyx} from 'react-native-onyx'; import Banner from '@components/Banner'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import DragAndDropProvider from '@components/DragAndDrop/Provider'; +import LoadingBar from '@components/LoadingBar'; import MoneyReportHeader from '@components/MoneyReportHeader'; import MoneyRequestHeader from '@components/MoneyRequestHeader'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; @@ -132,7 +133,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro const finishedLoadingApp = wasLoadingApp && !isLoadingApp; const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(parentReportAction); const prevIsDeletedParentAction = usePrevious(isDeletedParentAction); - + const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA, {initialValue: true}); const isLoadingReportOnyx = isLoadingOnyxValue(reportResult); const permissions = useDeepCompareRef(reportOnyx?.permissions); @@ -756,6 +757,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro needsOffscreenAlphaCompositing > {headerView} + {shouldUseNarrowLayout && !!isLoadingReportData && } {!!report && ReportUtils.isTaskReport(report) && shouldUseNarrowLayout && ReportUtils.isOpenTaskReport(report, parentReportAction) && ( diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index e77f2000b85f..9b6b498ab078 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -1,6 +1,7 @@ import React, {useEffect} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; +import LoadingBar from '@components/LoadingBar'; import ScreenWrapper from '@components/ScreenWrapper'; import useActiveWorkspaceFromNavigationState from '@hooks/useActiveWorkspaceFromNavigationState'; import useLocalize from '@hooks/useLocalize'; @@ -30,7 +31,7 @@ function BaseSidebarScreen() { const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const [activeWorkspace] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID ?? -1}`); - + const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA, {initialValue: true}); useEffect(() => { Performance.markStart(CONST.TIMING.SIDEBAR_LOADED); Timing.start(CONST.TIMING.SIDEBAR_LOADED); @@ -62,6 +63,7 @@ function BaseSidebarScreen() { activeWorkspaceID={activeWorkspaceID} shouldDisplaySearch={shouldDisplaySearch} /> + width: variables.sideBarWidth - 19, }, + progressBarWrapper: { + height: 2, + width: '100%', + backgroundColor: theme.border, + borderRadius: 5, + overflow: 'hidden', + }, + + progressBar: { + height: '100%', + backgroundColor: theme.success, + width: '100%', + }, + accountSwitcherAnchorPosition: { top: 80, left: 12, From c8f048a174b6cfe847e1b8ec19609c529d15e0bf Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Sat, 9 Nov 2024 00:51:15 +0700 Subject: [PATCH 002/179] fix lint --- src/components/LoadingBar.tsx | 85 +++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/components/LoadingBar.tsx diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx new file mode 100644 index 000000000000..163ffe2aa66b --- /dev/null +++ b/src/components/LoadingBar.tsx @@ -0,0 +1,85 @@ +import React, {useEffect} from 'react'; +import Animated, {cancelAnimation, Easing, runOnJS, useAnimatedStyle, useSharedValue, withDelay, withRepeat, withSequence, withTiming} from 'react-native-reanimated'; +import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; + +type LoadingBarProps = { + // Whether or not to show the loading bar + shouldShow: boolean; +}; + +function LoadingBar({shouldShow}: LoadingBarProps) { + const left = useSharedValue(0); + const width = useSharedValue(0); + const opacity = useSharedValue(0); + const isVisible = useSharedValue(false); + const styles = useThemeStyles(); + + useEffect(() => { + if (shouldShow) { + // eslint-disable-next-line react-compiler/react-compiler + isVisible.value = true; + left.value = 0; + width.value = 0; + opacity.value = withTiming(1, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION}); + left.value = withDelay( + CONST.ANIMATED_PROGRESS_BAR_DELAY, + withRepeat( + withSequence( + withTiming(0, {duration: 0}), + withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + withTiming(100, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + ), + -1, + false, + ), + ); + + width.value = withDelay( + CONST.ANIMATED_PROGRESS_BAR_DELAY, + withRepeat( + withSequence( + withTiming(0, {duration: 0}), + withTiming(100, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + ), + -1, + false, + ), + ); + } else if (isVisible.value) { + opacity.value = withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION}, () => { + runOnJS(() => { + isVisible.value = false; + cancelAnimation(left); + cancelAnimation(width); + }); + }); + } + // we want to update only when shouldShow changes + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps + }, [shouldShow]); + + const animatedIndicatorStyle = useAnimatedStyle(() => { + return { + left: `${left.value}%`, + width: `${width.value}%`, + }; + }); + + const animatedContainerStyle = useAnimatedStyle(() => { + return { + opacity: opacity.value, + }; + }); + + return ( + + {isVisible.value ? : null} + + ); +} + +LoadingBar.displayName = 'ProgressBar'; + +export default LoadingBar; From a3dcad33f2cc76729d6733c6d93d8ee9701c6941 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 12 Nov 2024 16:29:48 +0700 Subject: [PATCH 003/179] remove isVisible shared value --- src/components/LoadingBar.tsx | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index 163ffe2aa66b..6956887227bb 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -1,5 +1,5 @@ import React, {useEffect} from 'react'; -import Animated, {cancelAnimation, Easing, runOnJS, useAnimatedStyle, useSharedValue, withDelay, withRepeat, withSequence, withTiming} from 'react-native-reanimated'; +import Animated, {cancelAnimation, Easing, useAnimatedStyle, useSharedValue, withDelay, withRepeat, withSequence, withTiming} from 'react-native-reanimated'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; @@ -12,16 +12,15 @@ function LoadingBar({shouldShow}: LoadingBarProps) { const left = useSharedValue(0); const width = useSharedValue(0); const opacity = useSharedValue(0); - const isVisible = useSharedValue(false); const styles = useThemeStyles(); useEffect(() => { if (shouldShow) { // eslint-disable-next-line react-compiler/react-compiler - isVisible.value = true; left.value = 0; width.value = 0; opacity.value = withTiming(1, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION}); + left.value = withDelay( CONST.ANIMATED_PROGRESS_BAR_DELAY, withRepeat( @@ -47,35 +46,28 @@ function LoadingBar({shouldShow}: LoadingBarProps) { false, ), ); - } else if (isVisible.value) { + } else { opacity.value = withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION}, () => { - runOnJS(() => { - isVisible.value = false; - cancelAnimation(left); - cancelAnimation(width); - }); + cancelAnimation(left); + cancelAnimation(width); }); } // we want to update only when shouldShow changes // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [shouldShow]); - const animatedIndicatorStyle = useAnimatedStyle(() => { - return { - left: `${left.value}%`, - width: `${width.value}%`, - }; - }); + const animatedIndicatorStyle = useAnimatedStyle(() => ({ + left: `${left.value}%`, + width: `${width.value}%`, + })); - const animatedContainerStyle = useAnimatedStyle(() => { - return { - opacity: opacity.value, - }; - }); + const animatedContainerStyle = useAnimatedStyle(() => ({ + opacity: opacity.value, + })); return ( - {isVisible.value ? : null} + {opacity.value > 0 ? : null} ); } From 8dd727afa25e4521b0e5a4156cddb500192914b4 Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:58:28 +1300 Subject: [PATCH 004/179] Fix selection list scrolling after closing context menu --- .../SelectionList/BaseSelectionListItemRenderer.tsx | 13 ++++++++++++- .../SelectionList/Search/SearchQueryListItem.tsx | 3 ++- src/components/SelectionList/types.ts | 3 ++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionListItemRenderer.tsx b/src/components/SelectionList/BaseSelectionListItemRenderer.tsx index b08d2ae2cfbc..5a35ddfbb892 100644 --- a/src/components/SelectionList/BaseSelectionListItemRenderer.tsx +++ b/src/components/SelectionList/BaseSelectionListItemRenderer.tsx @@ -1,6 +1,8 @@ import React from 'react'; +import type {TargetedEvent} from 'react-native'; import type useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import type useSingleExecution from '@hooks/useSingleExecution'; +import * as Browser from '@libs/Browser'; import * as SearchUIUtils from '@libs/SearchUIUtils'; import type {BaseListItemProps, BaseSelectionListProps, ListItem} from './types'; @@ -13,6 +15,11 @@ type BaseSelectionListItemRendererProps = Omit['singleExecution']; }; +// `sourceCapabilities` property exists in Chrome. +type ExtendedTargetedEvent = TargetedEvent & { + sourceCapabilities?: unknown; +}; + function BaseSelectionListItemRenderer({ ListItem, item, @@ -72,11 +79,15 @@ function BaseSelectionListItemRenderer({ isMultilineSupported={isMultilineSupported} isAlternateTextMultilineSupported={isAlternateTextMultilineSupported} alternateTextNumberOfLines={alternateTextNumberOfLines} - onFocus={() => { + onFocus={(event) => { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if (shouldIgnoreFocus || isDisabled) { return; } + // Prevent unexpected scrolling on mobile Chrome after the context menu closes by ignoring programmatic focus not triggered by direct user interaction. + if (Browser.isMobileChrome() && event?.nativeEvent && !(event.nativeEvent as ExtendedTargetedEvent).sourceCapabilities) { + return; + } setFocusedIndex(normalizedIndex); }} shouldSyncFocus={shouldSyncFocus} diff --git a/src/components/SelectionList/Search/SearchQueryListItem.tsx b/src/components/SelectionList/Search/SearchQueryListItem.tsx index 0dad7796556c..a0fb77821ba6 100644 --- a/src/components/SelectionList/Search/SearchQueryListItem.tsx +++ b/src/components/SelectionList/Search/SearchQueryListItem.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {View} from 'react-native'; +import type {NativeSyntheticEvent, TargetedEvent} from 'react-native'; import type {ValueOf} from 'type-fest'; import Icon from '@components/Icon'; import BaseListItem from '@components/SelectionList/BaseListItem'; @@ -23,7 +24,7 @@ type SearchQueryListItemProps = { isFocused?: boolean; showTooltip: boolean; onSelectRow: (item: SearchQueryItem) => void; - onFocus?: () => void; + onFocus?: (event: NativeSyntheticEvent) => void; shouldSyncFocus?: boolean; }; diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index edf6ee955ecc..d87169a9450c 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -7,6 +7,7 @@ import type { NativeSyntheticEvent, SectionListData, StyleProp, + TargetedEvent, TextInput, TextStyle, ViewStyle, @@ -84,7 +85,7 @@ type CommonListItemProps = { alternateTextNumberOfLines?: number; /** Handles what to do when the item is focused */ - onFocus?: () => void; + onFocus?: (event: NativeSyntheticEvent) => void; /** Callback to fire when the item is long pressed */ onLongPressRow?: (item: TItem) => void; From 1e2991871fb176caeb7b486f993d92694c817a92 Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Tue, 26 Nov 2024 22:54:58 +1300 Subject: [PATCH 005/179] Refactor --- .../SelectionList/BaseSelectionListItemRenderer.tsx | 8 +------- .../SelectionList/Search/SearchQueryListItem.tsx | 5 ++--- src/components/SelectionList/types.ts | 10 +++++++++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionListItemRenderer.tsx b/src/components/SelectionList/BaseSelectionListItemRenderer.tsx index 5a35ddfbb892..864a353ce784 100644 --- a/src/components/SelectionList/BaseSelectionListItemRenderer.tsx +++ b/src/components/SelectionList/BaseSelectionListItemRenderer.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import type {TargetedEvent} from 'react-native'; import type useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import type useSingleExecution from '@hooks/useSingleExecution'; import * as Browser from '@libs/Browser'; @@ -15,11 +14,6 @@ type BaseSelectionListItemRendererProps = Omit['singleExecution']; }; -// `sourceCapabilities` property exists in Chrome. -type ExtendedTargetedEvent = TargetedEvent & { - sourceCapabilities?: unknown; -}; - function BaseSelectionListItemRenderer({ ListItem, item, @@ -85,7 +79,7 @@ function BaseSelectionListItemRenderer({ return; } // Prevent unexpected scrolling on mobile Chrome after the context menu closes by ignoring programmatic focus not triggered by direct user interaction. - if (Browser.isMobileChrome() && event?.nativeEvent && !(event.nativeEvent as ExtendedTargetedEvent).sourceCapabilities) { + if (Browser.isMobileChrome() && event?.nativeEvent && !event.nativeEvent.sourceCapabilities) { return; } setFocusedIndex(normalizedIndex); diff --git a/src/components/SelectionList/Search/SearchQueryListItem.tsx b/src/components/SelectionList/Search/SearchQueryListItem.tsx index a0fb77821ba6..dd2fd33a006c 100644 --- a/src/components/SelectionList/Search/SearchQueryListItem.tsx +++ b/src/components/SelectionList/Search/SearchQueryListItem.tsx @@ -1,10 +1,9 @@ import React from 'react'; import {View} from 'react-native'; -import type {NativeSyntheticEvent, TargetedEvent} from 'react-native'; import type {ValueOf} from 'type-fest'; import Icon from '@components/Icon'; import BaseListItem from '@components/SelectionList/BaseListItem'; -import type {ListItem} from '@components/SelectionList/types'; +import type {ListItem, ListItemFocusEventHandler} from '@components/SelectionList/types'; import TextWithTooltip from '@components/TextWithTooltip'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -24,7 +23,7 @@ type SearchQueryListItemProps = { isFocused?: boolean; showTooltip: boolean; onSelectRow: (item: SearchQueryItem) => void; - onFocus?: (event: NativeSyntheticEvent) => void; + onFocus?: ListItemFocusEventHandler; shouldSyncFocus?: boolean; }; diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index d87169a9450c..086504176cc7 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -85,12 +85,19 @@ type CommonListItemProps = { alternateTextNumberOfLines?: number; /** Handles what to do when the item is focused */ - onFocus?: (event: NativeSyntheticEvent) => void; + onFocus?: ListItemFocusEventHandler; /** Callback to fire when the item is long pressed */ onLongPressRow?: (item: TItem) => void; } & TRightHandSideComponent; +type ListItemFocusEventHandler = (event: NativeSyntheticEvent) => void; + +type ExtendedTargetedEvent = TargetedEvent & { + /** Provides information about the input device responsible for the event, or null if triggered programmatically, available in some browsers */ + sourceCapabilities?: unknown; +}; + type ListItem = { /** Text to display */ text?: string; @@ -654,6 +661,7 @@ export type { InviteMemberListItemProps, ItemLayout, ListItem, + ListItemFocusEventHandler, ListItemProps, RadioListItemProps, ReportListItemProps, From 85b7568b66a612329c19bba992bc623ab8bd4b42 Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Tue, 26 Nov 2024 22:55:32 +1300 Subject: [PATCH 006/179] Set a fixed header height --- src/pages/workspace/categories/WorkspaceCategoriesPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index 38d34be4fb5c..978589d7414c 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -368,6 +368,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { Navigation.goBack(backTo); }} shouldShowThreeDotsButton + style={styles.headerBarDesktopHeight} threeDotsMenuItems={threeDotsMenuItems} threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)} > From cd37b5129065077111ef86012b5c44a84ea1e5a9 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 28 Nov 2024 14:22:17 +0700 Subject: [PATCH 007/179] fix lint --- src/components/LoadingBar.tsx | 64 +++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index 6956887227bb..63b4c80a4a8b 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -17,57 +17,63 @@ function LoadingBar({shouldShow}: LoadingBarProps) { useEffect(() => { if (shouldShow) { // eslint-disable-next-line react-compiler/react-compiler - left.value = 0; - width.value = 0; - opacity.value = withTiming(1, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION}); + left.set(0); + width.set(0); + opacity.set(withTiming(1, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION})); - left.value = withDelay( - CONST.ANIMATED_PROGRESS_BAR_DELAY, - withRepeat( - withSequence( - withTiming(0, {duration: 0}), - withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), - withTiming(100, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + left.set( + withDelay( + CONST.ANIMATED_PROGRESS_BAR_DELAY, + withRepeat( + withSequence( + withTiming(0, {duration: 0}), + withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + withTiming(100, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + ), + -1, + false, ), - -1, - false, ), ); - width.value = withDelay( - CONST.ANIMATED_PROGRESS_BAR_DELAY, - withRepeat( - withSequence( - withTiming(0, {duration: 0}), - withTiming(100, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), - withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + width.set( + withDelay( + CONST.ANIMATED_PROGRESS_BAR_DELAY, + withRepeat( + withSequence( + withTiming(0, {duration: 0}), + withTiming(100, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + ), + -1, + false, ), - -1, - false, ), ); } else { - opacity.value = withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION}, () => { - cancelAnimation(left); - cancelAnimation(width); - }); + opacity.set( + withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION}, () => { + cancelAnimation(left); + cancelAnimation(width); + }), + ); } // we want to update only when shouldShow changes // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [shouldShow]); const animatedIndicatorStyle = useAnimatedStyle(() => ({ - left: `${left.value}%`, - width: `${width.value}%`, + left: `${left.get()}%`, + width: `${width.get()}%`, })); const animatedContainerStyle = useAnimatedStyle(() => ({ - opacity: opacity.value, + opacity: opacity.get(), })); return ( - {opacity.value > 0 ? : null} + {opacity.get() > 0 ? : null} ); } From 7e16699b8cffd0dd75c894f510b8bb7224968308 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 28 Nov 2024 14:26:35 +0700 Subject: [PATCH 008/179] fix border radius --- src/styles/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index 5cdbf4fa6ea0..111312d749c5 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5325,7 +5325,7 @@ const styles = (theme: ThemeColors) => height: 2, width: '100%', backgroundColor: theme.border, - borderRadius: 5, + borderRadius: 2, overflow: 'hidden', }, From 4015f65a09831a14d688a9812069880954d7b1b1 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 29 Nov 2024 12:36:08 +0700 Subject: [PATCH 009/179] fix loading bar animation doesn't work --- src/components/LoadingBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index 63b4c80a4a8b..e6d1ec0cd66d 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -73,7 +73,7 @@ function LoadingBar({shouldShow}: LoadingBarProps) { return ( - {opacity.get() > 0 ? : null} + ); } From 683e2c3636794e1cf782846c2732c15082673904 Mon Sep 17 00:00:00 2001 From: nkdengineer <161821005+nkdengineer@users.noreply.github.com> Date: Thu, 5 Dec 2024 11:55:32 +0700 Subject: [PATCH 010/179] Update src/pages/home/ReportScreen.tsx Co-authored-by: Getabalew <75031127+getusha@users.noreply.github.com> --- src/pages/home/ReportScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 8e9bc187a92d..022a48db3f42 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -769,7 +769,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro needsOffscreenAlphaCompositing > {headerView} - {shouldUseNarrowLayout && !!isLoadingReportData && } + {shouldUseNarrowLayout && } {!!report && ReportUtils.isTaskReport(report) && shouldUseNarrowLayout && ReportUtils.isOpenTaskReport(report, parentReportAction) && ( From 1b1278748b6417baf61ab8943e439607c9b9cf33 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 5 Dec 2024 11:33:56 +0530 Subject: [PATCH 011/179] fix: Tweak UI for deleted message and expense. Signed-off-by: krishna2323 --- src/CONST.ts | 8 ++ .../BaseHTMLEngineProvider.tsx | 5 + .../HTMLRenderers/DeletedActionRenderer.tsx | 64 ++++++++++ .../HTMLEngineProvider/HTMLRenderers/index.ts | 2 + .../home/report/ReportActionItemFragment.tsx | 2 +- tests/ui/DemoTest.tsx | 120 ++++++++++++++++++ 6 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx create mode 100644 tests/ui/DemoTest.tsx diff --git a/src/CONST.ts b/src/CONST.ts index c28914541113..52ff37ca791b 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1184,6 +1184,14 @@ const CONST = { TEXT: 'TEXT', }, }, + ACTION_TYPE: { + DELETED_MESSAGE: 'deletedMessage', + DELETED_REPORT: 'deletedReport', + DELETED_EXPENSE: 'deletedExpense', + REVERSED_TRANSACTION: 'reversedTransaction', + DELETED_TASK: 'deletedTask', + HIDDEN_MESSAGE: 'hiddenMessage', + }, TYPE: { CHAT: 'chat', EXPENSE: 'expense', diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx index d211aac7fd4c..703835710e37 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx @@ -37,6 +37,11 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim mixedUAStyles: {...styles.formError, ...styles.mb0}, contentModel: HTMLContentModel.block, }), + 'deleted-action': HTMLElementModel.fromCustomModel({ + tagName: 'alert-text', + mixedUAStyles: {...styles.formError, ...styles.mb0}, + contentModel: HTMLContentModel.block, + }), 'muted-text': HTMLElementModel.fromCustomModel({ tagName: 'muted-text', mixedUAStyles: {...styles.colorMuted, ...styles.mb0}, diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx new file mode 100644 index 000000000000..55b628ac4cba --- /dev/null +++ b/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import {View} from 'react-native'; +import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; + +function DeletedActionRenderer({tnode}: CustomRendererProps) { + const styles = useThemeStyles(); + const theme = useTheme(); + const {translate} = useLocalize(); + + const actionTypes = Object.values(CONST.REPORT.ACTION_TYPE); + const action = tnode.attributes.action; + let translation = action; + + if (actionTypes.some((e) => e === action)) { + translation = translate(`parentReportAction.${action}` as TranslationPaths); + } + + Object.values(CONST.REPORT.ACTION_TYPE); + + const getIcon = () => { + // This needs to be updated with new icons + switch (action) { + case CONST.REPORT.ACTION_TYPE.DELETED_MESSAGE: + return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; + case CONST.REPORT.ACTION_TYPE.DELETED_EXPENSE: + return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; + case CONST.REPORT.ACTION_TYPE.DELETED_REPORT: + return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; + case CONST.REPORT.ACTION_TYPE.DELETED_TASK: + return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; + case CONST.REPORT.ACTION_TYPE.HIDDEN_MESSAGE: + return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; + case CONST.REPORT.ACTION_TYPE.REVERSED_TRANSACTION: + return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; + default: + return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; + } + }; + + const icon = getIcon(); + return ( + + + {translation} + + ); +} + +DeletedActionRenderer.displayName = 'DeletedActionRenderer'; + +export default DeletedActionRenderer; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts index ce24584048b0..91ed66f8b931 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts +++ b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts @@ -1,6 +1,7 @@ import type {CustomTagRendererRecord} from 'react-native-render-html'; import AnchorRenderer from './AnchorRenderer'; import CodeRenderer from './CodeRenderer'; +import DeletedActionRenderer from './DeletedActionRenderer'; import EditedRenderer from './EditedRenderer'; import EmojiRenderer from './EmojiRenderer'; import ImageRenderer from './ImageRenderer'; @@ -30,6 +31,7 @@ const HTMLEngineProviderComponentList: CustomTagRendererRecord = { 'mention-here': MentionHereRenderer, emoji: EmojiRenderer, 'next-step-email': NextStepEmailRenderer, + 'deleted-action': DeletedActionRenderer, /* eslint-enable @typescript-eslint/naming-convention */ }; diff --git a/src/pages/home/report/ReportActionItemFragment.tsx b/src/pages/home/report/ReportActionItemFragment.tsx index 05cb657b1e54..b6e4a1e9f48e 100644 --- a/src/pages/home/report/ReportActionItemFragment.tsx +++ b/src/pages/home/report/ReportActionItemFragment.tsx @@ -106,7 +106,7 @@ function ReportActionItemFragment({ // immediately display "[Deleted message]" while the delete action is pending. if ((!isOffline && isThreadParentMessage && isPendingDelete) || fragment?.isDeletedParentAction) { - return ${translate('parentReportAction.deletedMessage')}`} />; + return `} />; } if (isThreadParentMessage && moderationDecision === CONST.MODERATION.MODERATOR_DECISION_PENDING_REMOVE) { diff --git a/tests/ui/DemoTest.tsx b/tests/ui/DemoTest.tsx new file mode 100644 index 000000000000..d05ca6a1524b --- /dev/null +++ b/tests/ui/DemoTest.tsx @@ -0,0 +1,120 @@ +import * as NativeNavigation from '@react-navigation/native'; +import {fireEvent, render, screen} from '@testing-library/react-native'; +import Onyx from 'react-native-onyx'; +import {act} from 'react-test-renderer'; +import * as Report from '@libs/actions/Report'; +import * as Localize from '@libs/Localize'; +import App from '@src/App'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {PolicyCollectionDataSet} from '@src/types/onyx/Policy'; +import type {ReportCollectionDataSet} from '@src/types/onyx/Report'; +import type {NativeNavigationMock} from '../../__mocks__/@react-navigation/native'; +import * as LHNTestUtils from '../utils/LHNTestUtils'; +import PusherHelper from '../utils/PusherHelper'; +import * as TestHelper from '../utils/TestHelper'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; +import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; + +const USER_A_ACCOUNT_ID = 1; +const USER_A_EMAIL = 'user_a@test.com'; + +jest.setTimeout(60000); + +jest.mock('@react-navigation/native'); + +TestHelper.setupApp(); +TestHelper.setupGlobalFetchMock(); + +function navigateToSetting() { + const hintText = Localize.translateLocal('workspace.switcher.headerTitle'); + const mySettingButton = screen.queryByAccessibilityHint(hintText); + if (mySettingButton) { + fireEvent(mySettingButton, 'press'); + } + return waitForBatchedUpdatesWithAct(); +} + +function navigateToExpensifyClassicFlow() { + const hintText = Localize.translateLocal('exitSurvey.goToExpensifyClassic'); + const switchToExpensifyClassicBtn = screen.queryByAccessibilityHint('Workspace 1'); + expect(switchToExpensifyClassicBtn).toHaveLength(210); + if (switchToExpensifyClassicBtn) { + fireEvent(switchToExpensifyClassicBtn, 'press'); + } + return waitForBatchedUpdatesWithAct(); +} + +function signInAppAndEnterTestFlow(dismissedValue?: boolean): Promise { + render(); + return waitForBatchedUpdatesWithAct() + .then(async () => { + await waitForBatchedUpdatesWithAct(); + const hintText = Localize.translateLocal('loginForm.loginForm'); + const loginForm = screen.queryAllByLabelText(hintText); + expect(loginForm).toHaveLength(1); + + // Given three unread reports in the recently updated order of 3, 2, 1 + const report1 = LHNTestUtils.getFakeReport([1, 2], 3); + const report2 = LHNTestUtils.getFakeReport([1, 3], 2); + const report3 = LHNTestUtils.getFakeReport([1, 4], 1); + + const policy1 = LHNTestUtils.getFakePolicy('1', 'Workspace A'); + const policy2 = LHNTestUtils.getFakePolicy('2', 'B'); + const policy3 = LHNTestUtils.getFakePolicy('3', 'C'); + + // Each report has at least one ADD_COMMENT action so should be rendered in the LNH + Report.addComment(report1.reportID, 'Hi, this is a comment'); + Report.addComment(report2.reportID, 'Hi, this is a comment'); + Report.addComment(report3.reportID, 'Hi, this is a comment'); + + const reportCollectionDataSet: ReportCollectionDataSet = { + [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, + [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, + [`${ONYXKEYS.COLLECTION.REPORT}${report3.reportID}`]: report3, + }; + + const policyCollectionDataSet: PolicyCollectionDataSet = { + [`${ONYXKEYS.COLLECTION.POLICY}${policy1.id}`]: policy1, + [`${ONYXKEYS.COLLECTION.POLICY}${policy2.id}`]: policy2, + [`${ONYXKEYS.COLLECTION.POLICY}${policy3.id}`]: policy3, + }; + + await act(async () => { + await TestHelper.signInWithTestUser(USER_A_ACCOUNT_ID, USER_A_EMAIL, undefined, undefined, 'A'); + }); + await Onyx.multiSet({ + [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, + [ONYXKEYS.IS_LOADING_APP]: false, + ...reportCollectionDataSet, + ...policyCollectionDataSet, + }); + await waitForBatchedUpdates(); + return navigateToSetting(); + }) + .then(async () => { + await act(() => (NativeNavigation as NativeNavigationMock).triggerTransitionEnd()); + return navigateToExpensifyClassicFlow(); + }); +} + +describe('Switch to Expensify Classic flow', () => { + beforeEach(() => { + jest.clearAllMocks(); + Onyx.clear(); + + // Unsubscribe to pusher channels + PusherHelper.teardown(); + }); + + test('Should navigate to BookACall when dismissed is false', () => { + signInAppAndEnterTestFlow(false).then(() => { + expect(screen.getAllByText(Localize.translateLocal('exitSurvey.bookACallTitle')).at(0)).toBeOnTheScreen(); + }); + }); + + test('Should navigate to ConfirmPage when dismissed is true', () => { + signInAppAndEnterTestFlow(true).then(() => { + expect(screen.getAllByText(Localize.translateLocal('exitSurvey.goToExpensifyClassic')).at(0)).toBeOnTheScreen(); + }); + }); +}); From 7c78f31687876f7d98ea92511132774b6c2fb915 Mon Sep 17 00:00:00 2001 From: Qichen Zhu <57348009+QichenZhu@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:00:55 +1300 Subject: [PATCH 012/179] Apply suggestions from code review Co-authored-by: Eugene Voloshchak --- .../SelectionList/BaseSelectionListItemRenderer.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionListItemRenderer.tsx b/src/components/SelectionList/BaseSelectionListItemRenderer.tsx index 864a353ce784..56ba75786fb8 100644 --- a/src/components/SelectionList/BaseSelectionListItemRenderer.tsx +++ b/src/components/SelectionList/BaseSelectionListItemRenderer.tsx @@ -1,9 +1,10 @@ import React from 'react'; +import type {NativeSyntheticEvent} from 'react-native'; import type useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import type useSingleExecution from '@hooks/useSingleExecution'; import * as Browser from '@libs/Browser'; import * as SearchUIUtils from '@libs/SearchUIUtils'; -import type {BaseListItemProps, BaseSelectionListProps, ListItem} from './types'; +import type {BaseListItemProps, BaseSelectionListProps, ExtendedTargetedEvent, ListItem} from './types'; type BaseSelectionListItemRendererProps = Omit, 'onSelectRow'> & Pick, 'ListItem' | 'shouldHighlightSelectedItem' | 'shouldIgnoreFocus' | 'shouldSingleExecuteRowSelect'> & { @@ -73,13 +74,13 @@ function BaseSelectionListItemRenderer({ isMultilineSupported={isMultilineSupported} isAlternateTextMultilineSupported={isAlternateTextMultilineSupported} alternateTextNumberOfLines={alternateTextNumberOfLines} - onFocus={(event) => { + onFocus={(event: NativeSyntheticEvent) => { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if (shouldIgnoreFocus || isDisabled) { return; } // Prevent unexpected scrolling on mobile Chrome after the context menu closes by ignoring programmatic focus not triggered by direct user interaction. - if (Browser.isMobileChrome() && event?.nativeEvent && !event.nativeEvent.sourceCapabilities) { + if (Browser.isMobileChrome() && event.nativeEvent && !event.nativeEvent.sourceCapabilities) { return; } setFocusedIndex(normalizedIndex); From 1ff1abcfe910168f71f6b233283c4162b144f8d3 Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:05:53 +1300 Subject: [PATCH 013/179] Export event type --- src/components/SelectionList/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 458586f545d7..db68f2f568f6 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -672,6 +672,7 @@ export type { BaseSelectionListProps, ButtonOrCheckBoxRoles, CommonListItemProps, + ExtendedTargetedEvent, FlattenedSectionsReturn, InviteMemberListItemProps, ItemLayout, From fcd4b10305948bc938c44dcc1c7bb1e30356211d Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:50:07 +1300 Subject: [PATCH 014/179] Pass preventScroll prop to focus-trap --- .../FocusTrap/FocusTrapForModal/FocusTrapForModalProps.ts | 1 + src/components/FocusTrap/FocusTrapForModal/index.web.tsx | 3 ++- src/components/Modal/BaseModal.tsx | 2 ++ src/components/Modal/types.ts | 3 +++ src/components/SelectionListWithModal/index.tsx | 1 + 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/FocusTrap/FocusTrapForModal/FocusTrapForModalProps.ts b/src/components/FocusTrap/FocusTrapForModal/FocusTrapForModalProps.ts index 5793885dacd1..6311a41f0987 100644 --- a/src/components/FocusTrap/FocusTrapForModal/FocusTrapForModalProps.ts +++ b/src/components/FocusTrap/FocusTrapForModal/FocusTrapForModalProps.ts @@ -6,6 +6,7 @@ type FocusTrapForModalProps = { children: React.ReactNode; active: boolean; initialFocus?: FocusTrapOptions['initialFocus']; + shouldPreventScroll?: boolean; }; export default FocusTrapForModalProps; diff --git a/src/components/FocusTrap/FocusTrapForModal/index.web.tsx b/src/components/FocusTrap/FocusTrapForModal/index.web.tsx index 63a33899822c..36b20c73535c 100644 --- a/src/components/FocusTrap/FocusTrapForModal/index.web.tsx +++ b/src/components/FocusTrap/FocusTrapForModal/index.web.tsx @@ -5,12 +5,13 @@ import blurActiveElement from '@libs/Accessibility/blurActiveElement'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import type FocusTrapForModalProps from './FocusTrapForModalProps'; -function FocusTrapForModal({children, active, initialFocus = false}: FocusTrapForModalProps) { +function FocusTrapForModal({children, active, initialFocus = false, shouldPreventScroll = false}: FocusTrapForModalProps) { return ( , ) { @@ -283,6 +284,7 @@ function BaseModal( & { /** Used to set the element that should receive the initial focus */ initialFocus?: FocusTrapOptions['initialFocus']; + + /** Whether to prevent the focus trap from scrolling the element into view. */ + shouldPreventScrollOnFocus?: boolean; }; export default BaseModalProps; diff --git a/src/components/SelectionListWithModal/index.tsx b/src/components/SelectionListWithModal/index.tsx index 59e9b268bc52..a4c8b83b29d8 100644 --- a/src/components/SelectionListWithModal/index.tsx +++ b/src/components/SelectionListWithModal/index.tsx @@ -113,6 +113,7 @@ function SelectionListWithModal( isVisible={isModalVisible} type={CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED} onClose={() => setIsModalVisible(false)} + shouldPreventScrollOnFocus > Date: Mon, 9 Dec 2024 22:16:24 +1300 Subject: [PATCH 015/179] Fix stories --- src/stories/SelectionList.stories.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/stories/SelectionList.stories.tsx b/src/stories/SelectionList.stories.tsx index 0e87fdbb4239..b3dc4c5ae2d2 100644 --- a/src/stories/SelectionList.stories.tsx +++ b/src/stories/SelectionList.stories.tsx @@ -19,6 +19,13 @@ const SelectionListWithNavigation = withNavigationFallback(SelectionList); const story: Meta = { title: 'Components/SelectionList', component: SelectionList, + parameters: { + docs: { + source: { + type: 'code', + }, + }, + }, }; const SECTIONS = [ @@ -417,6 +424,7 @@ WithConfirmButton.args = { ...MultipleSelection.args, onConfirm: () => {}, confirmButtonText: 'Confirm', + showConfirmButton: true, }; export {Default, WithTextInput, WithHeaderMessage, WithAlternateText, MultipleSelection, WithSectionHeader, WithConfirmButton}; From bf4660ceff559f426b5e3d807fe763cc5fcbb6dd Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 10 Dec 2024 15:53:42 +0700 Subject: [PATCH 016/179] remove initial value --- src/pages/home/ReportScreen.tsx | 2 +- src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index c4a7952b4b67..81079f52d045 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -137,7 +137,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro const finishedLoadingApp = wasLoadingApp && !isLoadingApp; const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(parentReportAction); const prevIsDeletedParentAction = usePrevious(isDeletedParentAction); - const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA, {initialValue: true}); + const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA); const isLoadingReportOnyx = isLoadingOnyxValue(reportResult); const permissions = useDeepCompareRef(reportOnyx?.permissions); diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index 6ab3fd109217..8db6ba294908 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -23,7 +23,7 @@ function BaseSidebarScreen() { const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const [activeWorkspace] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID ?? -1}`); - const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA, {initialValue: true}); + const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA); useEffect(() => { Performance.markStart(CONST.TIMING.SIDEBAR_LOADED); Timing.start(CONST.TIMING.SIDEBAR_LOADED); From bb2b53811f18b1b8731ccfb95f59f830992aefe0 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 18 Dec 2024 14:14:06 +0700 Subject: [PATCH 017/179] move the loading bar to TopBar and HeaderView --- src/components/LoadingBar.tsx | 1 + .../AppNavigator/createCustomBottomTabNavigator/TopBar.tsx | 3 +++ src/pages/home/HeaderView.tsx | 3 +++ src/pages/home/ReportScreen.tsx | 2 +- src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx | 2 +- 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index e6d1ec0cd66d..68408cb2881f 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -19,6 +19,7 @@ function LoadingBar({shouldShow}: LoadingBarProps) { // eslint-disable-next-line react-compiler/react-compiler left.set(0); width.set(0); + height.set(withTiming(2, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION})); opacity.set(withTiming(1, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION})); left.set( diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx index 01caa79692f1..aad05f388971 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx @@ -16,6 +16,7 @@ import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import LoadingBar from '@components/LoadingBar'; type TopBarProps = { breadcrumbLabel: string; @@ -35,6 +36,7 @@ function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, const {translate} = useLocalize(); const policy = usePolicy(activeWorkspaceID); const [session] = useOnyx(ONYXKEYS.SESSION, {selector: (sessionValue) => sessionValue && {authTokenType: sessionValue.authTokenType}}); + const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA); const isAnonymousUser = Session.isAnonymousUser(session); const headerBreadcrumb = policy?.name @@ -83,6 +85,7 @@ function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, )} {displaySearch && } + ); } diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index 03e7dcd82156..30cb51fad186 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -40,6 +40,7 @@ import SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; import type {Icon as IconType} from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import LoadingBar from '@components/LoadingBar'; type HeaderViewProps = { /** Toggles the navigationMenu open and closed */ @@ -75,6 +76,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || report?.reportID || '-1'}`); const policy = usePolicy(report?.policyID); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); + const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA); const {translate} = useLocalize(); const theme = useTheme(); @@ -312,6 +314,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto {!isParentReportLoading && !isLoading && canJoin && shouldUseNarrowLayout && {joinButton}} {!isLoading && isChatUsedForOnboarding && shouldUseNarrowLayout && {freeTrialButton}} + {shouldUseNarrowLayout && } ); } diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 81079f52d045..6250cc6d7b22 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -786,7 +786,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro needsOffscreenAlphaCompositing > {headerView} - {shouldUseNarrowLayout && } + {/* {shouldUseNarrowLayout && } */} {!!report && ReportUtils.isTaskReport(report) && shouldUseNarrowLayout && ReportUtils.isOpenTaskReport(report, parentReportAction) && ( diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index 8db6ba294908..de003c462e34 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -70,7 +70,7 @@ function BaseSidebarScreen() { shouldDisplaySearch={shouldDisplaySearch} onSwitchWorkspace={() => (isSwitchingWorkspace.current = true)} /> - + {/* */} From 6da849152d798dbfed815390b478da1c86a784a1 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 19 Dec 2024 04:44:33 +0700 Subject: [PATCH 018/179] fix bug --- src/components/LoadingBar.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index 68408cb2881f..e6d1ec0cd66d 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -19,7 +19,6 @@ function LoadingBar({shouldShow}: LoadingBarProps) { // eslint-disable-next-line react-compiler/react-compiler left.set(0); width.set(0); - height.set(withTiming(2, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION})); opacity.set(withTiming(1, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION})); left.set( From bca49eabf4e8777288f1c3b69330a99e7aa926a9 Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Fri, 20 Dec 2024 14:08:31 +1300 Subject: [PATCH 019/179] Remove default ID --- src/pages/workspace/categories/WorkspaceCategoriesPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index 130677c77cdc..98a61f32aec1 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -72,7 +72,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { const [isDownloadFailureModalVisible, setIsDownloadFailureModalVisible] = useState(false); const [deleteCategoriesConfirmModalVisible, setDeleteCategoriesConfirmModalVisible] = useState(false); const {environmentURL} = useEnvironment(); - const policyId = route.params.policyID ?? '-1'; + const policyId = route.params.policyID; const backTo = route.params?.backTo; const policy = usePolicy(policyId); const {selectionMode} = useMobileSelectionMode(); From fce28c5f3bc715b2d2dd02dda2faedb9086de7f6 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 20 Dec 2024 22:01:35 +0700 Subject: [PATCH 020/179] update loading bar height to 1px --- src/styles/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index 602b3174d593..799a567d7d17 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5339,7 +5339,7 @@ const styles = (theme: ThemeColors) => }, progressBarWrapper: { - height: 2, + height: 1, width: '100%', backgroundColor: theme.border, borderRadius: 2, From 118212b3786fd509ed1e4b542e21011abe672b93 Mon Sep 17 00:00:00 2001 From: Povilas Zirgulis Date: Thu, 2 Jan 2025 11:19:56 +0200 Subject: [PATCH 021/179] improve ActiveHoverable CPU performance --- src/components/Hoverable/ActiveHoverable.tsx | 172 ++++++++----------- 1 file changed, 76 insertions(+), 96 deletions(-) diff --git a/src/components/Hoverable/ActiveHoverable.tsx b/src/components/Hoverable/ActiveHoverable.tsx index eda42a703d65..5015f6e55beb 100644 --- a/src/components/Hoverable/ActiveHoverable.tsx +++ b/src/components/Hoverable/ActiveHoverable.tsx @@ -15,142 +15,122 @@ type OnMouseEvents = Record void>; function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, shouldFreezeCapture, children}: ActiveHoverableProps, outerRef: Ref) { const [isHovered, setIsHovered] = useState(false); - const elementRef = useRef(null); const isScrollingRef = useRef(false); - const isHoveredRef = useRef(false); - const isVisibiltyHidden = useRef(false); - - const updateIsHovered = useCallback( - (hovered: boolean) => { - isHoveredRef.current = hovered; - // Nullish coalescing operator (`??`) wouldn't be appropriate here because - // it's not a matter of providing a default when encountering `null` or `undefined` - // but rather making a decision based on the truthy nature of the complete expressions. - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - if ((shouldHandleScroll && isScrollingRef.current) || shouldFreezeCapture) { + const lastKnownHoverState = useRef(false); + + const updateHoverState = useCallback( + (value: boolean) => { + if (lastKnownHoverState.current === value) { return; } - setIsHovered(hovered); + lastKnownHoverState.current = value; + setIsHovered(value); + if (value) { + onHoverIn?.(); + } else { + onHoverOut?.(); + } }, - [shouldHandleScroll, shouldFreezeCapture], + [onHoverIn, onHoverOut], ); - useEffect(() => { - if (isHovered) { - onHoverIn?.(); - } else { - onHoverOut?.(); - } - }, [isHovered, onHoverIn, onHoverOut]); - useEffect(() => { if (!shouldHandleScroll) { return; } const scrollingListener = DeviceEventEmitter.addListener(CONST.EVENTS.SCROLLING, (scrolling: boolean) => { + const wasScrolling = isScrollingRef.current; isScrollingRef.current = scrolling; - if (!isScrollingRef.current) { - setIsHovered(isHoveredRef.current); + + if (scrolling) { + // Store current hover state before clearing it + if (isHovered) { + lastKnownHoverState.current = true; + } + updateHoverState(false); + } else if (wasScrolling) { + // Only check hover state when scroll actually ends + const isMouseOver = elementRef.current?.matches(':hover'); + if (isMouseOver && !shouldFreezeCapture && lastKnownHoverState.current) { + updateHoverState(true); + } } }); return () => scrollingListener.remove(); - }, [shouldHandleScroll]); + }, [shouldHandleScroll, shouldFreezeCapture, updateHoverState, isHovered]); + // Handle visibility changes useEffect(() => { - // Do not mount a listener if the component is not hovered - if (!isHovered) { - return; - } - - /** - * Checks the hover state of a component and updates it based on the event target. - * This is necessary to handle cases where the hover state might get stuck due to an unreliable mouseleave trigger, - * such as when an element is removed before the mouseleave event is triggered. - * @param event The hover event object. - */ - const unsetHoveredIfOutside = (event: MouseEvent) => { - // We're also returning early if shouldFreezeCapture is true in order - // to not update the hover state but keep it frozen. - if (!elementRef.current || elementRef.current.contains(event.target as Node) || shouldFreezeCapture) { - return; + const handleVisibilityChange = () => { + if (document.visibilityState === 'hidden') { + if (isHovered) { + lastKnownHoverState.current = true; + } + updateHoverState(false); + } else if (lastKnownHoverState.current) { + // When becoming visible, check if we should restore hover + const isMouseOver = elementRef.current?.matches(':hover'); + if (isMouseOver && !shouldFreezeCapture) { + requestAnimationFrame(() => { + updateHoverState(true); + }); + } } - - setIsHovered(false); }; - document.addEventListener('mouseover', unsetHoveredIfOutside, true); - - return () => document.removeEventListener('mouseover', unsetHoveredIfOutside); - }, [isHovered, elementRef, shouldFreezeCapture]); + document.addEventListener('visibilitychange', handleVisibilityChange); + return () => document.removeEventListener('visibilitychange', handleVisibilityChange); + }, [updateHoverState, isHovered, shouldFreezeCapture]); - useEffect(() => { - const unsetHoveredWhenDocumentIsHidden = () => { - if (document.visibilityState !== 'hidden') { + const handleMouseEvents = useCallback( + (type: 'enter' | 'leave' | 'blur' | 'move') => (e: MouseEvent) => { + if (shouldFreezeCapture ?? (shouldHandleScroll && isScrollingRef.current)) { return; } - isVisibiltyHidden.current = true; - setIsHovered(false); - }; + if (type === 'blur') { + if (!elementRef.current?.contains(e.relatedTarget as Node)) { + updateHoverState(false); + } + return; + } - document.addEventListener('visibilitychange', unsetHoveredWhenDocumentIsHidden); + // Handle quick mouse movements with move event + if (type === 'move' && !isHovered && lastKnownHoverState.current) { + updateHoverState(true); + return; + } - return () => document.removeEventListener('visibilitychange', unsetHoveredWhenDocumentIsHidden); - }, []); + updateHoverState(type === 'enter'); + }, + [shouldFreezeCapture, shouldHandleScroll, updateHoverState, isHovered], + ); const child = useMemo(() => getReturnValue(children, !isScrollingRef.current && isHovered), [children, isHovered]); const {onMouseEnter, onMouseLeave, onMouseMove, onBlur} = child.props as OnMouseEvents; - const hoverAndForwardOnMouseEnter = useCallback( - (e: MouseEvent) => { - isVisibiltyHidden.current = false; - updateIsHovered(true); + return cloneElement(child, { + ref: mergeRefs(elementRef, outerRef, child.ref), + onMouseEnter: (e: MouseEvent) => { + handleMouseEvents('enter')(e); onMouseEnter?.(e); }, - [updateIsHovered, onMouseEnter], - ); - - const unhoverAndForwardOnMouseLeave = useCallback( - (e: MouseEvent) => { - updateIsHovered(false); + onMouseLeave: (e: MouseEvent) => { + handleMouseEvents('leave')(e); onMouseLeave?.(e); }, - [updateIsHovered, onMouseLeave], - ); - - const unhoverAndForwardOnBlur = useCallback( - (event: MouseEvent) => { - // Check if the blur event occurred due to clicking outside the element - // and the wrapperView contains the element that caused the blur and reset isHovered - if (!elementRef.current?.contains(event.target as Node) && !elementRef.current?.contains(event.relatedTarget as Node) && !shouldFreezeCapture) { - setIsHovered(false); - } - - onBlur?.(event); - }, - [onBlur, shouldFreezeCapture], - ); - - const handleAndForwardOnMouseMove = useCallback( - (e: MouseEvent) => { - isVisibiltyHidden.current = false; - updateIsHovered(true); + onMouseMove: (e: MouseEvent) => { + handleMouseEvents('move')(e); onMouseMove?.(e); }, - [updateIsHovered, onMouseMove], - ); - - return cloneElement(child, { - ref: mergeRefs(elementRef, outerRef, child.ref), - onMouseEnter: hoverAndForwardOnMouseEnter, - onMouseLeave: unhoverAndForwardOnMouseLeave, - onBlur: unhoverAndForwardOnBlur, - ...(isVisibiltyHidden.current ? {onMouseMove: handleAndForwardOnMouseMove} : {}), + onBlur: (e: MouseEvent) => { + handleMouseEvents('blur')(e); + onBlur?.(e); + }, }); } - export default forwardRef(ActiveHoverable); From e6069b2480c8c016c7d533ce4284adf90e13eb21 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jan 2025 17:29:22 +0530 Subject: [PATCH 022/179] add chatbubble slash svg. Signed-off-by: krishna2323 --- assets/images/chatbubble-slash.svg | 4 ++++ .../HTMLRenderers/DeletedActionRenderer.tsx | 6 ++---- src/components/Icon/Expensicons.ts | 2 ++ src/pages/home/report/ReportActionItemFragment.tsx | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 assets/images/chatbubble-slash.svg diff --git a/assets/images/chatbubble-slash.svg b/assets/images/chatbubble-slash.svg new file mode 100644 index 000000000000..09d2b5bd3149 --- /dev/null +++ b/assets/images/chatbubble-slash.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx index 55b628ac4cba..aaa0f3a3d2d1 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx @@ -23,13 +23,11 @@ function DeletedActionRenderer({tnode}: CustomRendererProps) translation = translate(`parentReportAction.${action}` as TranslationPaths); } - Object.values(CONST.REPORT.ACTION_TYPE); - const getIcon = () => { // This needs to be updated with new icons switch (action) { case CONST.REPORT.ACTION_TYPE.DELETED_MESSAGE: - return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; + return {icon: Expensicons.ChatBubbleSlash, width: 18, height: 18}; case CONST.REPORT.ACTION_TYPE.DELETED_EXPENSE: return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; case CONST.REPORT.ACTION_TYPE.DELETED_REPORT: @@ -47,7 +45,7 @@ function DeletedActionRenderer({tnode}: CustomRendererProps) const icon = getIcon(); return ( - + `} />; + return `} />; } if (isThreadParentMessage && moderationDecision === CONST.MODERATION.MODERATOR_DECISION_PENDING_REMOVE) { From 0c52fb1599e2fbe29098ae757731470a3397e1ed Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 6 Jan 2025 17:49:24 +0530 Subject: [PATCH 023/179] update deleted message fragment for tasks. Signed-off-by: krishna2323 --- src/CONST.ts | 2 +- .../HTMLRenderers/DeletedActionRenderer.tsx | 14 +- .../ReportActionItem/TaskPreview.tsx | 3 +- .../report/ReportActionItemContentCreated.tsx | 2 +- .../home/report/ReportActionItemFragment.tsx | 2 +- .../home/report/ReportActionItemSingle.tsx | 2 + tests/ui/DemoTest.tsx | 120 ------------------ 7 files changed, 14 insertions(+), 131 deletions(-) delete mode 100644 tests/ui/DemoTest.tsx diff --git a/src/CONST.ts b/src/CONST.ts index 8be35e0c98ac..f883340c5bb2 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1197,7 +1197,7 @@ const CONST = { TEXT: 'TEXT', }, }, - ACTION_TYPE: { + DELETED_ACTION_TYPE: { DELETED_MESSAGE: 'deletedMessage', DELETED_REPORT: 'deletedReport', DELETED_EXPENSE: 'deletedExpense', diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx index aaa0f3a3d2d1..546ea5c6ca5a 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx @@ -15,7 +15,7 @@ function DeletedActionRenderer({tnode}: CustomRendererProps) const theme = useTheme(); const {translate} = useLocalize(); - const actionTypes = Object.values(CONST.REPORT.ACTION_TYPE); + const actionTypes = Object.values(CONST.REPORT.DELETED_ACTION_TYPE); const action = tnode.attributes.action; let translation = action; @@ -26,17 +26,17 @@ function DeletedActionRenderer({tnode}: CustomRendererProps) const getIcon = () => { // This needs to be updated with new icons switch (action) { - case CONST.REPORT.ACTION_TYPE.DELETED_MESSAGE: + case CONST.REPORT.DELETED_ACTION_TYPE.DELETED_MESSAGE: return {icon: Expensicons.ChatBubbleSlash, width: 18, height: 18}; - case CONST.REPORT.ACTION_TYPE.DELETED_EXPENSE: + case CONST.REPORT.DELETED_ACTION_TYPE.DELETED_EXPENSE: return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; - case CONST.REPORT.ACTION_TYPE.DELETED_REPORT: + case CONST.REPORT.DELETED_ACTION_TYPE.DELETED_REPORT: return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; - case CONST.REPORT.ACTION_TYPE.DELETED_TASK: + case CONST.REPORT.DELETED_ACTION_TYPE.DELETED_TASK: return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; - case CONST.REPORT.ACTION_TYPE.HIDDEN_MESSAGE: + case CONST.REPORT.DELETED_ACTION_TYPE.HIDDEN_MESSAGE: return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; - case CONST.REPORT.ACTION_TYPE.REVERSED_TRANSACTION: + case CONST.REPORT.DELETED_ACTION_TYPE.REVERSED_TRANSACTION: return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; default: return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; diff --git a/src/components/ReportActionItem/TaskPreview.tsx b/src/components/ReportActionItem/TaskPreview.tsx index 8c6cf3d43e3f..08928a362c20 100644 --- a/src/components/ReportActionItem/TaskPreview.tsx +++ b/src/components/ReportActionItem/TaskPreview.tsx @@ -100,7 +100,8 @@ function TaskPreview({ const shouldShowGreenDotIndicator = ReportUtils.isOpenTaskReport(taskReport, action) && ReportUtils.isReportManager(taskReport); if (isDeletedParentAction) { - return ${translate('parentReportAction.deletedTask')}`} />; + // return ${translate('parentReportAction.deletedTask')}`} />; + return `} />; } return ( diff --git a/src/pages/home/report/ReportActionItemContentCreated.tsx b/src/pages/home/report/ReportActionItemContentCreated.tsx index 69e27701edd8..3da54f6ff6a5 100644 --- a/src/pages/home/report/ReportActionItemContentCreated.tsx +++ b/src/pages/home/report/ReportActionItemContentCreated.tsx @@ -131,7 +131,7 @@ function ReportActionItemContentCreated({contextValue, parentReportAction, trans showHeader={draftMessage === undefined} report={report} > - ${translate('parentReportAction.deletedTask')}`} /> + `} /> diff --git a/src/pages/home/report/ReportActionItemFragment.tsx b/src/pages/home/report/ReportActionItemFragment.tsx index 3a7f57f36a2f..f6e75d70d525 100644 --- a/src/pages/home/report/ReportActionItemFragment.tsx +++ b/src/pages/home/report/ReportActionItemFragment.tsx @@ -106,7 +106,7 @@ function ReportActionItemFragment({ // immediately display "[Deleted message]" while the delete action is pending. if ((!isOffline && isThreadParentMessage && isPendingDelete) || fragment?.isDeletedParentAction) { - return `} />; + return `} />; } if (isThreadParentMessage && moderationDecision === CONST.MODERATION.MODERATOR_DECISION_PENDING_REMOVE) { diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index 7c42991d2852..fd50141b0e36 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -248,6 +248,8 @@ function ReportActionItemSingle({ const statusText = status?.text ?? ''; const statusTooltipText = formattedDate ? `${statusText ? `${statusText} ` : ''}(${formattedDate})` : statusText; + console.log(action); + return ( { - render(); - return waitForBatchedUpdatesWithAct() - .then(async () => { - await waitForBatchedUpdatesWithAct(); - const hintText = Localize.translateLocal('loginForm.loginForm'); - const loginForm = screen.queryAllByLabelText(hintText); - expect(loginForm).toHaveLength(1); - - // Given three unread reports in the recently updated order of 3, 2, 1 - const report1 = LHNTestUtils.getFakeReport([1, 2], 3); - const report2 = LHNTestUtils.getFakeReport([1, 3], 2); - const report3 = LHNTestUtils.getFakeReport([1, 4], 1); - - const policy1 = LHNTestUtils.getFakePolicy('1', 'Workspace A'); - const policy2 = LHNTestUtils.getFakePolicy('2', 'B'); - const policy3 = LHNTestUtils.getFakePolicy('3', 'C'); - - // Each report has at least one ADD_COMMENT action so should be rendered in the LNH - Report.addComment(report1.reportID, 'Hi, this is a comment'); - Report.addComment(report2.reportID, 'Hi, this is a comment'); - Report.addComment(report3.reportID, 'Hi, this is a comment'); - - const reportCollectionDataSet: ReportCollectionDataSet = { - [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, - [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, - [`${ONYXKEYS.COLLECTION.REPORT}${report3.reportID}`]: report3, - }; - - const policyCollectionDataSet: PolicyCollectionDataSet = { - [`${ONYXKEYS.COLLECTION.POLICY}${policy1.id}`]: policy1, - [`${ONYXKEYS.COLLECTION.POLICY}${policy2.id}`]: policy2, - [`${ONYXKEYS.COLLECTION.POLICY}${policy3.id}`]: policy3, - }; - - await act(async () => { - await TestHelper.signInWithTestUser(USER_A_ACCOUNT_ID, USER_A_EMAIL, undefined, undefined, 'A'); - }); - await Onyx.multiSet({ - [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, - [ONYXKEYS.IS_LOADING_APP]: false, - ...reportCollectionDataSet, - ...policyCollectionDataSet, - }); - await waitForBatchedUpdates(); - return navigateToSetting(); - }) - .then(async () => { - await act(() => (NativeNavigation as NativeNavigationMock).triggerTransitionEnd()); - return navigateToExpensifyClassicFlow(); - }); -} - -describe('Switch to Expensify Classic flow', () => { - beforeEach(() => { - jest.clearAllMocks(); - Onyx.clear(); - - // Unsubscribe to pusher channels - PusherHelper.teardown(); - }); - - test('Should navigate to BookACall when dismissed is false', () => { - signInAppAndEnterTestFlow(false).then(() => { - expect(screen.getAllByText(Localize.translateLocal('exitSurvey.bookACallTitle')).at(0)).toBeOnTheScreen(); - }); - }); - - test('Should navigate to ConfirmPage when dismissed is true', () => { - signInAppAndEnterTestFlow(true).then(() => { - expect(screen.getAllByText(Localize.translateLocal('exitSurvey.goToExpensifyClassic')).at(0)).toBeOnTheScreen(); - }); - }); -}); From ea91789c6d763b87f08a50db6ad45e95cd1674bc Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 6 Jan 2025 18:02:25 +0530 Subject: [PATCH 024/179] update deleted message fragment for iou requests. Signed-off-by: krishna2323 --- .../ReportActionItem/MoneyRequestAction.tsx | 11 ++++------- src/components/ReportActionItem/TaskPreview.tsx | 1 - src/pages/home/report/PureReportActionItem.tsx | 2 +- .../home/report/ReportActionItemContentCreated.tsx | 13 ++++--------- 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index e17c30e8bddb..2a6f72506b9d 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -3,7 +3,6 @@ import type {StyleProp, ViewStyle} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import RenderHTML from '@components/RenderHTML'; -import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import * as IOUUtils from '@libs/IOUUtils'; @@ -11,7 +10,6 @@ import Navigation from '@libs/Navigation/Navigation'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; -import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; @@ -85,7 +83,6 @@ function MoneyRequestAction({ shouldDisplayContextMenu = true, }: MoneyRequestActionProps) { const styles = useThemeStyles(); - const {translate} = useLocalize(); const {isOffline} = useNetwork(); const isSplitBillAction = ReportActionsUtils.isSplitBillAction(action); const isTrackExpenseAction = ReportActionsUtils.isTrackExpenseAction(action); @@ -116,13 +113,13 @@ function MoneyRequestAction({ } if (isDeletedParentAction || isReversedTransaction) { - let message: TranslationPaths; + let deleteActionType; if (isReversedTransaction) { - message = 'parentReportAction.reversedTransaction'; + deleteActionType = CONST.REPORT.DELETED_ACTION_TYPE.REVERSED_TRANSACTION; } else { - message = 'parentReportAction.deletedExpense'; + deleteActionType = CONST.REPORT.DELETED_ACTION_TYPE.DELETED_EXPENSE; } - return ${translate(message)}`} />; + return `} />; } return ( ${translate('parentReportAction.deletedTask')}`} />; return `} />; } diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 5dbd399e2737..d9650161bae7 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -718,7 +718,7 @@ function PureReportActionItem({ ); } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) { children = isClosedExpenseReportWithNoExpenses ? ( - ${translate('parentReportAction.deletedReport')}`} /> + `} /> ) : ( - ${translate(message)}`} /> + `} /> From 4ac5653df435a539f7eb5068a27b186a0286bff7 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 6 Jan 2025 18:11:31 +0530 Subject: [PATCH 025/179] remove console log. Signed-off-by: krishna2323 --- src/pages/home/report/ReportActionItemSingle.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index fd50141b0e36..7c42991d2852 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -248,8 +248,6 @@ function ReportActionItemSingle({ const statusText = status?.text ?? ''; const statusTooltipText = formattedDate ? `${statusText ? `${statusText} ` : ''}(${formattedDate})` : statusText; - console.log(action); - return ( Date: Mon, 6 Jan 2025 18:31:56 +0530 Subject: [PATCH 026/179] use variables size. Signed-off-by: krishna2323 --- .../HTMLRenderers/DeletedActionRenderer.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx index 546ea5c6ca5a..158d464db7e5 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx @@ -7,6 +7,7 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import variables from '@styles/variables'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; @@ -24,22 +25,21 @@ function DeletedActionRenderer({tnode}: CustomRendererProps) } const getIcon = () => { - // This needs to be updated with new icons switch (action) { case CONST.REPORT.DELETED_ACTION_TYPE.DELETED_MESSAGE: - return {icon: Expensicons.ChatBubbleSlash, width: 18, height: 18}; + return {icon: Expensicons.ChatBubbleSlash}; case CONST.REPORT.DELETED_ACTION_TYPE.DELETED_EXPENSE: - return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; + return {icon: Expensicons.ReceiptSlash}; case CONST.REPORT.DELETED_ACTION_TYPE.DELETED_REPORT: - return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; + return {icon: Expensicons.ReceiptSlash}; case CONST.REPORT.DELETED_ACTION_TYPE.DELETED_TASK: - return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; + return {icon: Expensicons.ReceiptSlash}; case CONST.REPORT.DELETED_ACTION_TYPE.HIDDEN_MESSAGE: - return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; + return {icon: Expensicons.ReceiptSlash}; case CONST.REPORT.DELETED_ACTION_TYPE.REVERSED_TRANSACTION: - return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; + return {icon: Expensicons.ReceiptSlash}; default: - return {icon: Expensicons.ReceiptSlash, width: 18, height: 18}; + return {icon: Expensicons.ReceiptSlash}; } }; @@ -47,8 +47,8 @@ function DeletedActionRenderer({tnode}: CustomRendererProps) return ( From a121cbcd61da68b7003c6bf76a298c764ba53ffc Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 6 Jan 2025 18:33:43 +0530 Subject: [PATCH 027/179] use delete action renderer for hidden messages also. Signed-off-by: krishna2323 --- src/pages/home/report/ReportActionItemFragment.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionItemFragment.tsx b/src/pages/home/report/ReportActionItemFragment.tsx index f6e75d70d525..c99221386199 100644 --- a/src/pages/home/report/ReportActionItemFragment.tsx +++ b/src/pages/home/report/ReportActionItemFragment.tsx @@ -2,7 +2,6 @@ import React, {memo} from 'react'; import type {StyleProp, TextStyle} from 'react-native'; import RenderHTML from '@components/RenderHTML'; import Text from '@components/Text'; -import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import convertToLTR from '@libs/convertToLTR'; @@ -95,7 +94,6 @@ function ReportActionItemFragment({ }: ReportActionItemFragmentProps) { const styles = useThemeStyles(); const {isOffline} = useNetwork(); - const {translate} = useLocalize(); switch (fragment?.type) { case 'COMMENT': { @@ -110,7 +108,7 @@ function ReportActionItemFragment({ } if (isThreadParentMessage && moderationDecision === CONST.MODERATION.MODERATOR_DECISION_PENDING_REMOVE) { - return ${translate('parentReportAction.hiddenMessage')}`} />; + return `} />; } if (ReportUtils.isReportMessageAttachment(fragment)) { From 1c557624e55602ae84ed398cd08f84383d206c0e Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 9 Jan 2025 16:38:10 +0700 Subject: [PATCH 028/179] fix lint error --- src/components/LoadingBar.tsx | 5 ++++- src/components/MoneyReportHeader.tsx | 12 +++++++++--- src/components/MoneyRequestHeader.tsx | 12 +++++++++--- .../createCustomBottomTabNavigator/TopBar.tsx | 2 +- src/pages/home/HeaderView.tsx | 7 +++++-- src/pages/home/ReportScreen.tsx | 3 --- .../home/sidebar/SidebarScreen/BaseSidebarScreen.tsx | 3 --- 7 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index e6d1ec0cd66d..85aa43bc5f06 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -6,9 +6,11 @@ import CONST from '@src/CONST'; type LoadingBarProps = { // Whether or not to show the loading bar shouldShow: boolean; + + height?: number; }; -function LoadingBar({shouldShow}: LoadingBarProps) { +function LoadingBar({shouldShow, height = 2}: LoadingBarProps) { const left = useSharedValue(0); const width = useSharedValue(0); const opacity = useSharedValue(0); @@ -69,6 +71,7 @@ function LoadingBar({shouldShow}: LoadingBarProps) { const animatedContainerStyle = useAnimatedStyle(() => ({ opacity: opacity.get(), + height, })); return ( diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 36310e4eb588..bd29de3ff58a 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -35,6 +35,7 @@ import DelegateNoAccessModal from './DelegateNoAccessModal'; import HeaderWithBackButton from './HeaderWithBackButton'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; +import LoadingBar from './LoadingBar'; import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar'; import type {MoneyRequestHeaderStatusBarProps} from './MoneyRequestHeaderStatusBar'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; @@ -177,6 +178,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const isMoreContentShown = shouldShowNextStep || shouldShowStatusBar || (shouldShowAnyButton && shouldUseNarrowLayout); const {isDelegateAccessRestricted} = useDelegateUserDetails(); const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); + const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA); const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP; const shouldDisplaySearchRouter = !isReportInRHP || isSmallScreenWidth; @@ -334,7 +336,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea }, [canDeleteRequest]); return ( - + {isDuplicate && !shouldUseNarrowLayout && ( @@ -414,7 +416,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea )} {!!isMoreContentShown && ( - + {isDuplicate && shouldUseNarrowLayout && ( diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx index d1dcdb2f57f5..1cce6bffe467 100644 --- a/src/components/ReportActionItem/MoneyReportView.tsx +++ b/src/components/ReportActionItem/MoneyReportView.tsx @@ -29,7 +29,7 @@ import type {PendingAction} from '@src/types/onyx/OnyxCommon'; type MoneyReportViewProps = { /** The report currently being looked at */ - report: Report; + report: OnyxEntry; /** Policy that the report belongs to */ policy: OnyxEntry; @@ -52,15 +52,15 @@ function MoneyReportView({report, policy, isCombinedReport = false, shouldShowTo const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const {isOffline} = useNetwork(); - const isSettled = ReportUtils.isSettled(report.reportID); + const isSettled = ReportUtils.isSettled(report?.reportID); const isTotalUpdated = ReportUtils.hasUpdatedTotal(report, policy); const {totalDisplaySpend, nonReimbursableSpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(report); const shouldShowBreakdown = nonReimbursableSpend && reimbursableSpend && shouldShowTotal; - const formattedTotalAmount = CurrencyUtils.convertToDisplayString(totalDisplaySpend, report.currency); - const formattedOutOfPocketAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, report.currency); - const formattedCompanySpendAmount = CurrencyUtils.convertToDisplayString(nonReimbursableSpend, report.currency); + const formattedTotalAmount = CurrencyUtils.convertToDisplayString(totalDisplaySpend, report?.currency); + const formattedOutOfPocketAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, report?.currency); + const formattedCompanySpendAmount = CurrencyUtils.convertToDisplayString(nonReimbursableSpend, report?.currency); const isPartiallyPaid = !!report?.pendingFields?.partial; const subAmountTextStyles: StyleProp = [ @@ -70,11 +70,11 @@ function MoneyReportView({report, policy, isCombinedReport = false, shouldShowTo StyleUtils.getColorStyle(theme.textSupporting), ]; - const [violations] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_VIOLATIONS}${report.reportID}`); + const [violations] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_VIOLATIONS}${report?.reportID}`); const sortedPolicyReportFields = useMemo((): PolicyReportField[] => { const fields = ReportUtils.getAvailableReportFields(report, Object.values(policy?.fieldList ?? {})); - return fields.filter((field) => field.target === report.type).sort(({orderWeight: firstOrderWeight}, {orderWeight: secondOrderWeight}) => firstOrderWeight - secondOrderWeight); + return fields.filter((field) => field.target === report?.type).sort(({orderWeight: firstOrderWeight}, {orderWeight: secondOrderWeight}) => firstOrderWeight - secondOrderWeight); }, [policy, report]); const enabledReportFields = sortedPolicyReportFields.filter((reportField) => !ReportUtils.isReportFieldDisabled(report, reportField, policy)); @@ -88,7 +88,7 @@ function MoneyReportView({report, policy, isCombinedReport = false, shouldShowTo () => shouldHideThreadDividerLine && !isCombinedReport ? ( ) : ( @@ -97,7 +97,7 @@ function MoneyReportView({report, policy, isCombinedReport = false, shouldShowTo style={[!shouldHideThreadDividerLine ? styles.reportHorizontalRule : {}]} /> ), - [shouldHideThreadDividerLine, report.reportID, styles.reportHorizontalRule, isCombinedReport], + [shouldHideThreadDividerLine, report?.reportID, styles.reportHorizontalRule, isCombinedReport], ); return ( @@ -124,25 +124,29 @@ function MoneyReportView({report, policy, isCombinedReport = false, shouldShowTo return ( reportActions.clearReportFieldKeyErrors(report.reportID, fieldKey)} + onClose={() => reportActions.clearReportFieldKeyErrors(report?.reportID, fieldKey)} > + onPress={() => { + if (!report?.reportID) { + return; + } + Navigation.navigate( ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute( - report.reportID, - report.policyID ?? '-1', + report?.reportID, + report?.policyID ?? '-1', reportField.fieldID, Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} shouldShowRightIcon disabled={isFieldDisabled} wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index f1d5bcde0764..80b25711b660 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -1,14 +1,18 @@ import React from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import RenderHTML from '@components/RenderHTML'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as IOUUtils from '@libs/IOUUtils'; +import {isIOUReportPendingCurrencyConversion} from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; +import { + isDeletedParentAction as isDeletedParentActionReportActionsUtils, + isReversedTransaction as isReversedTransactionReportActionsUtils, + isSplitBillAction as isSplitBillActionReportActionsUtils, + isTrackExpenseAction as isTrackExpenseActionReportActionsUtils, +} from '@libs/ReportActionsUtils'; import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; @@ -18,18 +22,7 @@ import type * as OnyxTypes from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import MoneyRequestPreview from './MoneyRequestPreview'; -type MoneyRequestActionOnyxProps = { - /** Chat report associated with iouReport */ - chatReport: OnyxEntry; - - /** IOU report data object */ - iouReport: OnyxEntry; - - /** Report actions for this report */ - reportActions: OnyxEntry; -}; - -type MoneyRequestActionProps = MoneyRequestActionOnyxProps & { +type MoneyRequestActionProps = { /** All the data of the action */ action: OnyxTypes.ReportAction; @@ -72,34 +65,38 @@ function MoneyRequestAction({ isMostRecentIOUReportAction, contextMenuAnchor, checkIfContextMenuActive = () => {}, - chatReport, - iouReport, - reportActions, isHovered = false, style, isWhisper = false, shouldDisplayContextMenu = true, }: MoneyRequestActionProps) { + const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`); + const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${requestReportID}`); + const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, {canEvict: false}); + const styles = useThemeStyles(); const {translate} = useLocalize(); const {isOffline} = useNetwork(); - const isSplitBillAction = ReportActionsUtils.isSplitBillAction(action); - const isTrackExpenseAction = ReportActionsUtils.isTrackExpenseAction(action); + const isSplitBillAction = isSplitBillActionReportActionsUtils(action); + const isTrackExpenseAction = isTrackExpenseActionReportActionsUtils(action); const onMoneyRequestPreviewPressed = () => { if (isSplitBillAction) { - const reportActionID = action.reportActionID ?? '-1'; + if (!chatReportID) { + return; + } + const reportActionID = action.reportActionID; Navigation.navigate(ROUTES.SPLIT_BILL_DETAILS.getRoute(chatReportID, reportActionID, Navigation.getReportRHPActiveRoute())); return; } - const childReportID = action?.childReportID ?? '-1'; + const childReportID = action?.childReportID; Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(childReportID)); }; let shouldShowPendingConversionMessage = false; - const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(action); - const isReversedTransaction = ReportActionsUtils.isReversedTransaction(action); + const isDeletedParentAction = isDeletedParentActionReportActionsUtils(action); + const isReversedTransaction = isReversedTransactionReportActionsUtils(action); if ( !isEmptyObject(iouReport) && !isEmptyObject(reportActions) && @@ -108,7 +105,7 @@ function MoneyRequestAction({ action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD && isOffline ) { - shouldShowPendingConversionMessage = IOUUtils.isIOUReportPendingCurrencyConversion(iouReport); + shouldShowPendingConversionMessage = isIOUReportPendingCurrencyConversion(iouReport); } if (isDeletedParentAction || isReversedTransaction) { @@ -142,15 +139,4 @@ function MoneyRequestAction({ MoneyRequestAction.displayName = 'MoneyRequestAction'; -export default withOnyx({ - chatReport: { - key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, - }, - iouReport: { - key: ({requestReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${requestReportID}`, - }, - reportActions: { - key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, - canEvict: false, - }, -})(MoneyRequestAction); +export default MoneyRequestAction; diff --git a/src/components/ReportActionItem/MoneyRequestPreview/types.ts b/src/components/ReportActionItem/MoneyRequestPreview/types.ts index c40b45c6d2bd..186c81a8c866 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/types.ts +++ b/src/components/ReportActionItem/MoneyRequestPreview/types.ts @@ -7,13 +7,13 @@ type MoneyRequestPreviewProps = { /** The active IOUReport, used for Onyx subscription */ // The iouReportID is used inside withOnyx HOC // eslint-disable-next-line react/no-unused-prop-types - iouReportID: string; + iouReportID: string | undefined; /** The associated chatReport */ - chatReportID: string; + chatReportID: string | undefined; /** The ID of the current report */ - reportID: string; + reportID: string | undefined; /** Callback for the preview pressed */ onPreviewPressed: (event?: GestureResponderEvent | KeyboardEvent) => void; diff --git a/src/components/ReportActionItem/TaskPreview.tsx b/src/components/ReportActionItem/TaskPreview.tsx index 1ea5f8206479..41e8dc5c0b40 100644 --- a/src/components/ReportActionItem/TaskPreview.tsx +++ b/src/components/ReportActionItem/TaskPreview.tsx @@ -21,14 +21,14 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import ControlSelection from '@libs/ControlSelection'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import getButtonState from '@libs/getButtonState'; import Navigation from '@libs/Navigation/Navigation'; -import * as ReportUtils from '@libs/ReportUtils'; -import * as TaskUtils from '@libs/TaskUtils'; +import {isCanceledTaskReport, isOpenTaskReport, isReportManager} from '@libs/ReportUtils'; +import {getTaskTitleFromReport} from '@libs/TaskUtils'; import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; -import * as Session from '@userActions/Session'; -import * as Task from '@userActions/Task'; +import {checkIfActionIsAllowed} from '@userActions/Session'; +import {canActionTask, completeTask, getTaskAssigneeAccountID, reopenTask} from '@userActions/Task'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -74,18 +74,18 @@ function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, che const isTaskCompleted = !isEmptyObject(taskReport) ? 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.getTaskTitleFromReport(taskReport, action?.childReportName ?? '')); - const taskAssigneeAccountID = Task.getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? CONST.DEFAULT_NUMBER_ID; + const taskTitle = Str.htmlEncode(getTaskTitleFromReport(taskReport, action?.childReportName ?? '')); + const taskAssigneeAccountID = getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? CONST.DEFAULT_NUMBER_ID; const taskOwnerAccountID = taskReport?.ownerAccountID ?? action?.actorAccountID ?? CONST.DEFAULT_NUMBER_ID; const hasAssignee = taskAssigneeAccountID > 0; const personalDetails = usePersonalDetails(); const avatar = personalDetails?.[taskAssigneeAccountID]?.avatar ?? Expensicons.FallbackAvatar; const avatarSize = CONST.AVATAR_SIZE.SMALL; - const isDeletedParentAction = ReportUtils.isCanceledTaskReport(taskReport, action); + const isDeletedParentAction = isCanceledTaskReport(taskReport, action); const iconWrapperStyle = StyleUtils.getTaskPreviewIconWrapper(hasAssignee ? avatarSize : undefined); const titleStyle = StyleUtils.getTaskPreviewTitleStyle(iconWrapperStyle.height, isTaskCompleted); - const shouldShowGreenDotIndicator = ReportUtils.isOpenTaskReport(taskReport, action) && ReportUtils.isReportManager(taskReport); + const shouldShowGreenDotIndicator = isOpenTaskReport(taskReport, action) && isReportManager(taskReport); if (isDeletedParentAction) { return ${translate('parentReportAction.deletedTask')}`} />; } @@ -94,7 +94,7 @@ function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, che Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(taskReportID))} - onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} + onPressIn={() => canUseTouchScreen() && ControlSelection.block()} onPressOut={() => ControlSelection.unblock()} onLongPress={(event) => showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive)} shouldUseHapticsOnLongPress @@ -107,12 +107,12 @@ function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, che { + disabled={!canActionTask(taskReport, currentUserPersonalDetails.accountID, taskOwnerAccountID, taskAssigneeAccountID)} + onPress={checkIfActionIsAllowed(() => { if (isTaskCompleted) { - Task.reopenTask(taskReport, taskReportID); + reopenTask(taskReport, taskReportID); } else { - Task.completeTask(taskReport, taskReportID); + completeTask(taskReport, taskReportID); } })} accessibilityLabel={translate('task.task')} diff --git a/src/components/ReportActionItem/TaskView.tsx b/src/components/ReportActionItem/TaskView.tsx index 7901426b33e0..df56b27b2cf4 100644 --- a/src/components/ReportActionItem/TaskView.tsx +++ b/src/components/ReportActionItem/TaskView.tsx @@ -1,5 +1,6 @@ import React, {useEffect} from 'react'; import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import Checkbox from '@components/Checkbox'; import Hoverable from '@components/Hoverable'; import Icon from '@components/Icon'; @@ -28,7 +29,7 @@ import type {Report} from '@src/types/onyx'; type TaskViewProps = { /** The report currently being looked at */ - report: Report; + report: OnyxEntry; }; function TaskView({report}: TaskViewProps) { @@ -39,9 +40,9 @@ function TaskView({report}: TaskViewProps) { useEffect(() => { Task.setTaskReport(report); }, [report]); - const taskTitle = convertToLTR(report.reportName ?? ''); + const taskTitle = convertToLTR(report?.reportName ?? ''); const assigneeTooltipDetails = ReportUtils.getDisplayNamesWithTooltips( - OptionsListUtils.getPersonalDetailsForAccountIDs(report.managerID ? [report.managerID] : [], personalDetails), + OptionsListUtils.getPersonalDetailsForAccountIDs(report?.managerID ? [report?.managerID] : [], personalDetails), false, ); const isOpen = ReportUtils.isOpenTaskReport(report); @@ -56,22 +57,22 @@ function TaskView({report}: TaskViewProps) { Task.clearTaskErrors(report.reportID)} + errors={report?.errorFields?.editTask ?? report?.errorFields?.createTask} + onClose={() => Task.clearTaskErrors(report?.reportID)} errorRowStyles={styles.ph5} > {(hovered) => ( { - if (isDisableInteractive) { + if (isDisableInteractive || !report?.reportID) { return; } if (e && e.type === 'click') { (e.currentTarget as HTMLElement).blur(); } - Navigation.navigate(ROUTES.TASK_TITLE.getRoute(report.reportID, Navigation.getReportRHPActiveRoute())); + Navigation.navigate(ROUTES.TASK_TITLE.getRoute(report?.reportID, Navigation.getReportRHPActiveRoute())); })} style={({pressed}) => [ styles.ph5, @@ -83,13 +84,13 @@ function TaskView({report}: TaskViewProps) { disabled={isDisableInteractive} > {({pressed}) => ( - + {translate('task.title')} { // If we're already navigating to these task editing pages, early return not to mark as completed, otherwise we would have not found page. - if (TaskUtils.isActiveTaskEditRoute(report.reportID)) { + if (TaskUtils.isActiveTaskEditRoute(report?.reportID)) { return; } if (isCompleted) { @@ -129,12 +130,12 @@ function TaskView({report}: TaskViewProps) { )} - + Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report.reportID, Navigation.getReportRHPActiveRoute()))} + title={report?.description ?? ''} + onPress={() => Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report?.reportID, Navigation.getReportRHPActiveRoute()))} shouldShowRightIcon={!isDisableInteractive} disabled={disableState} wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} @@ -144,16 +145,16 @@ function TaskView({report}: TaskViewProps) { shouldUseDefaultCursorWhenDisabled /> - - {report.managerID ? ( + + {report?.managerID ? ( Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report.reportID, Navigation.getReportRHPActiveRoute()))} + onPress={() => Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report?.reportID, Navigation.getReportRHPActiveRoute()))} shouldShowRightIcon={!isDisableInteractive} disabled={disableState} wrapperStyle={[styles.pv2]} @@ -166,7 +167,7 @@ function TaskView({report}: TaskViewProps) { ) : ( Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report.reportID, Navigation.getReportRHPActiveRoute()))} + onPress={() => Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report?.reportID, Navigation.getReportRHPActiveRoute()))} shouldShowRightIcon={!isDisableInteractive} disabled={disableState} wrapperStyle={[styles.pv2]} diff --git a/src/components/ReportActionItem/TripRoomPreview.tsx b/src/components/ReportActionItem/TripRoomPreview.tsx index 65dcac0845e6..39a7d3fa8028 100644 --- a/src/components/ReportActionItem/TripRoomPreview.tsx +++ b/src/components/ReportActionItem/TripRoomPreview.tsx @@ -14,12 +14,13 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import ControlSelection from '@libs/ControlSelection'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; +import {convertToDisplayString} from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; -import * as ReportUtils from '@libs/ReportUtils'; -import * as TripReservationUtils from '@libs/TripReservationUtils'; +import {getMoneyRequestSpendBreakdown, getTripTransactions} from '@libs/ReportUtils'; +import type {ReservationData} from '@libs/TripReservationUtils'; +import {getReservationsFromTripTransactions, getTripReservationIcon} from '@libs/TripReservationUtils'; import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import variables from '@styles/variables'; import * as Expensicons from '@src/components/Icon/Expensicons'; @@ -59,7 +60,7 @@ function ReservationView({reservation}: ReservationViewProps) { const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); - const reservationIcon = TripReservationUtils.getTripReservationIcon(reservation.type); + const reservationIcon = getTripReservationIcon(reservation.type); const title = reservation.type === CONST.RESERVATION_TYPE.CAR ? reservation.carInfo?.name : reservation.start.longName; let titleComponent = ( @@ -109,7 +110,7 @@ function ReservationView({reservation}: ReservationViewProps) { ); } -const renderItem = ({item}: {item: TripReservationUtils.ReservationData}) => ; +const renderItem = ({item}: {item: ReservationData}) => ; function TripRoomPreview({action, chatReportID, containerStyles, contextMenuAnchor, isHovered = false, checkIfContextMenuActive = () => {}}: TripRoomPreviewProps) { const styles = useThemeStyles(); @@ -117,18 +118,18 @@ function TripRoomPreview({action, chatReportID, containerStyles, contextMenuAnch const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`); const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReport?.iouReportID}`); - const tripTransactions = ReportUtils.getTripTransactions(chatReport?.reportID); - const reservationsData: TripReservationUtils.ReservationData[] = TripReservationUtils.getReservationsFromTripTransactions(tripTransactions); + const tripTransactions = getTripTransactions(chatReport?.reportID); + const reservationsData: ReservationData[] = getReservationsFromTripTransactions(tripTransactions); const dateInfo = chatReport?.tripData ? DateUtils.getFormattedDateRange(new Date(chatReport.tripData.startDate), new Date(chatReport.tripData.endDate)) : ''; - const {totalDisplaySpend} = ReportUtils.getMoneyRequestSpendBreakdown(chatReport); + const {totalDisplaySpend} = getMoneyRequestSpendBreakdown(chatReport); const currency = iouReport?.currency ?? chatReport?.currency; const displayAmount = useMemo(() => { if (totalDisplaySpend) { - return CurrencyUtils.convertToDisplayString(totalDisplaySpend, currency); + return convertToDisplayString(totalDisplaySpend, currency); } - return CurrencyUtils.convertToDisplayString( + return convertToDisplayString( tripTransactions.reduce((acc, transaction) => acc + Math.abs(transaction.amount), 0), currency, ); @@ -142,7 +143,7 @@ function TripRoomPreview({action, chatReportID, containerStyles, contextMenuAnch > DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} + onPressIn={() => canUseTouchScreen() && ControlSelection.block()} onPressOut={() => ControlSelection.unblock()} onLongPress={(event) => showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive)} shouldUseHapticsOnLongPress diff --git a/src/components/UnreadActionIndicator.tsx b/src/components/UnreadActionIndicator.tsx index c63079e69853..f99efda0c2b6 100755 --- a/src/components/UnreadActionIndicator.tsx +++ b/src/components/UnreadActionIndicator.tsx @@ -7,7 +7,7 @@ import Text from './Text'; type UnreadActionIndicatorProps = { /** The ID of the report action */ - reportActionID: string; + reportActionID: string | undefined; /** Whether we should hide thread divider line */ shouldHideThreadDividerLine?: boolean; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 674708fb4f9f..cf6551aead00 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3202,10 +3202,10 @@ function getReportFieldsByPolicyID(policyID: string | undefined): Record, policyReportFields: PolicyReportField[]): PolicyReportField[] { // Get the report fields that are attached to a report. These will persist even if a field is deleted from the policy. - const reportFields = Object.values(report.fieldList ?? {}); - const reportIsSettled = isSettled(report.reportID); + const reportFields = Object.values(report?.fieldList ?? {}); + const reportIsSettled = isSettled(report?.reportID); // If the report is settled, we don't want to show any new field that gets added to the policy. if (reportIsSettled) { diff --git a/src/libs/TaskUtils.ts b/src/libs/TaskUtils.ts index 975c3e617ce5..1e6ace5e5e0b 100644 --- a/src/libs/TaskUtils.ts +++ b/src/libs/TaskUtils.ts @@ -22,7 +22,11 @@ Onyx.connect({ /** * Check if the active route belongs to task edit flow. */ -function isActiveTaskEditRoute(reportID: string): boolean { +function isActiveTaskEditRoute(reportID: string | undefined): boolean { + if (!reportID) { + return false; + } + return [ROUTES.TASK_TITLE, ROUTES.TASK_ASSIGNEE, ROUTES.REPORT_DESCRIPTION].map((route) => route.getRoute(reportID)).some(Navigation.isActiveRoute); } diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index b61e36a413bb..0d776a104bc6 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2068,7 +2068,7 @@ function updateReportName(reportID: string, value: string, previousValue: string API.write(WRITE_COMMANDS.SET_REPORT_NAME, parameters, {optimisticData, failureData, successData}); } -function clearReportFieldKeyErrors(reportID: string, fieldKey: string) { +function clearReportFieldKeyErrors(reportID: string | undefined, fieldKey: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { pendingFields: { [fieldKey]: null, @@ -2486,13 +2486,18 @@ function addPolicyReport(policyReport: OptimisticChatReport) { } /** Deletes a report, along with its reportActions, any linked reports, and any linked IOU report. */ -function deleteReport(reportID: string, shouldDeleteChildReports = false) { +function deleteReport(reportID: string | undefined, shouldDeleteChildReports = false) { const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; const onyxData: Record = { [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: null, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: null, }; + if (!reportID) { + Log.warn('[Report] deleteReport called with no reportID'); + return; + } + // Delete linked transactions const reportActionsForReport = allReportActions?.[reportID]; @@ -2524,7 +2529,7 @@ function deleteReport(reportID: string, shouldDeleteChildReports = false) { /** * @param reportID The reportID of the policy report (workspace room) */ -function navigateToConciergeChatAndDeleteReport(reportID: string, shouldPopToTop = false, shouldDeleteChildReports = false) { +function navigateToConciergeChatAndDeleteReport(reportID: string | undefined, shouldPopToTop = false, shouldDeleteChildReports = false) { // Dismiss the current report screen and replace it with Concierge Chat if (shouldPopToTop) { Navigation.setShouldPopAllStateOnUP(true); @@ -2731,7 +2736,7 @@ function showReportActionNotification(reportID: string, reportAction: ReportActi } /** Clear the errors associated with the IOUs of a given report. */ -function clearIOUError(reportID: string) { +function clearIOUError(reportID: string | undefined) { Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {errorFields: {iou: null}}); } @@ -4312,9 +4317,13 @@ function clearNewRoomFormError() { }); } -function resolveActionableMentionWhisper(reportId: string, reportAction: OnyxEntry, resolution: ValueOf) { +function resolveActionableMentionWhisper( + reportId: string | undefined, + reportAction: OnyxEntry, + resolution: ValueOf, +) { const message = ReportActionsUtils.getReportActionMessage(reportAction); - if (!message || !reportAction) { + if (!message || !reportAction || !reportId) { return; } @@ -4389,11 +4398,11 @@ function resolveActionableMentionWhisper(reportId: string, reportAction: OnyxEnt } function resolveActionableReportMentionWhisper( - reportId: string, + reportId: string | undefined, reportAction: OnyxEntry, resolution: ValueOf, ) { - if (!reportAction) { + if (!reportAction || !reportId) { return; } @@ -4460,10 +4469,10 @@ function resolveActionableReportMentionWhisper( API.write(WRITE_COMMANDS.RESOLVE_ACTIONABLE_REPORT_MENTION_WHISPER, parameters, {optimisticData, failureData}); } -function dismissTrackExpenseActionableWhisper(reportID: string, reportAction: OnyxEntry): void { +function dismissTrackExpenseActionableWhisper(reportID: string | undefined, reportAction: OnyxEntry): void { const isArrayMessage = Array.isArray(reportAction?.message); const message = ReportActionsUtils.getReportActionMessage(reportAction); - if (!message || !reportAction) { + if (!message || !reportAction || !reportID) { return; } diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 89517a753c26..a1c3c676f46e 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -81,9 +81,9 @@ function clearReportActionErrors(reportID: string, reportAction: ReportAction, k ignore: `undefined` means we want to check both parent and children report actions ignore: `parent` or `child` means we want to ignore checking parent or child report actions because they've been previously checked */ -function clearAllRelatedReportActionErrors(reportID: string, reportAction: ReportAction | null | undefined, ignore?: IgnoreDirection, keys?: string[]) { +function clearAllRelatedReportActionErrors(reportID: string | undefined, reportAction: ReportAction | null | undefined, ignore?: IgnoreDirection, keys?: string[]) { const errorKeys = keys ?? Object.keys(reportAction?.errors ?? {}); - if (!reportAction || errorKeys.length === 0) { + if (!reportAction || errorKeys.length === 0 || !reportID) { return; } diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 2a13ff69b769..e9531ed80979 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -1251,7 +1251,7 @@ function canActionTask(taskReport: OnyxEntry, sessionAccountID return sessionAccountID === ownerAccountID || sessionAccountID === assigneeAccountID; } -function clearTaskErrors(reportID: string) { +function clearTaskErrors(reportID: string | undefined) { const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; // Delete the task preview in the parent report diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx index 0d008eae668d..0773aea82fb7 100755 --- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx @@ -18,11 +18,19 @@ import usePaginatedReportActions from '@hooks/usePaginatedReportActions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useRestoreInputFocus from '@hooks/useRestoreInputFocus'; import useStyleUtils from '@hooks/useStyleUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getPolicy, getWorkspaceAccountID, isPolicyAdmin as isPolicyAdminPolicyUtils} from '@libs/PolicyUtils'; +import {getLinkedTransactionID, getOneTransactionThreadReportID, getOriginalMessage, getReportAction, isActionOfType} from '@libs/ReportActionsUtils'; +import { + chatIncludesChronosWithID, + getSourceIDFromReportAction, + isArchivedNonExpenseReport, + isArchivedNonExpenseReportWithID, + isInvoiceReport as isInvoiceReportUtils, + isMoneyRequestReport as isMoneyRequestReportUtils, + isTrackExpenseReport as isTrackExpenseReportUtils, +} from '@libs/ReportUtils'; import shouldEnableContextMenuEnterShortcut from '@libs/shouldEnableContextMenuEnterShortcut'; -import * as Session from '@userActions/Session'; +import {isAnonymousUser, signOutAndRedirectToSignIn} from '@userActions/Session'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ReportAction} from '@src/types/onyx'; @@ -132,12 +140,12 @@ function BaseReportActionContextMenu({ const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${originalReportID}`, { canEvict: false, }); - const transactionID = ReportActionsUtils.getLinkedTransactionID(reportActionID, reportID); + const transactionID = getLinkedTransactionID(reportActionID, reportID); const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`); const [user] = useOnyx(ONYXKEYS.USER); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); const policyID = report?.policyID; - const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policyID); + const workspaceAccountID = getWorkspaceAccountID(policyID); const [cardList = {}] = useOnyx(ONYXKEYS.CARD_LIST); const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`); @@ -148,23 +156,23 @@ function BaseReportActionContextMenu({ return reportActions[reportActionID]; }, [reportActions, reportActionID]); - const sourceID = ReportUtils.getSourceIDFromReportAction(reportAction); + const sourceID = getSourceIDFromReportAction(reportAction); const [download] = useOnyx(`${ONYXKEYS.COLLECTION.DOWNLOAD}${sourceID}`); const [childReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportAction?.childReportID}`); - const parentReportAction = ReportActionsUtils.getReportAction(childReport?.parentReportID, childReport?.parentReportActionID); + const parentReportAction = getReportAction(childReport?.parentReportID, childReport?.parentReportActionID); const {reportActions: paginatedReportActions} = usePaginatedReportActions(childReport?.reportID); const transactionThreadReportID = useMemo( - () => ReportActionsUtils.getOneTransactionThreadReportID(childReport?.reportID, paginatedReportActions ?? [], isOffline), + () => getOneTransactionThreadReportID(childReport?.reportID, paginatedReportActions ?? [], isOffline), [childReport?.reportID, paginatedReportActions, isOffline], ); const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`); - const isMoneyRequestReport = useMemo(() => ReportUtils.isMoneyRequestReport(childReport), [childReport]); - const isInvoiceReport = useMemo(() => ReportUtils.isInvoiceReport(childReport), [childReport]); + const isMoneyRequestReport = useMemo(() => isMoneyRequestReportUtils(childReport), [childReport]); + const isInvoiceReport = useMemo(() => isInvoiceReportUtils(childReport), [childReport]); const requestParentReportAction = useMemo(() => { if (isMoneyRequestReport || isInvoiceReport) { @@ -180,13 +188,13 @@ function BaseReportActionContextMenu({ const [parentReportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${childReport?.parentReportID}`); const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${childReport?.parentReportID}`); - const isMoneyRequest = useMemo(() => ReportUtils.isMoneyRequest(childReport), [childReport]); - const isTrackExpenseReport = ReportUtils.isTrackExpenseReport(childReport); + const isMoneyRequest = useMemo(() => isMoneyRequestReportUtils(childReport), [childReport]); + const isTrackExpenseReport = isTrackExpenseReportUtils(childReport); const isSingleTransactionView = isMoneyRequest || isTrackExpenseReport; const isMoneyRequestOrReport = isMoneyRequestReport || isSingleTransactionView; const areHoldRequirementsMet = - !isInvoiceReport && isMoneyRequestOrReport && !ReportUtils.isArchivedNonExpenseReport(transactionThreadReportID ? childReport : parentReport, parentReportNameValuePairs); + !isInvoiceReport && isMoneyRequestOrReport && !isArchivedNonExpenseReport(transactionThreadReportID ? childReport : parentReport, parentReportNameValuePairs); const shouldEnableArrowNavigation = !isMini && (isVisible || shouldKeepOpen); let filteredContextMenuActions = ContextMenuActions.filter( @@ -242,11 +250,11 @@ function BaseReportActionContextMenu({ * shows the sign in modal. Else, executes the callback. */ const interceptAnonymousUser = (callback: () => void, isAnonymousAction = false) => { - if (Session.isAnonymousUser() && !isAnonymousAction) { + if (isAnonymousUser() && !isAnonymousAction) { hideContextMenu(false); InteractionManager.runAfterInteractions(() => { - Session.signOutAndRedirectToSignIn(); + signOutAndRedirectToSignIn(); }); } else { callback(); @@ -287,8 +295,8 @@ function BaseReportActionContextMenu({ checkIfContextMenuActive?.(); setShouldKeepOpen(false); }, - ReportUtils.isArchivedNonExpenseReportWithID(originalReportID), - ReportUtils.chatIncludesChronosWithID(originalReportID), + isArchivedNonExpenseReportWithID(originalReportID), + chatIncludesChronosWithID(originalReportID), undefined, undefined, filteredContextMenuActions, @@ -299,16 +307,16 @@ function BaseReportActionContextMenu({ ); }; - const cardIssuedActionOriginalMessage = ReportActionsUtils.isActionOfType( + const cardIssuedActionOriginalMessage = isActionOfType( reportAction, CONST.REPORT.ACTIONS.TYPE.CARD_ISSUED, CONST.REPORT.ACTIONS.TYPE.CARD_ISSUED_VIRTUAL, CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS, ) - ? ReportActionsUtils.getOriginalMessage(reportAction) + ? getOriginalMessage(reportAction) : undefined; const cardID = cardIssuedActionOriginalMessage?.cardID ?? CONST.DEFAULT_NUMBER_ID; - const isPolicyAdmin = PolicyUtils.isPolicyAdmin(PolicyUtils.getPolicy(policyID)); + const isPolicyAdmin = isPolicyAdminPolicyUtils(getPolicy(policyID)); const card = isPolicyAdmin ? cardsList?.[cardID] : cardList[cardID]; return ( diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts index d12676596650..a9c2d7c83225 100644 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts @@ -105,9 +105,9 @@ function showContextMenu( event: GestureResponderEvent | MouseEvent, selection: string, contextMenuAnchor: ContextMenuAnchor, - reportID = '-1', - reportActionID = '-1', - originalReportID = '-1', + reportID: string | undefined = undefined, + reportActionID: string | undefined = undefined, + originalReportID: string | undefined = undefined, draftMessage: string | undefined = undefined, onShow = () => {}, onHide = () => {}, diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index dbe30103ebd9..667c22667435 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -39,27 +39,82 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import ControlSelection from '@libs/ControlSelection'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import * as ErrorUtils from '@libs/ErrorUtils'; +import {canUseTouchScreen} from '@libs/DeviceCapabilities'; +import type {OnyxDataWithErrors} from '@libs/ErrorUtils'; +import {getLatestErrorMessageField} from '@libs/ErrorUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; -import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; +import {formatPhoneNumber} from '@libs/LocalePhoneNumber'; import Navigation from '@libs/Navigation/Navigation'; import Permissions from '@libs/Permissions'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getDisplayNameOrDefault} from '@libs/PersonalDetailsUtils'; +import {getCleanedTagName} from '@libs/PolicyUtils'; +import { + extractLinksFromMessageHtml, + getDismissedViolationMessageText, + getIOUReportIDFromReportActionPreview, + getOriginalMessage, + getPolicyChangeLogAddEmployeeMessage, + getPolicyChangeLogChangeRoleMessage, + getPolicyChangeLogDeleteMemberMessage, + getRemovedConnectionMessage, + getRemovedFromApprovalChainMessage, + getRenamedAction, + getReportActionMessage, + getReportActionText, + getWhisperedTo, + isActionableAddPaymentCard, + isActionableJoinRequest, + isActionableMentionWhisper, + isActionableReportMentionWhisper, + isActionableTrackExpense, + isActionOfType, + isChronosOOOListAction, + isCreatedTaskReportAction, + isDeletedAction, + isDeletedParentAction as isDeletedParentActionReportActionsUtils, + isMessageDeleted, + isMoneyRequestAction, + isPendingRemove, + isReimbursementDeQueuedAction, + isReimbursementQueuedAction, + isRenamedAction, + isTagModificationAction, + isTaskAction, + isTripPreview, + isUnapprovedAction, + isWhisperActionTargetedToOthers, +} from '@libs/ReportActionsUtils'; +import { + canWriteInReport, + chatIncludesConcierge, + getDisplayNamesWithTooltips, + getIconsForParticipants, + getIOUApprovedMessage, + getIOUForwardedMessage, + getIOUSubmittedMessage, + getIOUUnapprovedMessage, + getReportAutomaticallyApprovedMessage, + getReportAutomaticallySubmittedMessage, + getWhisperDisplayNames, + getWorkspaceNameUpdatedMessage, + isArchivedNonExpenseReport, + isChatThread, + isCompletedTaskReport, + isReportMessageAttachment, + isTaskReport, + shouldDisplayThreadReplies as shouldDisplayThreadRepliesReportUtils, +} from '@libs/ReportUtils'; import type {MissingPaymentMethod} from '@libs/ReportUtils'; import SelectionScraper from '@libs/SelectionScraper'; import shouldRenderAddPaymentCard from '@libs/shouldRenderAppPaymentCard'; import {ReactionListContext} from '@pages/home/ReportScreenContext'; -import * as BankAccounts from '@userActions/BankAccounts'; -import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; -import * as Member from '@userActions/Policy/Member'; -import * as Report from '@userActions/Report'; +import {openPersonalBankAccountSetupView} from '@userActions/BankAccounts'; +import {hideEmojiPicker, isActive} from '@userActions/EmojiPickerAction'; +import {acceptJoinRequest, declineJoinRequest} from '@userActions/Policy/Member'; +import {expandURLPreview} from '@userActions/Report'; import type {IgnoreDirection} from '@userActions/ReportActions'; -import * as Session from '@userActions/Session'; -import * as User from '@userActions/User'; +import {isAnonymousUser, signOutAndRedirectToSignIn} from '@userActions/Session'; +import {isBlockedFromConcierge} from '@userActions/User'; import CONST from '@src/CONST'; import type {IOUAction} from '@src/CONST'; import ROUTES from '@src/ROUTES'; @@ -69,8 +124,8 @@ import type {JoinWorkspaceResolution} from '@src/types/onyx/OriginalMessage'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import {RestrictedReadOnlyContextMenuActions} from './ContextMenu/ContextMenuActions'; import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; -import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; -import {hideContextMenu} from './ContextMenu/ReportActionContextMenu'; +import type {ContextMenuAnchor} from './ContextMenu/ReportActionContextMenu'; +import {hideContextMenu, hideDeleteModal, isActiveReportAction, showContextMenu} from './ContextMenu/ReportActionContextMenu'; import LinkPreviewer from './LinkPreviewer'; import ReportActionItemBasicMessage from './ReportActionItemBasicMessage'; import ReportActionItemContentCreated from './ReportActionItemContentCreated'; @@ -300,7 +355,7 @@ function PureReportActionItem({ const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(action.reportActionID)); + const [isContextMenuActive, setIsContextMenuActive] = useState(() => isActiveReportAction(action.reportActionID)); const [isEmojiPickerActive, setIsEmojiPickerActive] = useState(); const [isPaymentMethodPopoverActive, setIsPaymentMethodPopoverActive] = useState(); @@ -309,36 +364,32 @@ function PureReportActionItem({ const reactionListRef = useContext(ReactionListContext); const {updateHiddenAttachments} = useContext(ReportAttachmentsContext); const textInputRef = useRef(null); - const popoverAnchorRef = useRef>(null); + const popoverAnchorRef = useRef>(null); const downloadedPreviews = useRef([]); const prevDraftMessage = usePrevious(draftMessage); const isReportActionLinked = linkedReportActionID && action.reportActionID && linkedReportActionID === action.reportActionID; const reportScrollManager = useReportScrollManager(); - const isActionableWhisper = - ReportActionsUtils.isActionableMentionWhisper(action) || ReportActionsUtils.isActionableTrackExpense(action) || ReportActionsUtils.isActionableReportMentionWhisper(action); - const originalMessage = ReportActionsUtils.getOriginalMessage(action); + const isActionableWhisper = isActionableMentionWhisper(action) || isActionableTrackExpense(action) || isActionableReportMentionWhisper(action); + const originalMessage = getOriginalMessage(action); const highlightedBackgroundColorIfNeeded = useMemo( () => (isReportActionLinked ? StyleUtils.getBackgroundColorStyle(theme.messageHighlightBG) : {}), [StyleUtils, isReportActionLinked, theme.messageHighlightBG], ); - const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(action); + const isDeletedParentAction = isDeletedParentActionReportActionsUtils(action); const isOriginalMessageAnObject = originalMessage && typeof originalMessage === 'object'; const hasResolutionInOriginalMessage = isOriginalMessageAnObject && 'resolution' in originalMessage; const prevActionResolution = usePrevious(isActionableWhisper && hasResolutionInOriginalMessage ? originalMessage?.resolution : null); // IOUDetails only exists when we are sending money - const isSendingMoney = - ReportActionsUtils.isMoneyRequestAction(action) && - ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && - ReportActionsUtils.getOriginalMessage(action)?.IOUDetails; + const isSendingMoney = isMoneyRequestAction(action) && getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && getOriginalMessage(action)?.IOUDetails; const updateHiddenState = useCallback( (isHiddenValue: boolean) => { setIsHidden(isHiddenValue); const message = Array.isArray(action.message) ? action.message?.at(-1) : action.message; - const isAttachment = ReportUtils.isReportMessageAttachment(message); + const isAttachment = isReportMessageAttachment(message); if (!isAttachment) { return; } @@ -351,12 +402,12 @@ function PureReportActionItem({ () => () => { // ReportActionContextMenu, EmojiPicker and PopoverReactionList are global components, // we should also hide them when the current component is destroyed - if (ReportActionContextMenu.isActiveReportAction(action.reportActionID)) { - ReportActionContextMenu.hideContextMenu(); - ReportActionContextMenu.hideDeleteModal(); + if (isActiveReportAction(action.reportActionID)) { + hideContextMenu(); + hideDeleteModal(); } - if (EmojiPickerAction.isActive(action.reportActionID)) { - EmojiPickerAction.hideEmojiPicker(true); + if (isActive(action.reportActionID)) { + hideEmojiPicker(true); } if (reactionListRef?.current?.isActiveReportAction(action.reportActionID)) { reactionListRef?.current?.hideReactionList(); @@ -367,11 +418,11 @@ function PureReportActionItem({ useEffect(() => { // We need to hide EmojiPicker when this is a deleted parent action - if (!isDeletedParentAction || !EmojiPickerAction.isActive(action.reportActionID)) { + if (!isDeletedParentAction || !isActive(action.reportActionID)) { return; } - EmojiPickerAction.hideEmojiPicker(true); + hideEmojiPicker(true); }, [isDeletedParentAction, action.reportActionID]); useEffect(() => { @@ -387,17 +438,17 @@ function PureReportActionItem({ return; } - const urls = ReportActionsUtils.extractLinksFromMessageHtml(action); + const urls = extractLinksFromMessageHtml(action); if (lodashIsEqual(downloadedPreviews.current, urls) || action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { return; } downloadedPreviews.current = urls; - Report.expandURLPreview(reportID, action.reportActionID); + expandURLPreview(reportID, action.reportActionID); }, [action, reportID]); useEffect(() => { - if (draftMessage === undefined || !ReportActionsUtils.isDeletedAction(action)) { + if (draftMessage === undefined || !isDeletedAction(action)) { return; } deleteReportActionDraft(reportID, action); @@ -405,7 +456,7 @@ function PureReportActionItem({ // Hide the message if it is being moderated for a higher offense, or is hidden by a moderator // Removed messages should not be shown anyway and should not need this flow - const latestDecision = ReportActionsUtils.getReportActionMessage(action)?.moderationDecision?.decision ?? ''; + const latestDecision = getReportActionMessage(action)?.moderationDecision?.decision ?? ''; useEffect(() => { if (action.actionName !== CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT) { return; @@ -419,10 +470,7 @@ function PureReportActionItem({ } setModerationDecision(latestDecision); - if ( - ![CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING].some((item) => item === latestDecision) && - !ReportActionsUtils.isPendingRemove(action) - ) { + if (![CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING].some((item) => item === latestDecision) && !isPendingRemove(action)) { setIsHidden(true); return; } @@ -430,10 +478,10 @@ function PureReportActionItem({ }, [latestDecision, action]); const toggleContextMenuFromActiveReportAction = useCallback(() => { - setIsContextMenuActive(ReportActionContextMenu.isActiveReportAction(action.reportActionID)); + setIsContextMenuActive(isActiveReportAction(action.reportActionID)); }, [action.reportActionID]); - const disabledActions = useMemo(() => (!ReportUtils.canWriteInReport(report) ? RestrictedReadOnlyContextMenuActions : []), [report]); + const disabledActions = useMemo(() => (!canWriteInReport(report) ? RestrictedReadOnlyContextMenuActions : []), [report]); /** * Show the ReportActionContextMenu modal popover. @@ -449,7 +497,7 @@ function PureReportActionItem({ setIsContextMenuActive(true); const selection = SelectionScraper.getCurrentSelection(); - ReportActionContextMenu.showContextMenu( + showContextMenu( CONST.CONTEXT_MENU_TYPES.REPORT_ACTION, event, selection, @@ -507,7 +555,7 @@ function PureReportActionItem({ const contextValue = useMemo( () => ({ anchor: popoverAnchorRef.current, - report: {...report, reportID: report?.reportID}, + report: report?.reportID ? {...report, reportID: report?.reportID} : undefined, reportNameValuePairs, action, transactionThreadReport, @@ -521,7 +569,7 @@ function PureReportActionItem({ const mentionReportContextValue = useMemo(() => ({currentReportID: report?.reportID}), [report?.reportID]); const actionableItemButtons: ActionableItem[] = useMemo(() => { - if (ReportActionsUtils.isActionableAddPaymentCard(action) && userBillingFundID === undefined && shouldRenderAddPaymentCard()) { + if (isActionableAddPaymentCard(action) && userBillingFundID === undefined && shouldRenderAddPaymentCard()) { return [ { text: 'subscription.cardSection.addCardButton', @@ -535,12 +583,12 @@ function PureReportActionItem({ ]; } - if (!isActionableWhisper && (!ReportActionsUtils.isActionableJoinRequest(action) || ReportActionsUtils.getOriginalMessage(action)?.choice !== ('' as JoinWorkspaceResolution))) { + if (!isActionableWhisper && (!isActionableJoinRequest(action) || getOriginalMessage(action)?.choice !== ('' as JoinWorkspaceResolution))) { return []; } - if (ReportActionsUtils.isActionableTrackExpense(action)) { - const transactionID = ReportActionsUtils.getOriginalMessage(action)?.transactionID; + if (isActionableTrackExpense(action)) { + const transactionID = getOriginalMessage(action)?.transactionID; return [ { text: 'actionableMentionTrackExpense.submit', @@ -577,23 +625,23 @@ function PureReportActionItem({ ]; } - if (ReportActionsUtils.isActionableJoinRequest(action)) { + if (isActionableJoinRequest(action)) { return [ { text: 'actionableMentionJoinWorkspaceOptions.accept', key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.ACCEPT}`, - onPress: () => Member.acceptJoinRequest(reportID, action), + onPress: () => acceptJoinRequest(reportID, action), isPrimary: true, }, { text: 'actionableMentionJoinWorkspaceOptions.decline', key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.DECLINE}`, - onPress: () => Member.declineJoinRequest(reportID, action), + onPress: () => declineJoinRequest(reportID, action), }, ]; } - if (ReportActionsUtils.isActionableReportMentionWhisper(action)) { + if (isActionableReportMentionWhisper(action)) { return [ { text: 'common.yes', @@ -645,19 +693,19 @@ function PureReportActionItem({ // Show the MoneyRequestPreview for when expense is present if ( - ReportActionsUtils.isMoneyRequestAction(action) && - ReportActionsUtils.getOriginalMessage(action) && + isMoneyRequestAction(action) && + getOriginalMessage(action) && // For the pay flow, we only want to show MoneyRequestAction when sending money. When paying, we display a regular system message - (ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || - ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT || - ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK) + (getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || + getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT || + getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK) ) { // There is no single iouReport for bill splits, so only 1:1 requests require an iouReportID - const iouReportID = ReportActionsUtils.getOriginalMessage(action)?.IOUReportID?.toString(); + const iouReportID = getOriginalMessage(action)?.IOUReportID?.toString(); children = ( ); - } else if (ReportActionsUtils.isTripPreview(action)) { + } else if (isTripPreview(action)) { children = ( ); - } else if (ReportActionsUtils.isTaskAction(action)) { + } else if (isTaskAction(action)) { children = ; - } else if (ReportActionsUtils.isCreatedTaskReportAction(action)) { + } else if (isCreatedTaskReportAction(action)) { children = ( ); - } else if (ReportActionsUtils.isReimbursementQueuedAction(action)) { - const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; - const submitterDisplayName = LocalePhoneNumber.formatPhoneNumber( - PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[linkedReport?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID]), - ); - const paymentType = ReportActionsUtils.getOriginalMessage(action)?.paymentType ?? ''; + } else if (isReimbursementQueuedAction(action)) { + const linkedReport = isChatThread(report) ? parentReport : report; + const submitterDisplayName = formatPhoneNumber(getDisplayNameOrDefault(personalDetails?.[linkedReport?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID])); + const paymentType = getOriginalMessage(action)?.paymentType ?? ''; children = ( - BankAccounts.openPersonalBankAccountSetupView(Navigation.getTopmostReportId() ?? linkedReport?.reportID, undefined, undefined, isUserValidated) - } + onPress={() => openPersonalBankAccountSetupView(Navigation.getTopmostReportId() ?? linkedReport?.reportID, undefined, undefined, isUserValidated)} pressOnEnter large /> @@ -765,39 +809,36 @@ function PureReportActionItem({ ); - } else if (ReportActionsUtils.isReimbursementDeQueuedAction(action)) { + } else if (isReimbursementDeQueuedAction(action)) { children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE) { children = ; - } else if ( - ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED) || - ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED_AND_CLOSED) - ) { - const wasSubmittedViaHarvesting = ReportActionsUtils.getOriginalMessage(action)?.harvesting ?? false; + } else if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED) || isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED_AND_CLOSED)) { + const wasSubmittedViaHarvesting = getOriginalMessage(action)?.harvesting ?? false; if (wasSubmittedViaHarvesting) { children = ( - ${ReportUtils.getReportAutomaticallySubmittedMessage(action)}`} /> + ${getReportAutomaticallySubmittedMessage(action)}`} /> ); } else { - children = ; + children = ; } - } else if (ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.APPROVED)) { - const wasAutoApproved = ReportActionsUtils.getOriginalMessage(action)?.automaticAction ?? false; + } else if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.APPROVED)) { + const wasAutoApproved = getOriginalMessage(action)?.automaticAction ?? false; if (wasAutoApproved) { children = ( - ${ReportUtils.getReportAutomaticallyApprovedMessage(action)}`} /> + ${getReportAutomaticallyApprovedMessage(action)}`} /> ); } else { - children = ; + children = ; } - } else if (ReportActionsUtils.isUnapprovedAction(action)) { - children = ; - } else if (ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.FORWARDED)) { - const wasAutoForwarded = ReportActionsUtils.getOriginalMessage(action)?.automaticAction ?? false; + } else if (isUnapprovedAction(action)) { + children = ; + } else if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.FORWARDED)) { + const wasAutoForwarded = getOriginalMessage(action)?.automaticAction ?? false; if (wasAutoForwarded) { children = ( @@ -805,7 +846,7 @@ function PureReportActionItem({ ); } else { - children = ; + children = ; } } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REJECTED) { children = ; @@ -816,27 +857,27 @@ function PureReportActionItem({ } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.HOLD) { children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.HOLD_COMMENT) { - children = ; + children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.UNHOLD) { children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.MERGED_WITH_CASH_TRANSACTION) { children = ; - } else if (ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.DISMISSED_VIOLATION)) { - children = ; - } else if (ReportActionsUtils.isTagModificationAction(action.actionName)) { - children = ; + } else if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.DISMISSED_VIOLATION)) { + children = ; + } else if (isTagModificationAction(action.actionName)) { + children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_NAME) { - children = ; + children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.ADD_EMPLOYEE) { - children = ; + children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_EMPLOYEE) { - children = ; + children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.DELETE_EMPLOYEE) { - children = ; - } else if (ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.REMOVED_FROM_APPROVAL_CHAIN)) { - children = ; + children = ; + } else if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.REMOVED_FROM_APPROVAL_CHAIN)) { + children = ; } else if ( - ReportActionsUtils.isActionOfType( + isActionOfType( action, CONST.REPORT.ACTIONS.TYPE.CARD_ISSUED, CONST.REPORT.ACTIONS.TYPE.CARD_ISSUED_VIRTUAL, @@ -850,20 +891,19 @@ function PureReportActionItem({ policyID={report?.policyID} /> ); - } else if (ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION)) { + } else if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION)) { children = ; - } else if (ReportActionsUtils.isRenamedAction(action)) { - const message = ReportActionsUtils.getRenamedAction(action); + } else if (isRenamedAction(action)) { + const message = getRenamedAction(action); children = ; - } else if (ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.INTEGRATION_SYNC_FAILED)) { - const {label, errorMessage} = ReportActionsUtils.getOriginalMessage(action) ?? {label: '', errorMessage: ''}; + } else if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.INTEGRATION_SYNC_FAILED)) { + const {label, errorMessage} = getOriginalMessage(action) ?? {label: '', errorMessage: ''}; children = ; - } else if (ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.DELETE_INTEGRATION)) { - children = ; + } else if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.DELETE_INTEGRATION)) { + children = ; } else { const hasBeenFlagged = - ![CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING].some((item) => item === moderationDecision) && - !ReportActionsUtils.isPendingRemove(action); + ![CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING].some((item) => item === moderationDecision) && !isPendingRemove(action); children = ( @@ -898,7 +938,7 @@ function PureReportActionItem({ {actionableItemButtons.length > 0 && ( )} @@ -911,8 +951,7 @@ function PureReportActionItem({ index={index} ref={textInputRef} shouldDisableEmojiPicker={ - (ReportUtils.chatIncludesConcierge(report) && User.isBlockedFromConcierge(blockedFromConcierge)) || - ReportUtils.isArchivedNonExpenseReport(report, reportNameValuePairs) + (chatIncludesConcierge(report) && isBlockedFromConcierge(blockedFromConcierge)) || isArchivedNonExpenseReport(report, reportNameValuePairs) } isGroupPolicyReport={!!report?.policyID && report.policyID !== CONST.POLICY.ID_FAKE} /> @@ -924,7 +963,7 @@ function PureReportActionItem({ } const numberOfThreadReplies = action.childVisibleActionCount ?? 0; - const shouldDisplayThreadReplies = ReportUtils.shouldDisplayThreadReplies(action, isThreadReportParentAction); + const shouldDisplayThreadReplies = shouldDisplayThreadRepliesReportUtils(action, isThreadReportParentAction); const oldestFourAccountIDs = action.childOldestFourAccountIDs ?.split(',') @@ -940,18 +979,18 @@ function PureReportActionItem({ !isEmptyObject(item))} /> )} - {!ReportActionsUtils.isMessageDeleted(action) && ( + {!isMessageDeleted(action) && ( { - if (Session.isAnonymousUser()) { + if (isAnonymousUser()) { hideContextMenu(false); InteractionManager.runAfterInteractions(() => { - Session.signOutAndRedirectToSignIn(); + signOutAndRedirectToSignIn(); }); } else { toggleReaction(emoji, ignoreSkinToneOnCompare); @@ -969,7 +1008,7 @@ function PureReportActionItem({ numberOfReplies={numberOfThreadReplies} mostRecentReply={`${action.childLastVisibleActionCreated}`} isHovered={hovered} - icons={ReportUtils.getIconsForParticipants(oldestFourAccountIDs, personalDetails)} + icons={getIconsForParticipants(oldestFourAccountIDs, personalDetails)} onSecondaryInteraction={showPopover} /> @@ -1004,8 +1043,7 @@ function PureReportActionItem({ iouReport={iouReport} isHovered={hovered} hasBeenFlagged={ - ![CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING].some((item) => item === moderationDecision) && - !ReportActionsUtils.isPendingRemove(action) + ![CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING].some((item) => item === moderationDecision) && !isPendingRemove(action) } > {content} @@ -1017,9 +1055,7 @@ function PureReportActionItem({ }; if (action.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) { - const transactionID = ReportActionsUtils.isMoneyRequestAction(parentReportActionForTransactionThread) - ? ReportActionsUtils.getOriginalMessage(parentReportActionForTransactionThread)?.IOUTransactionID - : undefined; + const transactionID = isMoneyRequestAction(parentReportActionForTransactionThread) ? getOriginalMessage(parentReportActionForTransactionThread)?.IOUTransactionID : undefined; return ( ); } - if (ReportActionsUtils.isChronosOOOListAction(action)) { + if (isChronosOOOListAction(action)) { return ( 1; - const iouReportID = - ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUReportID - ? (ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ?? '').toString() - : undefined; + const iouReportID = isMoneyRequestAction(action) && getOriginalMessage(action)?.IOUReportID ? getOriginalMessage(action)?.IOUReportID?.toString() : undefined; const transactionsWithReceipts = getTransactionsWithReceipts(iouReportID); const isWhisper = whisperedTo.length > 0 && transactionsWithReceipts.length === 0; const whisperedToPersonalDetails = isWhisper ? (Object.values(personalDetails ?? {}).filter((details) => whisperedTo.includes(details?.accountID ?? CONST.DEFAULT_NUMBER_ID)) as OnyxTypes.PersonalDetails[]) : []; const isWhisperOnlyVisibleByUser = isWhisper && isCurrentUserTheOnlyParticipant(whisperedTo); - const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; + const displayNamesWithTooltips = isWhisper ? getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; return ( shouldUseNarrowLayout && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} + onPressIn={() => shouldUseNarrowLayout && canUseTouchScreen() && ControlSelection.block()} onPressOut={() => ControlSelection.unblock()} onSecondaryInteraction={showPopover} preventDefaultContextMenu={draftMessage === undefined && !hasErrors} @@ -1133,7 +1161,7 @@ function PureReportActionItem({ > { - const transactionID = ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : undefined; + const transactionID = isMoneyRequestAction(action) ? getOriginalMessage(action)?.IOUTransactionID : undefined; if (transactionID) { clearError(transactionID); } @@ -1144,9 +1172,9 @@ function PureReportActionItem({ draftMessage !== undefined ? undefined : action.pendingAction ?? (action.isOptimisticAction ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : undefined) } shouldHideOnDelete={!isThreadReportParentAction} - errors={linkedTransactionRouteError ?? ErrorUtils.getLatestErrorMessageField(action as ErrorUtils.OnyxDataWithErrors)} + errors={linkedTransactionRouteError ?? getLatestErrorMessageField(action as OnyxDataWithErrors)} errorRowStyles={[styles.ml10, styles.mr2]} - needsOffscreenAlphaCompositing={ReportActionsUtils.isMoneyRequestAction(action)} + needsOffscreenAlphaCompositing={isMoneyRequestAction(action)} shouldDisableStrikeThrough > {isWhisper && ( @@ -1163,7 +1191,7 @@ function PureReportActionItem({   { prevProps.report?.parentReportID === nextProps.report?.parentReportID && prevProps.report?.parentReportActionID === nextProps.report?.parentReportActionID && // TaskReport's created actions render the TaskView, which updates depending on certain fields in the TaskReport - ReportUtils.isTaskReport(prevProps.report) === ReportUtils.isTaskReport(nextProps.report) && + isTaskReport(prevProps.report) === isTaskReport(nextProps.report) && prevProps.action.actionName === nextProps.action.actionName && prevProps.report?.reportName === nextProps.report?.reportName && prevProps.report?.description === nextProps.report?.description && - ReportUtils.isCompletedTaskReport(prevProps.report) === ReportUtils.isCompletedTaskReport(nextProps.report) && + isCompletedTaskReport(prevProps.report) === isCompletedTaskReport(nextProps.report) && prevProps.report?.managerID === nextProps.report?.managerID && prevProps.shouldHideThreadDividerLine === nextProps.shouldHideThreadDividerLine && prevProps.report?.total === nextProps.report?.total && diff --git a/src/pages/home/report/ReportActionItemContentCreated.tsx b/src/pages/home/report/ReportActionItemContentCreated.tsx index 69e27701edd8..1555854e50cd 100644 --- a/src/pages/home/report/ReportActionItemContentCreated.tsx +++ b/src/pages/home/report/ReportActionItemContentCreated.tsx @@ -29,10 +29,7 @@ import ReportActionItemSingle from './ReportActionItemSingle'; type ReportActionItemContentCreatedProps = { /** The context value containing the report and action data, along with the show context menu props */ - contextValue: ShowContextMenuContextProps & { - report: OnyxTypes.Report; - action: OnyxTypes.ReportAction; - }; + contextValue: ShowContextMenuContextProps; /** Report action belonging to the report's parent */ parentReportAction: OnyxEntry; @@ -53,7 +50,7 @@ function ReportActionItemContentCreated({contextValue, parentReportAction, trans const {report, action, transactionThreadReport} = contextValue; - const policy = usePolicy(report.policyID === CONST.POLICY.OWNER_EMAIL_FAKE ? '-1' : report.policyID ?? '-1'); + const policy = usePolicy(report?.policyID === CONST.POLICY.OWNER_EMAIL_FAKE ? '-1' : report?.policyID ?? '-1'); const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID ?? '-1'}`); const transactionCurrency = TransactionUtils.getCurrency(transaction); @@ -62,7 +59,7 @@ function ReportActionItemContentCreated({contextValue, parentReportAction, trans () => shouldHideThreadDividerLine ? ( ) : ( @@ -71,7 +68,7 @@ function ReportActionItemContentCreated({contextValue, parentReportAction, trans style={[!shouldHideThreadDividerLine ? styles.reportHorizontalRule : {}]} /> ), - [shouldHideThreadDividerLine, report.reportID, styles.reportHorizontalRule], + [shouldHideThreadDividerLine, report?.reportID, styles.reportHorizontalRule], ); const contextMenuValue = useMemo(() => ({...contextValue, isDisabled: true}), [contextValue]); @@ -106,7 +103,7 @@ function ReportActionItemContentCreated({contextValue, parentReportAction, trans } return ( - + + {!isEmptyObject(transactionThreadReport?.reportID) ? ( <> @@ -177,7 +174,7 @@ function ReportActionItemContentCreated({contextValue, parentReportAction, trans )} @@ -187,8 +184,8 @@ function ReportActionItemContentCreated({contextValue, parentReportAction, trans return ( ); } diff --git a/src/pages/home/report/ReportActionItemCreated.tsx b/src/pages/home/report/ReportActionItemCreated.tsx index 1adb24fa23a7..679855a2847d 100644 --- a/src/pages/home/report/ReportActionItemCreated.tsx +++ b/src/pages/home/report/ReportActionItemCreated.tsx @@ -17,7 +17,7 @@ import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground'; type ReportActionItemCreatedProps = { /** The id of the report */ - reportID: string; + reportID: string | undefined; /** The id of the policy */ // eslint-disable-next-line react/no-unused-prop-types diff --git a/src/pages/home/report/ReportActionItemMessage.tsx b/src/pages/home/report/ReportActionItemMessage.tsx index 4868bd42ee4d..0da568acb098 100644 --- a/src/pages/home/report/ReportActionItemMessage.tsx +++ b/src/pages/home/report/ReportActionItemMessage.tsx @@ -8,8 +8,20 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; +import { + getLinkedTransactionID, + getMemberChangeMessageFragment, + getOriginalMessage, + getReportActionMessage, + getReportActionMessageFragments, + getUpdateRoomDescriptionFragment, + isAddCommentAction, + isApprovedOrSubmittedReportAction as isApprovedOrSubmittedReportActionUtils, + isMemberChangeAction, + isMoneyRequestAction, + isThreadParentMessage, +} from '@libs/ReportActionsUtils'; +import {getIOUReportActionDisplayMessage, hasMissingInvoiceBankAccount, isSettled} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -38,13 +50,13 @@ function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHid const styles = useThemeStyles(); const {translate} = useLocalize(); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); - const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.getLinkedTransactionID(action) ?? -1}`); + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getLinkedTransactionID(action) ?? CONST.DEFAULT_NUMBER_ID}`); - const fragments = ReportActionsUtils.getReportActionMessageFragments(action); - const isIOUReport = ReportActionsUtils.isMoneyRequestAction(action); + const fragments = getReportActionMessageFragments(action); + const isIOUReport = isMoneyRequestAction(action); - if (ReportActionsUtils.isMemberChangeAction(action)) { - const fragment = ReportActionsUtils.getMemberChangeMessageFragment(action); + if (isMemberChangeAction(action)) { + const fragment = getMemberChangeMessageFragment(action); return ( @@ -60,7 +72,7 @@ function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHid } if (action.actionName === CONST.REPORT.ACTIONS.TYPE.ROOM_CHANGE_LOG.UPDATE_ROOM_DESCRIPTION) { - const fragment = ReportActionsUtils.getUpdateRoomDescriptionFragment(action); + const fragment = getUpdateRoomDescriptionFragment(action); return ( type === action.actionName); @@ -99,11 +111,11 @@ function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHid key={`actionFragment-${action.reportActionID}-${index}`} fragment={fragment} iouMessage={iouMessage} - isThreadParentMessage={ReportActionsUtils.isThreadParentMessage(action, reportID)} + isThreadParentMessage={isThreadParentMessage(action, reportID)} pendingAction={action.pendingAction} actionName={action.actionName} - source={ReportActionsUtils.isAddCommentAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.source : ''} - accountID={action.actorAccountID ?? -1} + source={isAddCommentAction(action) ? getOriginalMessage(action)?.source : ''} + accountID={action.actorAccountID ?? CONST.DEFAULT_NUMBER_ID} style={style} displayAsGroup={displayAsGroup} isApprovedOrSubmittedReportAction={isApprovedOrSubmittedReportAction} @@ -113,7 +125,7 @@ function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHid // to decide if the fragment should be from left to right for RTL display names e.g. Arabic for proper // formatting. isFragmentContainingDisplayName={index === 0} - moderationDecision={ReportActionsUtils.getReportActionMessage(action)?.moderationDecision?.decision} + moderationDecision={getReportActionMessage(action)?.moderationDecision?.decision} /> )); @@ -132,7 +144,7 @@ function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHid Navigation.navigate(ROUTES.WORKSPACE_INVOICES.getRoute(policyID)); }; - const shouldShowAddBankAccountButton = action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && ReportUtils.hasMissingInvoiceBankAccount(reportID) && !ReportUtils.isSettled(reportID); + const shouldShowAddBankAccountButton = action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && hasMissingInvoiceBankAccount(reportID) && !isSettled(reportID); return ( From 3ad33967094c5032b365ff57793ba07631ffcafb Mon Sep 17 00:00:00 2001 From: Rutika Pawar <183392827+twilight2294@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:19:16 +0530 Subject: [PATCH 077/179] make ISSUE_NEW_EXPENSIFY_CARD as collection --- src/ONYXKEYS.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 6b26ecd73700..c151fbb3b0a3 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -415,9 +415,6 @@ const ONYXKEYS = { /** Stores the last export method for policy */ LAST_EXPORT_METHOD: 'lastExportMethod', - /** Stores the information about the state of issuing a new card */ - ISSUE_NEW_EXPENSIFY_CARD: 'issueNewExpensifyCard', - /** Stores the information about the state of addint a new company card */ ADD_NEW_COMPANY_CARD: 'addNewCompanyCard', @@ -550,6 +547,9 @@ const ONYXKEYS = { /** Whether the bank account chosen for Expensify Card in on verification waitlist */ NVP_EXPENSIFY_ON_CARD_WAITLIST: 'nvp_expensify_onCardWaitlist_', + + /** Stores the information about the state of issuing a new card */ + ISSUE_NEW_EXPENSIFY_CARD: 'issueNewExpensifyCard_', }, /** List of Form ids */ @@ -885,6 +885,7 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION]: boolean; [ONYXKEYS.COLLECTION.LAST_SELECTED_FEED]: OnyxTypes.CompanyCardFeed; [ONYXKEYS.COLLECTION.NVP_EXPENSIFY_ON_CARD_WAITLIST]: OnyxTypes.CardOnWaitlist; + [ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD]: OnyxTypes.IssueNewCard; }; type OnyxValuesMapping = { @@ -1023,7 +1024,6 @@ type OnyxValuesMapping = { [ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_PENDING]: boolean; [ONYXKEYS.NVP_TRAVEL_SETTINGS]: OnyxTypes.TravelSettings; [ONYXKEYS.REVIEW_DUPLICATES]: OnyxTypes.ReviewDuplicates; - [ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD]: OnyxTypes.IssueNewCard; [ONYXKEYS.ADD_NEW_COMPANY_CARD]: OnyxTypes.AddNewCompanyCardFeed; [ONYXKEYS.ASSIGN_CARD]: OnyxTypes.AssignCard; [ONYXKEYS.MOBILE_SELECTION_MODE]: OnyxTypes.MobileSelectionMode; From 593388ccf1c573e9e75c89dc09da8e75d0d315d7 Mon Sep 17 00:00:00 2001 From: Rutika Pawar <183392827+twilight2294@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:20:49 +0530 Subject: [PATCH 078/179] use collection for ISSUE_NEW_EXPENSIFY_CARD --- src/libs/actions/Card.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/libs/actions/Card.ts b/src/libs/actions/Card.ts index 271da9b4eada..f1b604b059e5 100644 --- a/src/libs/actions/Card.ts +++ b/src/libs/actions/Card.ts @@ -35,6 +35,9 @@ type IssueNewCardFlowData = { /** Data required to be sent to issue a new card */ data?: Partial; + + /** ID of the policy */ + policyID: string; }; function reportVirtualExpensifyCardFraud(card: Card, validateCode: string) { @@ -381,19 +384,19 @@ function getCardDefaultName(userName?: string) { return `${userName}'s Card`; } -function setIssueNewCardStepAndData({data, isEditing, step}: IssueNewCardFlowData) { - Onyx.merge(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD, {data, isEditing, currentStep: step, errors: null}); +function setIssueNewCardStepAndData({data, isEditing, step, policyID}: IssueNewCardFlowData) { + Onyx.merge(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, {data, isEditing, currentStep: step, errors: null}); } -function clearIssueNewCardFlow() { - Onyx.set(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD, { +function clearIssueNewCardFlow(policyID: string) { + Onyx.set(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, { currentStep: null, data: {}, }); } -function clearIssueNewCardError() { - Onyx.merge(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD, {errors: null}); +function clearIssueNewCardError(policyID: string) { + Onyx.merge(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, {errors: null}); } function updateExpensifyCardLimit(workspaceAccountID: number, cardID: number, newLimit: number, newAvailableSpend: number, oldLimit?: number, oldAvailableSpend?: number) { @@ -735,7 +738,7 @@ function issueExpensifyCard(policyID: string, feedCountry: string, validateCode: const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD, + key: `${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, value: { isLoading: true, errors: null, @@ -747,7 +750,7 @@ function issueExpensifyCard(policyID: string, feedCountry: string, validateCode: const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD, + key: `${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, value: { isLoading: false, isSuccessful: true, @@ -758,7 +761,7 @@ function issueExpensifyCard(policyID: string, feedCountry: string, validateCode: const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD, + key: `${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, value: { isLoading: false, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), From bc2cebb555e11980c2446dc6ececa339416d4094 Mon Sep 17 00:00:00 2001 From: Rutika Pawar <183392827+twilight2294@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:21:19 +0530 Subject: [PATCH 079/179] Update AssigneeStep.tsx --- .../workspace/expensifyCard/issueNew/AssigneeStep.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx b/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx index 769532e49351..1042671b52ff 100644 --- a/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx @@ -33,7 +33,8 @@ function AssigneeStep({policy}: AssigneeStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const {isOffline} = useNetwork(); - const [issueNewCard] = useOnyx(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD); + const policyID = policy?.id ?? '-1'; + const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); const isEditing = issueNewCard?.isEditing; @@ -53,16 +54,17 @@ function AssigneeStep({policy}: AssigneeStepProps) { step: isEditing ? CONST.EXPENSIFY_CARD.STEP.CONFIRMATION : CONST.EXPENSIFY_CARD.STEP.CARD_TYPE, data, isEditing: false, + policyID, }); }; const handleBackButtonPress = () => { if (isEditing) { - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false}); + Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false, policyID}); return; } Navigation.goBack(); - Card.clearIssueNewCardFlow(); + Card.clearIssueNewCardFlow(policyID); }; const shouldShowSearchInput = policy?.employeeList && Object.keys(policy.employeeList).length >= MINIMUM_MEMBER_TO_SHOW_SEARCH; From dd0d76ec1078a9c0499ff89175bcd4fb89676988 Mon Sep 17 00:00:00 2001 From: Rutika Pawar <183392827+twilight2294@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:21:31 +0530 Subject: [PATCH 080/179] Update CardNameStep.tsx --- .../expensifyCard/issueNew/CardNameStep.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx b/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx index 75ea83755704..ef33f4c6cc0e 100644 --- a/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx @@ -18,11 +18,16 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/IssueNewExpensifyCardForm'; -function CardNameStep() { +type CardNameStepProps = { + /** ID of the policy */ + policyID: string; +}; + +function CardNameStep({policyID}: CardNameStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const {inputCallbackRef} = useAutoFocusInput(); - const [issueNewCard] = useOnyx(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD); + const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); const isEditing = issueNewCard?.isEditing; const data = issueNewCard?.data; @@ -46,15 +51,16 @@ function CardNameStep() { cardTitle: values.cardTitle, }, isEditing: false, + policyID, }); }, []); const handleBackButtonPress = useCallback(() => { if (isEditing) { - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false}); + Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false, policyID}); return; } - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.LIMIT}); + Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.LIMIT, policyID}); }, [isEditing]); return ( From 0367b93e00414d69e13cc233e34c061c293e1093 Mon Sep 17 00:00:00 2001 From: Rutika Pawar <183392827+twilight2294@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:21:42 +0530 Subject: [PATCH 081/179] Update CardTypeStep.tsx --- .../expensifyCard/issueNew/CardTypeStep.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx b/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx index ba335974663b..3714c5177c03 100644 --- a/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx @@ -13,10 +13,15 @@ import * as Card from '@userActions/Card'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -function CardTypeStep() { +type CardTypeStepProps = { + /** ID of the policy */ + policyID: string; +}; + +function CardTypeStep({policyID}: CardTypeStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const [issueNewCard] = useOnyx(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD); + const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); const isEditing = issueNewCard?.isEditing; @@ -27,15 +32,16 @@ function CardTypeStep() { cardType: value, }, isEditing: false, + policyID, }); }; const handleBackButtonPress = () => { if (isEditing) { - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false}); + Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false, policyID}); return; } - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.ASSIGNEE}); + Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.ASSIGNEE, policyID}); }; return ( From 092775a066ea87754a016cda230634e9c777f91f Mon Sep 17 00:00:00 2001 From: Rutika Pawar <183392827+twilight2294@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:21:52 +0530 Subject: [PATCH 082/179] Update ConfirmationStep.tsx --- .../expensifyCard/issueNew/ConfirmationStep.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx b/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx index 154cc53a882c..514ab49795db 100644 --- a/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx @@ -37,7 +37,7 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) { const styles = useThemeStyles(); const {isOffline} = useNetwork(); const [account] = useOnyx(ONYXKEYS.ACCOUNT); - const [issueNewCard] = useOnyx(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD); + const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE); const validateError = ErrorUtils.getLatestErrorMessageField(issueNewCard); const [isValidateCodeActionModalVisible, setIsValidateCodeActionModalVisible] = useState(false); @@ -59,7 +59,7 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) { return; } Navigation.navigate(backTo ?? ROUTES.WORKSPACE_EXPENSIFY_CARD.getRoute(policyID ?? '-1')); - Card.clearIssueNewCardFlow(); + Card.clearIssueNewCardFlow(policyID); }, [backTo, policyID, isSuccessful]); const submit = (validateCode: string) => { @@ -69,11 +69,11 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) { const errorMessage = ErrorUtils.getLatestErrorMessage(issueNewCard); const editStep = (step: IssueNewCardStep) => { - Card.setIssueNewCardStepAndData({step, isEditing: true}); + Card.setIssueNewCardStepAndData({step, isEditing: true, policyID}); }; const handleBackButtonPress = () => { - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CARD_NAME}); + Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CARD_NAME, policyID}); }; const translationForLimitType = getTranslationKeyForLimitType(data?.limitType); @@ -143,7 +143,7 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) { sendValidateCode={() => User.requestValidateCodeAction()} validateError={validateError} hasMagicCodeBeenSent={validateCodeSent} - clearError={() => Card.clearIssueNewCardError()} + clearError={() => Card.clearIssueNewCardError(policyID)} onClose={() => setIsValidateCodeActionModalVisible(false)} isVisible={isValidateCodeActionModalVisible} title={translate('cardPage.validateCardTitle')} From 567f5eeee32ab0b7795d2b5ee2213bdfe2017f1b Mon Sep 17 00:00:00 2001 From: Rutika Pawar <183392827+twilight2294@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:22:02 +0530 Subject: [PATCH 083/179] Update IssueNewCardPage.tsx --- .../workspace/expensifyCard/issueNew/IssueNewCardPage.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx b/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx index 27653df9f9b0..195e30d39d16 100644 --- a/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx @@ -21,7 +21,7 @@ import LimitTypeStep from './LimitTypeStep'; type IssueNewCardPageProps = WithPolicyAndFullscreenLoadingProps & PlatformStackScreenProps; function IssueNewCardPage({policy, route}: IssueNewCardPageProps) { - const [issueNewCard] = useOnyx(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD); + const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policy?.id}`); const {currentStep} = issueNewCard ?? {}; @@ -39,13 +39,13 @@ function IssueNewCardPage({policy, route}: IssueNewCardPageProps) { case CONST.EXPENSIFY_CARD.STEP.ASSIGNEE: return ; case CONST.EXPENSIFY_CARD.STEP.CARD_TYPE: - return ; + return ; case CONST.EXPENSIFY_CARD.STEP.LIMIT_TYPE: return ; case CONST.EXPENSIFY_CARD.STEP.LIMIT: - return ; + return ; case CONST.EXPENSIFY_CARD.STEP.CARD_NAME: - return ; + return ; case CONST.EXPENSIFY_CARD.STEP.CONFIRMATION: return ( Date: Mon, 20 Jan 2025 22:22:12 +0530 Subject: [PATCH 084/179] Update LimitStep.tsx --- .../workspace/expensifyCard/issueNew/LimitStep.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/expensifyCard/issueNew/LimitStep.tsx b/src/pages/workspace/expensifyCard/issueNew/LimitStep.tsx index 6c59489ad89b..c69f6a0d7c75 100644 --- a/src/pages/workspace/expensifyCard/issueNew/LimitStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/LimitStep.tsx @@ -16,11 +16,16 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/IssueNewExpensifyCardForm'; -function LimitStep() { +type LimitStepProps = { + /** ID of the policy */ + policyID: string; +}; + +function LimitStep({policyID}: LimitStepProps) { const {translate} = useLocalize(); const {inputCallbackRef} = useAutoFocusInput(); const styles = useThemeStyles(); - const [issueNewCard] = useOnyx(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD); + const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); const isEditing = issueNewCard?.isEditing; const submit = useCallback( @@ -30,6 +35,7 @@ function LimitStep() { step: isEditing ? CONST.EXPENSIFY_CARD.STEP.CONFIRMATION : CONST.EXPENSIFY_CARD.STEP.CARD_NAME, data: {limit}, isEditing: false, + policyID, }); }, [isEditing], @@ -37,10 +43,10 @@ function LimitStep() { const handleBackButtonPress = useCallback(() => { if (isEditing) { - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false}); + Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false, policyID}); return; } - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.LIMIT_TYPE}); + Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.LIMIT_TYPE, policyID}); }, [isEditing]); const validate = useCallback( From 67998788c1ff9f00aa8a4d6a37acca2a19934076 Mon Sep 17 00:00:00 2001 From: Rutika Pawar <183392827+twilight2294@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:22:23 +0530 Subject: [PATCH 085/179] Update LimitTypeStep.tsx --- .../workspace/expensifyCard/issueNew/LimitTypeStep.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx b/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx index a593fb4c75c7..9491eba48e95 100644 --- a/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx @@ -22,7 +22,8 @@ type LimitTypeStepProps = { function LimitTypeStep({policy}: LimitTypeStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const [issueNewCard] = useOnyx(ONYXKEYS.ISSUE_NEW_EXPENSIFY_CARD); + const policyID = policy?.id ?? '-1'; + const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); const areApprovalsConfigured = PolicyUtils.getApprovalWorkflow(policy) !== CONST.POLICY.APPROVAL_MODE.OPTIONAL; const defaultType = areApprovalsConfigured ? CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART : CONST.EXPENSIFY_CARD.LIMIT_TYPES.MONTHLY; @@ -36,15 +37,16 @@ function LimitTypeStep({policy}: LimitTypeStepProps) { step: isEditing ? CONST.EXPENSIFY_CARD.STEP.CONFIRMATION : CONST.EXPENSIFY_CARD.STEP.LIMIT, data: {limitType: typeSelected}, isEditing: false, + policyID, }); }, [isEditing, typeSelected]); const handleBackButtonPress = useCallback(() => { if (isEditing) { - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false}); + Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false, policyID}); return; } - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CARD_TYPE}); + Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CARD_TYPE, policyID}); }, [isEditing]); const data = useMemo(() => { From db6974523491d72bfc3436fda67180c12f0f971e Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Mon, 20 Jan 2025 13:53:29 -0300 Subject: [PATCH 086/179] stop recording sessions when we don't want the mto be recorded --- src/libs/Fullstory/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index c7b1c2c9eb7a..81635da5b3c8 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -131,6 +131,8 @@ const FS = { Environment.getEnvironment().then((envName: string) => { const isTestEmail = value.email !== undefined && value.email.startsWith('fullstory') && value.email.endsWith(CONST.EMAIL.QA_DOMAIN); if ((CONST.ENVIRONMENT.PRODUCTION !== envName && !isTestEmail) || isExpensifyTeam(value?.email)) { + // On web, if we started FS at some point in a browser, it will run forever. So let's shut it down if we don't want it to run. + FullStory('shutdown'); return; } FS.onReady().then(() => { From 7c214b3599bb6f082ce99afc04294931a11d0c59 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Mon, 20 Jan 2025 13:54:33 -0300 Subject: [PATCH 087/179] ignore only expensify domain --- src/libs/Fullstory/index.native.ts | 3 ++- src/libs/Fullstory/index.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts index 930c12241b78..03a52a0a603e 100644 --- a/src/libs/Fullstory/index.native.ts +++ b/src/libs/Fullstory/index.native.ts @@ -5,6 +5,7 @@ import {isConciergeChatReport, shouldUnmaskChat} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import * as Environment from '@src/libs/Environment/Environment'; import type {OnyxInputOrEntry, PersonalDetailsList, Report, UserMetadata} from '@src/types/onyx'; +import { Str } from 'expensify-common'; /** * Fullstory React-Native lib adapter @@ -43,7 +44,7 @@ const FS = { // UserMetadata onyx key. Environment.getEnvironment().then((envName: string) => { const isTestEmail = value.email !== undefined && value.email.startsWith('fullstory') && value.email.endsWith(CONST.EMAIL.QA_DOMAIN); - if ((CONST.ENVIRONMENT.PRODUCTION !== envName && !isTestEmail) || isExpensifyTeam(value?.email)) { + if ((CONST.ENVIRONMENT.PRODUCTION !== envName && !isTestEmail) || Str.extractEmailDomain(value.email ?? '') === CONST.EXPENSIFY_PARTNER_NAME) { return; } FullStory.restart(); diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 81635da5b3c8..972442380850 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -6,6 +6,7 @@ import CONST from '@src/CONST'; import * as Environment from '@src/libs/Environment/Environment'; import type {OnyxInputOrEntry, PersonalDetailsList, Report, UserMetadata} from '@src/types/onyx'; import type NavigationProperties from './types'; +import { Str } from 'expensify-common'; /** * Extract values from non-scraped at build time attribute WEB_PROP_ATTR, @@ -130,7 +131,7 @@ const FS = { try { Environment.getEnvironment().then((envName: string) => { const isTestEmail = value.email !== undefined && value.email.startsWith('fullstory') && value.email.endsWith(CONST.EMAIL.QA_DOMAIN); - if ((CONST.ENVIRONMENT.PRODUCTION !== envName && !isTestEmail) || isExpensifyTeam(value?.email)) { + if ((CONST.ENVIRONMENT.PRODUCTION !== envName && !isTestEmail) || Str.extractEmailDomain(value.email ?? '') === CONST.EXPENSIFY_PARTNER_NAME) { // On web, if we started FS at some point in a browser, it will run forever. So let's shut it down if we don't want it to run. FullStory('shutdown'); return; From 6456aa99dcf25b902f1a7bfe80c39552188edf29 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Mon, 20 Jan 2025 13:56:11 -0300 Subject: [PATCH 088/179] intentionally running start for FS --- src/libs/Fullstory/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 972442380850..b6fc60c78f0e 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -137,6 +137,7 @@ const FS = { return; } FS.onReady().then(() => { + FullStory('start'); FS.consent(true); const localMetadata = value; localMetadata.environment = envName; From 9814a44b4b83375996881a062953a3a5d912b299 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Mon, 20 Jan 2025 13:56:54 -0300 Subject: [PATCH 089/179] reverting change --- src/libs/Fullstory/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index b6fc60c78f0e..972442380850 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -137,7 +137,6 @@ const FS = { return; } FS.onReady().then(() => { - FullStory('start'); FS.consent(true); const localMetadata = value; localMetadata.environment = envName; From c8382017a3f5b3a65a06d80443360e755dbdc18e Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Mon, 20 Jan 2025 13:59:00 -0300 Subject: [PATCH 090/179] adding restart in case we ran shutdown at some point for that browser. --- src/libs/Fullstory/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 972442380850..9596dd70fdcc 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -136,6 +136,7 @@ const FS = { FullStory('shutdown'); return; } + FullStory('restart'); FS.onReady().then(() => { FS.consent(true); const localMetadata = value; From 17f21df2c9ded970d15d3d92d7637101f07458ca Mon Sep 17 00:00:00 2001 From: Rutika Pawar <183392827+twilight2294@users.noreply.github.com> Date: Mon, 20 Jan 2025 17:54:13 +0000 Subject: [PATCH 091/179] fix lint --- src/libs/actions/Card.ts | 6 +-- .../expensifyCard/issueNew/AssigneeStep.tsx | 30 ++++++------ .../expensifyCard/issueNew/CardNameStep.tsx | 47 +++++++++--------- .../expensifyCard/issueNew/LimitStep.tsx | 4 +- .../expensifyCard/issueNew/LimitTypeStep.tsx | 6 +-- .../members/WorkspaceMemberDetailsPage.tsx | 49 +++++++++---------- .../members/WorkspaceMemberNewCardPage.tsx | 1 + 7 files changed, 73 insertions(+), 70 deletions(-) diff --git a/src/libs/actions/Card.ts b/src/libs/actions/Card.ts index f1b604b059e5..7ffea41b0384 100644 --- a/src/libs/actions/Card.ts +++ b/src/libs/actions/Card.ts @@ -37,7 +37,7 @@ type IssueNewCardFlowData = { data?: Partial; /** ID of the policy */ - policyID: string; + policyID: string | undefined; }; function reportVirtualExpensifyCardFraud(card: Card, validateCode: string) { @@ -388,14 +388,14 @@ function setIssueNewCardStepAndData({data, isEditing, step, policyID}: IssueNewC Onyx.merge(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, {data, isEditing, currentStep: step, errors: null}); } -function clearIssueNewCardFlow(policyID: string) { +function clearIssueNewCardFlow(policyID: string | undefined) { Onyx.set(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, { currentStep: null, data: {}, }); } -function clearIssueNewCardError(policyID: string) { +function clearIssueNewCardError(policyID: string | undefined) { Onyx.merge(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, {errors: null}); } diff --git a/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx b/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx index 1042671b52ff..f6841113b706 100644 --- a/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx @@ -12,11 +12,11 @@ import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import {formatPhoneNumber} from '@libs/LocalePhoneNumber'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; +import {getHeaderMessage, getSearchValueForPhoneOrEmail, sortAlphabetically} from '@libs/OptionsListUtils'; +import {getPersonalDetailByEmail, getUserNameByEmail} from '@libs/PersonalDetailsUtils'; +import {isDeletedPolicyEmployee} from '@libs/PolicyUtils'; import Navigation from '@navigation/Navigation'; -import * as Card from '@userActions/Card'; +import {clearIssueNewCardFlow, getCardDefaultName, setIssueNewCardStepAndData} from '@userActions/Card'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; @@ -33,7 +33,7 @@ function AssigneeStep({policy}: AssigneeStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const {isOffline} = useNetwork(); - const policyID = policy?.id ?? '-1'; + const policyID = policy?.id; const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); const isEditing = issueNewCard?.isEditing; @@ -45,12 +45,12 @@ function AssigneeStep({policy}: AssigneeStepProps) { assigneeEmail: assignee?.login ?? '', }; - if (isEditing && issueNewCard?.data?.cardTitle === Card.getCardDefaultName(PersonalDetailsUtils.getUserNameByEmail(issueNewCard?.data?.assigneeEmail, 'firstName'))) { + if (isEditing && issueNewCard?.data?.cardTitle === getCardDefaultName(getUserNameByEmail(issueNewCard?.data?.assigneeEmail, 'firstName'))) { // If the card title is the default card title, update it with the new assignee's name - data.cardTitle = Card.getCardDefaultName(PersonalDetailsUtils.getUserNameByEmail(assignee?.login ?? '', 'firstName')); + data.cardTitle = getCardDefaultName(getUserNameByEmail(assignee?.login ?? '', 'firstName')); } - Card.setIssueNewCardStepAndData({ + setIssueNewCardStepAndData({ step: isEditing ? CONST.EXPENSIFY_CARD.STEP.CONFIRMATION : CONST.EXPENSIFY_CARD.STEP.CARD_TYPE, data, isEditing: false, @@ -60,11 +60,11 @@ function AssigneeStep({policy}: AssigneeStepProps) { const handleBackButtonPress = () => { if (isEditing) { - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false, policyID}); + setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false, policyID}); return; } Navigation.goBack(); - Card.clearIssueNewCardFlow(policyID); + clearIssueNewCardFlow(policyID); }; const shouldShowSearchInput = policy?.employeeList && Object.keys(policy.employeeList).length >= MINIMUM_MEMBER_TO_SHOW_SEARCH; @@ -77,11 +77,11 @@ function AssigneeStep({policy}: AssigneeStepProps) { } Object.entries(policy.employeeList ?? {}).forEach(([email, policyEmployee]) => { - if (PolicyUtils.isDeletedPolicyEmployee(policyEmployee, isOffline)) { + if (isDeletedPolicyEmployee(policyEmployee, isOffline)) { return; } - const personalDetail = PersonalDetailsUtils.getPersonalDetailByEmail(email); + const personalDetail = getPersonalDetailByEmail(email); membersList.push({ keyForList: email, text: personalDetail?.displayName, @@ -99,7 +99,7 @@ function AssigneeStep({policy}: AssigneeStepProps) { }); }); - membersList = OptionsListUtils.sortAlphabetically(membersList, 'text'); + membersList = sortAlphabetically(membersList, 'text'); return membersList; }, [isOffline, policy?.employeeList]); @@ -114,7 +114,7 @@ function AssigneeStep({policy}: AssigneeStepProps) { ]; } - const searchValue = OptionsListUtils.getSearchValueForPhoneOrEmail(debouncedSearchTerm).toLowerCase(); + const searchValue = getSearchValueForPhoneOrEmail(debouncedSearchTerm).toLowerCase(); const filteredOptions = membersDetails.filter((option) => !!option.text?.toLowerCase().includes(searchValue) || !!option.alternateText?.toLowerCase().includes(searchValue)); return [ @@ -129,7 +129,7 @@ function AssigneeStep({policy}: AssigneeStepProps) { const headerMessage = useMemo(() => { const searchValue = debouncedSearchTerm.trim().toLowerCase(); - return OptionsListUtils.getHeaderMessage(sections[0].data.length !== 0, false, searchValue); + return getHeaderMessage(sections[0].data.length !== 0, false, searchValue); }, [debouncedSearchTerm, sections]); return ( diff --git a/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx b/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx index ef33f4c6cc0e..1cec58dfcc31 100644 --- a/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx @@ -9,11 +9,11 @@ import TextInput from '@components/TextInput'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as CardUtils from '@libs/CardUtils'; -import * as ErrorUtils from '@libs/ErrorUtils'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; -import * as ValidationUtils from '@libs/ValidationUtils'; -import * as Card from '@userActions/Card'; +import {getDefaultCardName} from '@libs/CardUtils'; +import {addErrorMessage} from '@libs/ErrorUtils'; +import {getUserNameByEmail} from '@libs/PersonalDetailsUtils'; +import {getFieldRequiredErrors} from '@libs/ValidationUtils'; +import {setIssueNewCardStepAndData} from '@userActions/Card'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/IssueNewExpensifyCardForm'; @@ -32,36 +32,39 @@ function CardNameStep({policyID}: CardNameStepProps) { const isEditing = issueNewCard?.isEditing; const data = issueNewCard?.data; - const userName = PersonalDetailsUtils.getUserNameByEmail(data?.assigneeEmail ?? '', 'firstName'); - const defaultCardTitle = data?.cardType !== CONST.EXPENSIFY_CARD.CARD_TYPE.VIRTUAL ? CardUtils.getDefaultCardName(userName) : ''; + const userName = getUserNameByEmail(data?.assigneeEmail ?? '', 'firstName'); + const defaultCardTitle = data?.cardType !== CONST.EXPENSIFY_CARD.CARD_TYPE.VIRTUAL ? getDefaultCardName(userName) : ''; const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, [INPUT_IDS.CARD_TITLE]); + const errors = getFieldRequiredErrors(values, [INPUT_IDS.CARD_TITLE]); const length = values.cardTitle.length; if (length > CONST.STANDARD_LENGTH_LIMIT) { - ErrorUtils.addErrorMessage(errors, INPUT_IDS.CARD_TITLE, translate('common.error.characterLimitExceedCounter', {length, limit: CONST.STANDARD_LENGTH_LIMIT})); + addErrorMessage(errors, INPUT_IDS.CARD_TITLE, translate('common.error.characterLimitExceedCounter', {length, limit: CONST.STANDARD_LENGTH_LIMIT})); } return errors; }; - const submit = useCallback((values: FormOnyxValues) => { - Card.setIssueNewCardStepAndData({ - step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, - data: { - cardTitle: values.cardTitle, - }, - isEditing: false, - policyID, - }); - }, []); + const submit = useCallback( + (values: FormOnyxValues) => { + setIssueNewCardStepAndData({ + step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, + data: { + cardTitle: values.cardTitle, + }, + isEditing: false, + policyID, + }); + }, + [policyID], + ); const handleBackButtonPress = useCallback(() => { if (isEditing) { - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false, policyID}); + setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false, policyID}); return; } - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.LIMIT, policyID}); - }, [isEditing]); + setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.LIMIT, policyID}); + }, [isEditing, policyID]); return ( { @@ -47,7 +47,7 @@ function LimitStep({policyID}: LimitStepProps) { return; } Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.LIMIT_TYPE, policyID}); - }, [isEditing]); + }, [isEditing, policyID]); const validate = useCallback( (values: FormOnyxValues): FormInputErrors => { diff --git a/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx b/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx index 9491eba48e95..3a5a6bfbd70e 100644 --- a/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx @@ -22,7 +22,7 @@ type LimitTypeStepProps = { function LimitTypeStep({policy}: LimitTypeStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const policyID = policy?.id ?? '-1'; + const policyID = policy?.id; const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); const areApprovalsConfigured = PolicyUtils.getApprovalWorkflow(policy) !== CONST.POLICY.APPROVAL_MODE.OPTIONAL; @@ -39,7 +39,7 @@ function LimitTypeStep({policy}: LimitTypeStepProps) { isEditing: false, policyID, }); - }, [isEditing, typeSelected]); + }, [isEditing, typeSelected, policyID]); const handleBackButtonPress = useCallback(() => { if (isEditing) { @@ -47,7 +47,7 @@ function LimitTypeStep({policy}: LimitTypeStepProps) { return; } Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CARD_TYPE, policyID}); - }, [isEditing]); + }, [isEditing, policyID]); const data = useMemo(() => { const options = []; diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index 7f9439298855..764a7b5635da 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -20,11 +20,14 @@ import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as CardUtils from '@libs/CardUtils'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; +import {setIssueNewCardStepAndData} from '@libs/actions/Card'; +import {openPolicyCompanyCardsPage} from '@libs/actions/CompanyCards'; +import {clearWorkspaceOwnerChangeFlow, isApprover, removeMembers, requestWorkspaceOwnerChange, updateWorkspaceMembersRole} from '@libs/actions/Policy/Member'; +import {getAllCardsForWorkspace, getCardFeedIcon, getCompanyFeeds, maskCardNumber} from '@libs/CardUtils'; +import {convertToDisplayString} from '@libs/CurrencyUtils'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; +import {getDisplayNameOrDefault} from '@libs/PersonalDetailsUtils'; +import {getWorkspaceAccountID} from '@libs/PolicyUtils'; import shouldRenderTransferOwnerButton from '@libs/shouldRenderTransferOwnerButton'; import Navigation from '@navigation/Navigation'; import type {SettingsNavigatorParamList} from '@navigation/types'; @@ -33,9 +36,6 @@ import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; import variables from '@styles/variables'; -import * as Card from '@userActions/Card'; -import * as CompanyCards from '@userActions/CompanyCards'; -import * as Member from '@userActions/Policy/Member'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -55,7 +55,7 @@ type WorkspaceMemberDetailsPageProps = Omit !feed.pending).length > 0; + const policyOwnerDisplayName = formatPhoneNumber(getDisplayNameOrDefault(ownerDetails)) ?? policy?.owner ?? ''; + const hasMultipleFeeds = Object.values(getCompanyFeeds(cardFeeds)).filter((feed) => !feed.pending).length > 0; - const workspaceCards = CardUtils.getAllCardsForWorkspace(workspaceAccountID); + const workspaceCards = getAllCardsForWorkspace(workspaceAccountID); const hasWorkspaceCardsAssigned = !!workspaceCards && !!Object.values(workspaceCards).length; useEffect(() => { - CompanyCards.openPolicyCompanyCardsPage(policyID, workspaceAccountID); + openPolicyCompanyCardsPage(policyID, workspaceAccountID); }, [policyID, workspaceAccountID]); const memberCards = useMemo(() => { @@ -97,8 +97,8 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM }, [accountID, workspaceCards]); const confirmModalPrompt = useMemo(() => { - const isApprover = Member.isApprover(policy, accountID); - if (!isApprover) { + const isUserApprover = isApprover(policy, accountID); + if (!isUserApprover) { return translate('workspace.people.removeMemberPrompt', {memberName: displayName}); } return translate('workspace.people.removeMembersWarningPrompt', { @@ -146,7 +146,7 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM }; const removeUser = useCallback(() => { - Member.removeMembers([accountID], policyID); + removeMembers([accountID], policyID); setIsRemoveMemberConfirmModalVisible(false); }, [accountID, policyID]); @@ -172,12 +172,13 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM } const activeRoute = Navigation.getActiveRoute(); - Card.setIssueNewCardStepAndData({ + setIssueNewCardStepAndData({ step: CONST.EXPENSIFY_CARD.STEP.CARD_TYPE, data: { assigneeEmail: memberLogin, }, isEditing: false, + policyID, }); Navigation.navigate(ROUTES.WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW.getRoute(policyID, activeRoute)); }, [accountID, hasMultipleFeeds, memberLogin, policyID]); @@ -189,14 +190,14 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM const changeRole = useCallback( ({value}: ListItemType) => { setIsRoleSelectionModalVisible(false); - Member.updateWorkspaceMembersRole(policyID, [accountID], value); + updateWorkspaceMembersRole(policyID, [accountID], value); }, [accountID, policyID], ); const startChangeOwnershipFlow = useCallback(() => { - Member.clearWorkspaceOwnerChangeFlow(policyID); - Member.requestWorkspaceOwnerChange(policyID); + clearWorkspaceOwnerChangeFlow(policyID); + requestWorkspaceOwnerChange(policyID); Navigation.navigate(ROUTES.WORKSPACE_OWNER_CHANGE_CHECK.getRoute(policyID, accountID, 'amountOwed' as ValueOf)); }, [accountID, policyID]); @@ -315,13 +316,11 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM > Date: Mon, 20 Jan 2025 14:59:07 -0300 Subject: [PATCH 092/179] fixing FS issue where we were not identifying the users --- src/libs/Fullstory/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 9596dd70fdcc..95888d98d457 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -1,6 +1,5 @@ import {FullStory, init, isInitialized} from '@fullstory/browser'; import type {OnyxEntry} from 'react-native-onyx'; -import {isExpensifyTeam} from '@libs/PolicyUtils'; import {isConciergeChatReport, shouldUnmaskChat} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import * as Environment from '@src/libs/Environment/Environment'; @@ -104,6 +103,12 @@ const FS = { new Promise((resolve) => { if (!isInitialized()) { init({orgId: ''}, resolve); + + // FS init function might have a race condition with the head snippet. If the head snipped is loaded first, + // then the init function will not call the resolve function, and we'll never identify the user logging in, + // and we need to call resolve manually. We're adding a 1s timeout to make sure the init function has enough + // time to call the resolve function in case it ran successfully. + setTimeout(resolve, 1000); } else { FullStory('observe', {type: 'start', callback: resolve}); } From de9421fe26f9ab8079064cf7c716f5900ef78bd9 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Mon, 20 Jan 2025 14:59:37 -0300 Subject: [PATCH 093/179] removing unecessary import --- src/libs/Fullstory/index.native.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts index 03a52a0a603e..000b3c9405c3 100644 --- a/src/libs/Fullstory/index.native.ts +++ b/src/libs/Fullstory/index.native.ts @@ -1,6 +1,5 @@ import FullStory, {FSPage} from '@fullstory/react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {isExpensifyTeam} from '@libs/PolicyUtils'; import {isConciergeChatReport, shouldUnmaskChat} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import * as Environment from '@src/libs/Environment/Environment'; From b11f492bfb152fdad9ed7de8d83aaa7a2e6e5e70 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Mon, 20 Jan 2025 15:00:21 -0300 Subject: [PATCH 094/179] prettier --- src/libs/Fullstory/index.native.ts | 2 +- src/libs/Fullstory/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts index 000b3c9405c3..d1f1bc2223d2 100644 --- a/src/libs/Fullstory/index.native.ts +++ b/src/libs/Fullstory/index.native.ts @@ -1,10 +1,10 @@ import FullStory, {FSPage} from '@fullstory/react-native'; +import {Str} from 'expensify-common'; import type {OnyxEntry} from 'react-native-onyx'; import {isConciergeChatReport, shouldUnmaskChat} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import * as Environment from '@src/libs/Environment/Environment'; import type {OnyxInputOrEntry, PersonalDetailsList, Report, UserMetadata} from '@src/types/onyx'; -import { Str } from 'expensify-common'; /** * Fullstory React-Native lib adapter diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 95888d98d457..7fe87b60192d 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -1,11 +1,11 @@ import {FullStory, init, isInitialized} from '@fullstory/browser'; +import {Str} from 'expensify-common'; import type {OnyxEntry} from 'react-native-onyx'; import {isConciergeChatReport, shouldUnmaskChat} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import * as Environment from '@src/libs/Environment/Environment'; import type {OnyxInputOrEntry, PersonalDetailsList, Report, UserMetadata} from '@src/types/onyx'; import type NavigationProperties from './types'; -import { Str } from 'expensify-common'; /** * Extract values from non-scraped at build time attribute WEB_PROP_ATTR, @@ -104,7 +104,7 @@ const FS = { if (!isInitialized()) { init({orgId: ''}, resolve); - // FS init function might have a race condition with the head snippet. If the head snipped is loaded first, + // FS init function might have a race condition with the head snippet. If the head snipped is loaded first, // then the init function will not call the resolve function, and we'll never identify the user logging in, // and we need to call resolve manually. We're adding a 1s timeout to make sure the init function has enough // time to call the resolve function in case it ran successfully. From 1a04911630e1fa76f02df0c594f9375da7e66700 Mon Sep 17 00:00:00 2001 From: Rutika Pawar <183392827+twilight2294@users.noreply.github.com> Date: Mon, 20 Jan 2025 18:24:43 +0000 Subject: [PATCH 095/179] fix lint --- .../parameters/CreateExpensifyCardParams.ts | 2 +- .../parameters/StartIssueNewCardFlowParams.ts | 2 +- src/libs/actions/Card.ts | 4 +-- .../expensifyCard/issueNew/CardNameStep.tsx | 2 +- .../expensifyCard/issueNew/CardTypeStep.tsx | 10 +++--- .../issueNew/ConfirmationStep.tsx | 34 +++++++++---------- .../issueNew/IssueNewCardPage.tsx | 6 ++-- .../expensifyCard/issueNew/LimitStep.tsx | 20 +++++------ 8 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/libs/API/parameters/CreateExpensifyCardParams.ts b/src/libs/API/parameters/CreateExpensifyCardParams.ts index f6aa9eb8e512..d90b7649a7d6 100644 --- a/src/libs/API/parameters/CreateExpensifyCardParams.ts +++ b/src/libs/API/parameters/CreateExpensifyCardParams.ts @@ -1,5 +1,5 @@ type CreateExpensifyCardParams = { - policyID: string; + policyID: string | undefined; assigneeEmail: string; limit: number; limitType: string; diff --git a/src/libs/API/parameters/StartIssueNewCardFlowParams.ts b/src/libs/API/parameters/StartIssueNewCardFlowParams.ts index 8ed04b756a10..8bff276b9cd0 100644 --- a/src/libs/API/parameters/StartIssueNewCardFlowParams.ts +++ b/src/libs/API/parameters/StartIssueNewCardFlowParams.ts @@ -1,5 +1,5 @@ type StartIssueNewCardFlowParams = { - policyID: string; + policyID: string | undefined; }; export default StartIssueNewCardFlowParams; diff --git a/src/libs/actions/Card.ts b/src/libs/actions/Card.ts index 7ffea41b0384..17943440c1f1 100644 --- a/src/libs/actions/Card.ts +++ b/src/libs/actions/Card.ts @@ -667,7 +667,7 @@ function deactivateCard(workspaceAccountID: number, card?: Card) { API.write(WRITE_COMMANDS.CARD_DEACTIVATE, parameters, {optimisticData, failureData}); } -function startIssueNewCardFlow(policyID: string) { +function startIssueNewCardFlow(policyID: string | undefined) { const parameters: StartIssueNewCardFlowParams = { policyID, }; @@ -728,7 +728,7 @@ function configureExpensifyCardsForPolicy(policyID: string, bankAccountID?: numb }); } -function issueExpensifyCard(policyID: string, feedCountry: string, validateCode: string, data?: IssueNewCardData) { +function issueExpensifyCard(policyID: string | undefined, feedCountry: string, validateCode: string, data?: IssueNewCardData) { if (!data) { return; } diff --git a/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx b/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx index 1cec58dfcc31..81ac308b746b 100644 --- a/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx @@ -20,7 +20,7 @@ import INPUT_IDS from '@src/types/form/IssueNewExpensifyCardForm'; type CardNameStepProps = { /** ID of the policy */ - policyID: string; + policyID: string | undefined; }; function CardNameStep({policyID}: CardNameStepProps) { diff --git a/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx b/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx index 3714c5177c03..cd56d7b3dd56 100644 --- a/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx @@ -8,14 +8,14 @@ import MenuItem from '@components/MenuItem'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import {setIssueNewCardStepAndData} from '@libs/actions/Card'; import variables from '@styles/variables'; -import * as Card from '@userActions/Card'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; type CardTypeStepProps = { /** ID of the policy */ - policyID: string; + policyID: string | undefined; }; function CardTypeStep({policyID}: CardTypeStepProps) { @@ -26,7 +26,7 @@ function CardTypeStep({policyID}: CardTypeStepProps) { const isEditing = issueNewCard?.isEditing; const submit = (value: ValueOf) => { - Card.setIssueNewCardStepAndData({ + setIssueNewCardStepAndData({ step: isEditing ? CONST.EXPENSIFY_CARD.STEP.CONFIRMATION : CONST.EXPENSIFY_CARD.STEP.LIMIT_TYPE, data: { cardType: value, @@ -38,10 +38,10 @@ function CardTypeStep({policyID}: CardTypeStepProps) { const handleBackButtonPress = () => { if (isEditing) { - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false, policyID}); + setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false, policyID}); return; } - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.ASSIGNEE, policyID}); + setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.ASSIGNEE, policyID}); }; return ( diff --git a/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx b/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx index 514ab49795db..ce3a98ba6158 100644 --- a/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx @@ -11,13 +11,13 @@ import useBeforeRemove from '@hooks/useBeforeRemove'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; +import {clearIssueNewCardError, clearIssueNewCardFlow, issueExpensifyCard, setIssueNewCardStepAndData} from '@libs/actions/Card'; +import {requestValidateCodeAction, resetValidateActionCodeSent} from '@libs/actions/User'; import {getTranslationKeyForLimitType} from '@libs/CardUtils'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; -import * as ErrorUtils from '@libs/ErrorUtils'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; +import {convertToShortDisplayString} from '@libs/CurrencyUtils'; +import {getLatestErrorMessage, getLatestErrorMessageField} from '@libs/ErrorUtils'; +import {getUserNameByEmail} from '@libs/PersonalDetailsUtils'; import Navigation from '@navigation/Navigation'; -import * as Card from '@userActions/Card'; -import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -26,7 +26,7 @@ import type {IssueNewCardStep} from '@src/types/onyx/Card'; type ConfirmationStepProps = { /** ID of the policy that the card will be issued under */ - policyID: string; + policyID: string | undefined; /** Route to navigate to */ backTo?: Route; @@ -39,7 +39,7 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) { const [account] = useOnyx(ONYXKEYS.ACCOUNT); const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE); - const validateError = ErrorUtils.getLatestErrorMessageField(issueNewCard); + const validateError = getLatestErrorMessageField(issueNewCard); const [isValidateCodeActionModalVisible, setIsValidateCodeActionModalVisible] = useState(false); const data = issueNewCard?.data; const isSuccessful = issueNewCard?.isSuccessful; @@ -51,7 +51,7 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) { useEffect(() => { submitButton.current?.focus(); - User.resetValidateActionCodeSent(); + resetValidateActionCodeSent(); }, []); useEffect(() => { @@ -59,21 +59,21 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) { return; } Navigation.navigate(backTo ?? ROUTES.WORKSPACE_EXPENSIFY_CARD.getRoute(policyID ?? '-1')); - Card.clearIssueNewCardFlow(policyID); + clearIssueNewCardFlow(policyID); }, [backTo, policyID, isSuccessful]); const submit = (validateCode: string) => { - Card.issueExpensifyCard(policyID, CONST.COUNTRY.US, validateCode, data); + issueExpensifyCard(policyID, CONST.COUNTRY.US, validateCode, data); }; - const errorMessage = ErrorUtils.getLatestErrorMessage(issueNewCard); + const errorMessage = getLatestErrorMessage(issueNewCard); const editStep = (step: IssueNewCardStep) => { - Card.setIssueNewCardStepAndData({step, isEditing: true, policyID}); + setIssueNewCardStepAndData({step, isEditing: true, policyID}); }; const handleBackButtonPress = () => { - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CARD_NAME, policyID}); + setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CARD_NAME, policyID}); }; const translationForLimitType = getTranslationKeyForLimitType(data?.limitType); @@ -96,7 +96,7 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) { {translate('workspace.card.issueNewCard.willBeReady')} editStep(CONST.EXPENSIFY_CARD.STEP.ASSIGNEE)} /> @@ -108,7 +108,7 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) { /> editStep(CONST.EXPENSIFY_CARD.STEP.LIMIT)} /> @@ -140,10 +140,10 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) { User.requestValidateCodeAction()} + sendValidateCode={() => requestValidateCodeAction()} validateError={validateError} hasMagicCodeBeenSent={validateCodeSent} - clearError={() => Card.clearIssueNewCardError(policyID)} + clearError={() => clearIssueNewCardError(policyID)} onClose={() => setIsValidateCodeActionModalVisible(false)} isVisible={isValidateCodeActionModalVisible} title={translate('cardPage.validateCardTitle')} diff --git a/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx b/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx index 195e30d39d16..2d25255b136f 100644 --- a/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx @@ -2,12 +2,12 @@ import React, {useEffect} from 'react'; import {useOnyx} from 'react-native-onyx'; import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper'; import ScreenWrapper from '@components/ScreenWrapper'; +import {startIssueNewCardFlow} from '@libs/actions/Card'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; -import * as Card from '@userActions/Card'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -25,13 +25,13 @@ function IssueNewCardPage({policy, route}: IssueNewCardPageProps) { const {currentStep} = issueNewCard ?? {}; - const policyID = policy?.id ?? '-1'; + const policyID = policy?.id; const backTo = route?.params?.backTo; const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate}); useEffect(() => { - Card.startIssueNewCardFlow(policyID); + startIssueNewCardFlow(policyID); }, [policyID]); const getCurrentStep = () => { diff --git a/src/pages/workspace/expensifyCard/issueNew/LimitStep.tsx b/src/pages/workspace/expensifyCard/issueNew/LimitStep.tsx index aaf862b95f4b..84b607bc406f 100644 --- a/src/pages/workspace/expensifyCard/issueNew/LimitStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/LimitStep.tsx @@ -9,16 +9,16 @@ import Text from '@components/Text'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; -import * as ValidationUtils from '@libs/ValidationUtils'; -import * as Card from '@userActions/Card'; +import {setIssueNewCardStepAndData} from '@libs/actions/Card'; +import {convertToBackendAmount, convertToFrontendAmountAsString} from '@libs/CurrencyUtils'; +import {getFieldRequiredErrors} from '@libs/ValidationUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/IssueNewExpensifyCardForm'; type LimitStepProps = { /** ID of the policy */ - policyID: string; + policyID: string | undefined; }; function LimitStep({policyID}: LimitStepProps) { @@ -30,8 +30,8 @@ function LimitStep({policyID}: LimitStepProps) { const submit = useCallback( (values: FormOnyxValues) => { - const limit = CurrencyUtils.convertToBackendAmount(Number(values?.limit)); - Card.setIssueNewCardStepAndData({ + const limit = convertToBackendAmount(Number(values?.limit)); + setIssueNewCardStepAndData({ step: isEditing ? CONST.EXPENSIFY_CARD.STEP.CONFIRMATION : CONST.EXPENSIFY_CARD.STEP.CARD_NAME, data: {limit}, isEditing: false, @@ -43,15 +43,15 @@ function LimitStep({policyID}: LimitStepProps) { const handleBackButtonPress = useCallback(() => { if (isEditing) { - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false, policyID}); + setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false, policyID}); return; } - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.LIMIT_TYPE, policyID}); + setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.LIMIT_TYPE, policyID}); }, [isEditing, policyID]); const validate = useCallback( (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, [INPUT_IDS.LIMIT]); + const errors = getFieldRequiredErrors(values, [INPUT_IDS.LIMIT]); // We only want integers to be sent as the limit if (!Number(values.limit)) { @@ -93,7 +93,7 @@ function LimitStep({policyID}: LimitStepProps) { > Date: Mon, 20 Jan 2025 18:39:15 +0000 Subject: [PATCH 096/179] fix eslint --- src/ROUTES.ts | 2 +- .../expensifyCard/issueNew/ConfirmationStep.tsx | 2 +- .../expensifyCard/issueNew/LimitTypeStep.tsx | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index ca869eb54d4b..5c68a25896b3 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1274,7 +1274,7 @@ const ROUTES = { }, WORKSPACE_EXPENSIFY_CARD: { route: 'settings/workspaces/:policyID/expensify-card', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card` as const, + getRoute: (policyID: string | undefined) => `settings/workspaces/${policyID}/expensify-card` as const, }, WORKSPACE_EXPENSIFY_CARD_DETAILS: { route: 'settings/workspaces/:policyID/expensify-card/:cardID', diff --git a/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx b/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx index ce3a98ba6158..b4cb6d8f6a95 100644 --- a/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx @@ -58,7 +58,7 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) { if (!isSuccessful) { return; } - Navigation.navigate(backTo ?? ROUTES.WORKSPACE_EXPENSIFY_CARD.getRoute(policyID ?? '-1')); + Navigation.navigate(backTo ?? ROUTES.WORKSPACE_EXPENSIFY_CARD.getRoute(policyID)); clearIssueNewCardFlow(policyID); }, [backTo, policyID, isSuccessful]); diff --git a/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx b/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx index 3a5a6bfbd70e..1f5cbad3f63b 100644 --- a/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx @@ -8,8 +8,8 @@ import RadioListItem from '@components/SelectionList/RadioListItem'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as Card from '@userActions/Card'; +import {setIssueNewCardStepAndData} from '@libs/actions/Card'; +import {getApprovalWorkflow} from '@libs/PolicyUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; @@ -25,7 +25,7 @@ function LimitTypeStep({policy}: LimitTypeStepProps) { const policyID = policy?.id; const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); - const areApprovalsConfigured = PolicyUtils.getApprovalWorkflow(policy) !== CONST.POLICY.APPROVAL_MODE.OPTIONAL; + const areApprovalsConfigured = getApprovalWorkflow(policy) !== CONST.POLICY.APPROVAL_MODE.OPTIONAL; const defaultType = areApprovalsConfigured ? CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART : CONST.EXPENSIFY_CARD.LIMIT_TYPES.MONTHLY; const [typeSelected, setTypeSelected] = useState(issueNewCard?.data?.limitType ?? defaultType); @@ -33,7 +33,7 @@ function LimitTypeStep({policy}: LimitTypeStepProps) { const isEditing = issueNewCard?.isEditing; const submit = useCallback(() => { - Card.setIssueNewCardStepAndData({ + setIssueNewCardStepAndData({ step: isEditing ? CONST.EXPENSIFY_CARD.STEP.CONFIRMATION : CONST.EXPENSIFY_CARD.STEP.LIMIT, data: {limitType: typeSelected}, isEditing: false, @@ -43,10 +43,10 @@ function LimitTypeStep({policy}: LimitTypeStepProps) { const handleBackButtonPress = useCallback(() => { if (isEditing) { - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false, policyID}); + setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CONFIRMATION, isEditing: false, policyID}); return; } - Card.setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CARD_TYPE, policyID}); + setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CARD_TYPE, policyID}); }, [isEditing, policyID]); const data = useMemo(() => { From 9aa20043aa54bfb35d08652e614802a5bbbf6be4 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 21 Jan 2025 11:08:39 +0700 Subject: [PATCH 097/179] fix: Pay button does not change to Review button --- src/libs/actions/IOU.ts | 41 ++++++++++++++----- .../request/step/IOURequestStepCategory.tsx | 3 +- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 26ebf5e37a8e..3c80839c24ed 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3200,6 +3200,7 @@ function getUpdateMoneyRequestParams( policyTagList: OnyxTypes.OnyxInputOrEntry, policyCategories: OnyxTypes.OnyxInputOrEntry, violations?: OnyxEntry, + hash?: number, ): UpdateMoneyRequestData { const optimisticData: OnyxUpdate[] = []; const successData: OnyxUpdate[] = []; @@ -3492,21 +3493,40 @@ function getUpdateMoneyRequestParams( if (policy && isPaidGroupPolicy(policy) && updatedTransaction && (hasModifiedTag || hasModifiedCategory || hasModifiedDistanceRate)) { const currentTransactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`] ?? []; - optimisticData.push( - ViolationsUtils.getViolationsOnyxData( - updatedTransaction, - currentTransactionViolations, - policy, - policyTagList ?? {}, - policyCategories ?? {}, - hasDependentTags(policy, policyTagList ?? {}), - ), + const violationsOnyxdata = ViolationsUtils.getViolationsOnyxData( + updatedTransaction, + currentTransactionViolations, + policy, + policyTagList ?? {}, + policyCategories ?? {}, + hasDependentTags(policy, policyTagList ?? {}), ); + optimisticData.push(violationsOnyxdata); failureData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, value: currentTransactionViolations, }); + if (hash) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, + value: { + data: { + [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]: violationsOnyxdata.value, + }, + }, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, + value: { + data: { + [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]: currentTransactionViolations, + }, + }, + }); + } } // Reset the transaction thread to its original state @@ -3922,12 +3942,13 @@ function updateMoneyRequestCategory( policy: OnyxEntry, policyTagList: OnyxEntry, policyCategories: OnyxEntry, + hash: number, ) { const transactionChanges: TransactionChanges = { category, }; - const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTagList, policyCategories); + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTagList, policyCategories, undefined, hash); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_CATEGORY, params, onyxData); } diff --git a/src/pages/iou/request/step/IOURequestStepCategory.tsx b/src/pages/iou/request/step/IOURequestStepCategory.tsx index 87a56e977817..d2615c406fc4 100644 --- a/src/pages/iou/request/step/IOURequestStepCategory.tsx +++ b/src/pages/iou/request/step/IOURequestStepCategory.tsx @@ -63,6 +63,7 @@ function IOURequestStepCategory({ const report = reportReal ?? reportDraft; const policy = policyReal ?? policyDraft; const policyCategories = policyCategoriesReal ?? policyCategoriesDraft; + const {currentSearchHash} = useSearchContext(); const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); @@ -120,7 +121,7 @@ function IOURequestStepCategory({ } if (isEditing && report) { - IOU.updateMoneyRequestCategory(transaction.transactionID, report.reportID, updatedCategory, policy, policyTags, policyCategories); + IOU.updateMoneyRequestCategory(transaction.transactionID, report.reportID, updatedCategory, policy, policyTags, policyCategories, currentSearchHash); navigateBack(); return; } From ab017bec6ef9ab14c76d21f31f9dbdb6d6e8be21 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 21 Jan 2025 11:19:07 +0700 Subject: [PATCH 098/179] fix ts --- src/libs/actions/IOU.ts | 2 +- src/pages/iou/request/step/IOURequestStepCategory.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 3c80839c24ed..4878415c0a05 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3942,7 +3942,7 @@ function updateMoneyRequestCategory( policy: OnyxEntry, policyTagList: OnyxEntry, policyCategories: OnyxEntry, - hash: number, + hash?: number, ) { const transactionChanges: TransactionChanges = { category, diff --git a/src/pages/iou/request/step/IOURequestStepCategory.tsx b/src/pages/iou/request/step/IOURequestStepCategory.tsx index d2615c406fc4..45da311ccf26 100644 --- a/src/pages/iou/request/step/IOURequestStepCategory.tsx +++ b/src/pages/iou/request/step/IOURequestStepCategory.tsx @@ -7,6 +7,7 @@ import Button from '@components/Button'; import CategoryPicker from '@components/CategoryPicker'; import FixedFooter from '@components/FixedFooter'; import * as Illustrations from '@components/Icon/Illustrations'; +import {useSearchContext} from '@components/Search/SearchContext'; import type {ListItem} from '@components/SelectionList/types'; import Text from '@components/Text'; import WorkspaceEmptyStateSection from '@components/WorkspaceEmptyStateSection'; From 49b526a2c6ae54ebeb3a0eba237897f86b3dedf5 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Tue, 21 Jan 2025 12:50:04 +0530 Subject: [PATCH 099/179] fix: Workspace - Default label tooltip content is truncated when language is Spanish. Signed-off-by: krishna2323 --- src/pages/workspace/WorkspacesListRow.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/workspace/WorkspacesListRow.tsx b/src/pages/workspace/WorkspacesListRow.tsx index 3c270d708e11..8da14d79c14d 100644 --- a/src/pages/workspace/WorkspacesListRow.tsx +++ b/src/pages/workspace/WorkspacesListRow.tsx @@ -149,6 +149,7 @@ function WorkspacesListRow({ Date: Tue, 21 Jan 2025 13:03:38 +0530 Subject: [PATCH 100/179] fix ESLint. Signed-off-by: krishna2323 --- src/pages/workspace/WorkspacesListRow.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/WorkspacesListRow.tsx b/src/pages/workspace/WorkspacesListRow.tsx index 8da14d79c14d..30ce3d221175 100644 --- a/src/pages/workspace/WorkspacesListRow.tsx +++ b/src/pages/workspace/WorkspacesListRow.tsx @@ -18,7 +18,7 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; +import {getDisplayNameOrDefault, getPersonalDetailsByIDs} from '@libs/PersonalDetailsUtils'; import {getUserFriendlyWorkspaceType} from '@libs/PolicyUtils'; import type {AvatarSource} from '@libs/UserUtils'; import type {AnchorPosition} from '@styles/index'; @@ -120,7 +120,7 @@ function WorkspacesListRow({ const threeDotsMenuContainerRef = useRef(null); const {shouldUseNarrowLayout} = useResponsiveLayout(); - const ownerDetails = ownerAccountID && PersonalDetailsUtils.getPersonalDetailsByIDs([ownerAccountID], currentUserPersonalDetails.accountID).at(0); + const ownerDetails = ownerAccountID && getPersonalDetailsByIDs([ownerAccountID], currentUserPersonalDetails.accountID).at(0); if (layoutWidth === CONST.LAYOUT_WIDTH.NONE) { // To prevent layout from jumping or rendering for a split second, when @@ -226,7 +226,7 @@ function WorkspacesListRow({ Date: Tue, 21 Jan 2025 16:21:55 +0700 Subject: [PATCH 101/179] remove border radius --- src/styles/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index 98ee4e4d63b1..6c98aeeaad9d 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5348,7 +5348,6 @@ const styles = (theme: ThemeColors) => height: 2, width: '100%', backgroundColor: theme.transparent, - borderRadius: 2, overflow: 'hidden', marginBottom: -1, }, From 84d6eb5c1c2932b042ad1b3cde8ffbf5bd0d73ec Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Tue, 21 Jan 2025 16:24:37 +0100 Subject: [PATCH 102/179] fix rest of the lint issues --- .eslintrc.changed.js | 1 - src/ROUTES.ts | 8 ++- .../LHNOptionsList/OptionRowLHN.tsx | 1 - .../ReportActionItem/MoneyReportView.tsx | 62 +++++++++++-------- src/components/ReportActionItem/TaskView.tsx | 41 ++++++------ src/libs/TaskUtils.ts | 16 ++--- src/libs/actions/ReportActions.ts | 20 +++--- src/libs/actions/Task.ts | 10 +-- .../report/ReportActionItemContentCreated.tsx | 24 +++---- .../home/report/ReportActionItemCreated.tsx | 16 ++--- 10 files changed, 104 insertions(+), 95 deletions(-) diff --git a/.eslintrc.changed.js b/.eslintrc.changed.js index 741c7710df66..3d25790cc389 100644 --- a/.eslintrc.changed.js +++ b/.eslintrc.changed.js @@ -25,7 +25,6 @@ module.exports = { 'src/libs/actions/IOU.ts', 'src/libs/actions/Report.ts', 'src/pages/workspace/WorkspaceInitialPage.tsx', - // 'src/pages/home/report/PureReportActionItem.tsx', 'src/libs/SidebarUtils.ts', ], rules: { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 1746a6bcc62c..d2ff98519d51 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -321,8 +321,12 @@ const ROUTES = { }, EDIT_REPORT_FIELD_REQUEST: { route: 'r/:reportID/edit/policyField/:policyID/:fieldID', - getRoute: (reportID: string, policyID: string, fieldID: string, backTo?: string) => - getUrlWithBackToParam(`r/${reportID}/edit/policyField/${policyID}/${encodeURIComponent(fieldID)}` as const, backTo), + getRoute: (reportID: string, policyID: string | undefined, fieldID: string, backTo?: string) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the EDIT_REPORT_FIELD_REQUEST route'); + } + return getUrlWithBackToParam(`r/${reportID}/edit/policyField/${policyID}/${encodeURIComponent(fieldID)}` as const, backTo); + }, }, REPORT_WITH_ID_DETAILS_SHARE_CODE: { route: 'r/:reportID/details/shareCode', diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 2dc93aa8c9c1..6433df1ddea3 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -55,7 +55,6 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti const [isScreenFocused, setIsScreenFocused] = useState(false); const {shouldUseNarrowLayout} = useResponsiveLayout(); - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${optionItem?.reportID}`); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx index 1cce6bffe467..ca9f0ea87f55 100644 --- a/src/components/ReportActionItem/MoneyReportView.tsx +++ b/src/components/ReportActionItem/MoneyReportView.tsx @@ -16,12 +16,25 @@ import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; +import {convertToDisplayString} from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as ReportUtils from '@libs/ReportUtils'; +import { + getAvailableReportFields, + getFieldViolation, + getFieldViolationTranslation, + getMoneyRequestSpendBreakdown, + getReportFieldKey, + hasUpdatedTotal, + isClosedExpenseReportWithNoExpenses as isClosedExpenseReportWithNoExpensesReportUtils, + isInvoiceReport as isInvoiceReportUtils, + isPaidGroupPolicyExpenseReport as isPaidGroupPolicyExpenseReportUtils, + isReportFieldDisabled, + isReportFieldOfTypeTitle, + isSettled as isSettledReportUtils, +} from '@libs/ReportUtils'; import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground'; import variables from '@styles/variables'; -import * as reportActions from '@src/libs/actions/Report'; +import {clearReportFieldKeyErrors} from '@src/libs/actions/Report'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Policy, PolicyReportField, Report} from '@src/types/onyx'; @@ -52,15 +65,15 @@ function MoneyReportView({report, policy, isCombinedReport = false, shouldShowTo const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const {isOffline} = useNetwork(); - const isSettled = ReportUtils.isSettled(report?.reportID); - const isTotalUpdated = ReportUtils.hasUpdatedTotal(report, policy); + const isSettled = isSettledReportUtils(report?.reportID); + const isTotalUpdated = hasUpdatedTotal(report, policy); - const {totalDisplaySpend, nonReimbursableSpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(report); + const {totalDisplaySpend, nonReimbursableSpend, reimbursableSpend} = getMoneyRequestSpendBreakdown(report); const shouldShowBreakdown = nonReimbursableSpend && reimbursableSpend && shouldShowTotal; - const formattedTotalAmount = CurrencyUtils.convertToDisplayString(totalDisplaySpend, report?.currency); - const formattedOutOfPocketAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, report?.currency); - const formattedCompanySpendAmount = CurrencyUtils.convertToDisplayString(nonReimbursableSpend, report?.currency); + const formattedTotalAmount = convertToDisplayString(totalDisplaySpend, report?.currency); + const formattedOutOfPocketAmount = convertToDisplayString(reimbursableSpend, report?.currency); + const formattedCompanySpendAmount = convertToDisplayString(nonReimbursableSpend, report?.currency); const isPartiallyPaid = !!report?.pendingFields?.partial; const subAmountTextStyles: StyleProp = [ @@ -73,15 +86,15 @@ function MoneyReportView({report, policy, isCombinedReport = false, shouldShowTo const [violations] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_VIOLATIONS}${report?.reportID}`); const sortedPolicyReportFields = useMemo((): PolicyReportField[] => { - const fields = ReportUtils.getAvailableReportFields(report, Object.values(policy?.fieldList ?? {})); + const fields = getAvailableReportFields(report, Object.values(policy?.fieldList ?? {})); return fields.filter((field) => field.target === report?.type).sort(({orderWeight: firstOrderWeight}, {orderWeight: secondOrderWeight}) => firstOrderWeight - secondOrderWeight); }, [policy, report]); - const enabledReportFields = sortedPolicyReportFields.filter((reportField) => !ReportUtils.isReportFieldDisabled(report, reportField, policy)); - const isOnlyTitleFieldEnabled = enabledReportFields.length === 1 && ReportUtils.isReportFieldOfTypeTitle(enabledReportFields.at(0)); - const isClosedExpenseReportWithNoExpenses = ReportUtils.isClosedExpenseReportWithNoExpenses(report); - const isPaidGroupPolicyExpenseReport = ReportUtils.isPaidGroupPolicyExpenseReport(report); - const isInvoiceReport = ReportUtils.isInvoiceReport(report); + const enabledReportFields = sortedPolicyReportFields.filter((reportField) => !isReportFieldDisabled(report, reportField, policy)); + const isOnlyTitleFieldEnabled = enabledReportFields.length === 1 && isReportFieldOfTypeTitle(enabledReportFields.at(0)); + const isClosedExpenseReportWithNoExpenses = isClosedExpenseReportWithNoExpensesReportUtils(report); + const isPaidGroupPolicyExpenseReport = isPaidGroupPolicyExpenseReportUtils(report); + const isInvoiceReport = isInvoiceReportUtils(report); const shouldShowReportField = !isClosedExpenseReportWithNoExpenses && (isPaidGroupPolicyExpenseReport || isInvoiceReport) && (!isCombinedReport || !isOnlyTitleFieldEnabled); const renderThreadDivider = useMemo( @@ -110,16 +123,16 @@ function MoneyReportView({report, policy, isCombinedReport = false, shouldShowTo policy?.areReportFieldsEnabled && (!isCombinedReport || !isOnlyTitleFieldEnabled) && sortedPolicyReportFields.map((reportField) => { - if (ReportUtils.isReportFieldOfTypeTitle(reportField)) { + if (isReportFieldOfTypeTitle(reportField)) { return null; } const fieldValue = reportField.value ?? reportField.defaultValue; - const isFieldDisabled = ReportUtils.isReportFieldDisabled(report, reportField, policy); - const fieldKey = ReportUtils.getReportFieldKey(reportField.fieldID); + const isFieldDisabled = isReportFieldDisabled(report, reportField, policy); + const fieldKey = getReportFieldKey(reportField.fieldID); - const violation = ReportUtils.getFieldViolation(violations, reportField); - const violationTranslation = ReportUtils.getFieldViolationTranslation(reportField, violation); + const violation = getFieldViolation(violations, reportField); + const violationTranslation = getFieldViolationTranslation(reportField, violation); return ( reportActions.clearReportFieldKeyErrors(report?.reportID, fieldKey)} + onClose={() => clearReportFieldKeyErrors(report?.reportID, fieldKey)} > { - Task.setTaskReport(report); + setTaskReport(report); }, [report]); const taskTitle = convertToLTR(report?.reportName ?? ''); - const assigneeTooltipDetails = ReportUtils.getDisplayNamesWithTooltips( - OptionsListUtils.getPersonalDetailsForAccountIDs(report?.managerID ? [report?.managerID] : [], personalDetails), - false, - ); - const isOpen = ReportUtils.isOpenTaskReport(report); - const isCompleted = ReportUtils.isCompletedTaskReport(report); - const canModifyTask = Task.canModifyTask(report, currentUserPersonalDetails.accountID); - const canActionTask = Task.canActionTask(report, currentUserPersonalDetails.accountID); + const assigneeTooltipDetails = getDisplayNamesWithTooltips(getPersonalDetailsForAccountIDs(report?.managerID ? [report?.managerID] : [], personalDetails), false); + const isOpen = isOpenTaskReport(report); + const isCompleted = isCompletedTaskReport(report); + const canModifyTask = canModifyTaskUtil(report, currentUserPersonalDetails.accountID); + const canActionTask = canActionTaskUtil(report, currentUserPersonalDetails.accountID); const disableState = !canModifyTask; const isDisableInteractive = !canModifyTask || !isOpen; const {translate} = useLocalize(); @@ -58,13 +55,13 @@ function TaskView({report}: TaskViewProps) { Task.clearTaskErrors(report?.reportID)} + onClose={() => clearTaskErrors(report?.reportID)} errorRowStyles={styles.ph5} > {(hovered) => ( { + onPress={checkIfActionIsAllowed((e) => { if (isDisableInteractive || !report?.reportID) { return; } @@ -88,15 +85,15 @@ function TaskView({report}: TaskViewProps) { {translate('task.title')} { + onPress={checkIfActionIsAllowed(() => { // If we're already navigating to these task editing pages, early return not to mark as completed, otherwise we would have not found page. - if (TaskUtils.isActiveTaskEditRoute(report?.reportID)) { + if (isActiveTaskEditRoute(report?.reportID)) { return; } if (isCompleted) { - Task.reopenTask(report); + reopenTask(report); } else { - Task.completeTask(report); + completeTask(report); } })} isChecked={isCompleted} @@ -149,8 +146,8 @@ function TaskView({report}: TaskViewProps) { {report?.managerID ? ( ): Pick { switch (action?.actionName) { case CONST.REPORT.ACTIONS.TYPE.TASK_COMPLETED: - return {text: Localize.translateLocal('task.messages.completed')}; + return {text: translateLocal('task.messages.completed')}; case CONST.REPORT.ACTIONS.TYPE.TASK_CANCELLED: - return {text: Localize.translateLocal('task.messages.canceled')}; + return {text: translateLocal('task.messages.canceled')}; case CONST.REPORT.ACTIONS.TYPE.TASK_REOPENED: - return {text: Localize.translateLocal('task.messages.reopened')}; + return {text: translateLocal('task.messages.reopened')}; case CONST.REPORT.ACTIONS.TYPE.TASK_EDITED: return { text: getReportActionText(action), html: getReportActionHtml(action), }; default: - return {text: Localize.translateLocal('task.task')}; + return {text: translateLocal('task.task')}; } } @@ -58,15 +58,15 @@ function getTaskTitleFromReport(taskReport: OnyxEntry, fallbackTitle = ' return taskReport?.reportID && taskReport.reportName ? taskReport.reportName : fallbackTitle; } -function getTaskTitle(taskReportID: string, fallbackTitle = ''): string { +function getTaskTitle(taskReportID: string | undefined, fallbackTitle = ''): string { const taskReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`]; return getTaskTitleFromReport(taskReport, fallbackTitle); } function getTaskCreatedMessage(reportAction: OnyxEntry) { - const taskReportID = reportAction?.childReportID ?? '-1'; + const taskReportID = reportAction?.childReportID; const taskTitle = getTaskTitle(taskReportID, reportAction?.childReportName); - return taskTitle ? Localize.translateLocal('task.messages.created', {title: taskTitle}) : ''; + return taskTitle ? translateLocal('task.messages.created', {title: taskTitle}) : ''; } export {isActiveTaskEditRoute, getTaskReportActionMessage, getTaskTitle, getTaskTitleFromReport, getTaskCreatedMessage}; diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index a1c3c676f46e..e8d7949464d8 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -1,12 +1,12 @@ import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; -import * as ReportActionUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getLinkedTransactionID, getReportAction, getReportActionMessage, isCreatedTaskReportAction} from '@libs/ReportActionsUtils'; +import {getOriginalReportID} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import type ReportAction from '@src/types/onyx/ReportAction'; -import * as Report from './Report'; +import {deleteReport} from './Report'; type IgnoreDirection = 'parent' | 'child'; @@ -27,7 +27,7 @@ Onyx.connect({ }); function clearReportActionErrors(reportID: string, reportAction: ReportAction, keys?: string[]) { - const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction); + const originalReportID = getOriginalReportID(reportID, reportAction); if (!reportAction?.reportActionID) { return; @@ -41,16 +41,16 @@ function clearReportActionErrors(reportID: string, reportAction: ReportAction, k // If there's a linked transaction, delete that too // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const linkedTransactionID = ReportActionUtils.getLinkedTransactionID(reportAction.reportActionID, originalReportID || '-1'); + const linkedTransactionID = getLinkedTransactionID(reportAction.reportActionID, originalReportID); if (linkedTransactionID) { Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${linkedTransactionID}`, null); Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${reportAction.childReportID}`, null); } // Delete the failed task report too - const taskReportID = ReportActionUtils.getReportActionMessage(reportAction)?.taskReportID; - if (taskReportID && ReportActionUtils.isCreatedTaskReportAction(reportAction)) { - Report.deleteReport(taskReportID); + const taskReportID = getReportActionMessage(reportAction)?.taskReportID; + if (taskReportID && isCreatedTaskReportAction(reportAction)) { + deleteReport(taskReportID); } return; } @@ -91,7 +91,7 @@ function clearAllRelatedReportActionErrors(reportID: string | undefined, reportA const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; if (report?.parentReportID && report?.parentReportActionID && ignore !== 'parent') { - const parentReportAction = ReportActionUtils.getReportAction(report.parentReportID, report.parentReportActionID); + const parentReportAction = getReportAction(report.parentReportID, report.parentReportActionID); const parentErrorKeys = Object.keys(parentReportAction?.errors ?? {}).filter((err) => errorKeys.includes(err)); clearAllRelatedReportActionErrors(report.parentReportID, parentReportAction, 'child', parentErrorKeys); @@ -101,7 +101,7 @@ function clearAllRelatedReportActionErrors(reportID: string | undefined, reportA const childActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportAction.childReportID}`] ?? {}; Object.values(childActions).forEach((action) => { const childErrorKeys = Object.keys(action.errors ?? {}).filter((err) => errorKeys.includes(err)); - clearAllRelatedReportActionErrors(reportAction.childReportID ?? '-1', action, 'parent', childErrorKeys); + clearAllRelatedReportActionErrors(reportAction.childReportID, action, 'parent', childErrorKeys); }); } } diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index e9531ed80979..b92240ebce4e 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -23,7 +23,7 @@ import type {Icon} from '@src/types/onyx/OnyxCommon'; import type {ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import * as Report from './Report'; +import {getMostRecentReportID, navigateToConciergeChatAndDeleteReport, notifyNewAction} from './Report'; type OptimisticReport = Pick; type Assignee = { @@ -335,7 +335,7 @@ function createTaskAndNavigate( clearOutTaskInfo(); Navigation.dismissModal(parentReportID); } - Report.notifyNewAction(parentReportID, currentUserAccountID); + notifyNewAction(parentReportID, currentUserAccountID); } /** @@ -1014,7 +1014,7 @@ function getNavigationUrlOnTaskDelete(report: OnyxEntry): stri } // If no parent report, try to navigate to most recent report - const mostRecentReportID = Report.getMostRecentReportID(report); + const mostRecentReportID = getMostRecentReportID(report); if (mostRecentReportID) { return ROUTES.REPORT_WITH_ID.getRoute(mostRecentReportID); } @@ -1162,7 +1162,7 @@ function deleteTask(report: OnyxEntry) { }; API.write(WRITE_COMMANDS.CANCEL_TASK, parameters, {optimisticData, successData, failureData}); - Report.notifyNewAction(report.reportID, currentUserAccountID); + notifyNewAction(report.reportID, currentUserAccountID); const urlToNavigateBack = getNavigationUrlOnTaskDelete(report); if (urlToNavigateBack) { @@ -1258,7 +1258,7 @@ function clearTaskErrors(reportID: string | undefined) { if (report?.pendingFields?.createChat === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, report.parentReportActionID ? {[report.parentReportActionID]: null} : {}); - Report.navigateToConciergeChatAndDeleteReport(reportID); + navigateToConciergeChatAndDeleteReport(reportID); return; } diff --git a/src/pages/home/report/ReportActionItemContentCreated.tsx b/src/pages/home/report/ReportActionItemContentCreated.tsx index 1555854e50cd..bee31684fad2 100644 --- a/src/pages/home/report/ReportActionItemContentCreated.tsx +++ b/src/pages/home/report/ReportActionItemContentCreated.tsx @@ -15,9 +15,9 @@ import UnreadActionIndicator from '@components/UnreadActionIndicator'; import useLocalize from '@hooks/useLocalize'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {isMessageDeleted, isReversedTransaction as isReversedTransactionReportActionsUtils, isTransactionThread} from '@libs/ReportActionsUtils'; +import {isCanceledTaskReport, isExpenseReport, isInvoiceReport, isIOUReport, isTaskReport} from '@libs/ReportUtils'; +import {getCurrency} from '@libs/TransactionUtils'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -50,10 +50,10 @@ function ReportActionItemContentCreated({contextValue, parentReportAction, trans const {report, action, transactionThreadReport} = contextValue; - const policy = usePolicy(report?.policyID === CONST.POLICY.OWNER_EMAIL_FAKE ? '-1' : report?.policyID ?? '-1'); - const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID ?? '-1'}`); + const policy = usePolicy(report?.policyID === CONST.POLICY.OWNER_EMAIL_FAKE ? '-1' : report?.policyID); + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`); - const transactionCurrency = TransactionUtils.getCurrency(transaction); + const transactionCurrency = getCurrency(transaction); const renderThreadDivider = useMemo( () => @@ -73,10 +73,10 @@ function ReportActionItemContentCreated({contextValue, parentReportAction, trans const contextMenuValue = useMemo(() => ({...contextValue, isDisabled: true}), [contextValue]); - if (ReportActionsUtils.isTransactionThread(parentReportAction)) { - const isReversedTransaction = ReportActionsUtils.isReversedTransaction(parentReportAction); + if (isTransactionThread(parentReportAction)) { + const isReversedTransaction = isReversedTransactionReportActionsUtils(parentReportAction); - if (ReportActionsUtils.isMessageDeleted(parentReportAction) || isReversedTransaction) { + if (isMessageDeleted(parentReportAction) || isReversedTransaction) { let message: TranslationPaths; if (isReversedTransaction) { @@ -117,8 +117,8 @@ function ReportActionItemContentCreated({contextValue, parentReportAction, trans ); } - if (ReportUtils.isTaskReport(report)) { - if (ReportUtils.isCanceledTaskReport(report, parentReportAction)) { + if (isTaskReport(report)) { + if (isCanceledTaskReport(report, parentReportAction)) { return ( @@ -147,7 +147,7 @@ function ReportActionItemContentCreated({contextValue, parentReportAction, trans ); } - if (ReportUtils.isExpenseReport(report) || ReportUtils.isIOUReport(report) || ReportUtils.isInvoiceReport(report)) { + if (isExpenseReport(report) || isIOUReport(report) || isInvoiceReport(report)) { return ( {!isEmptyObject(transactionThreadReport?.reportID) ? ( diff --git a/src/pages/home/report/ReportActionItemCreated.tsx b/src/pages/home/report/ReportActionItemCreated.tsx index 679855a2847d..fb6e9bba570f 100644 --- a/src/pages/home/report/ReportActionItemCreated.tsx +++ b/src/pages/home/report/ReportActionItemCreated.tsx @@ -9,7 +9,7 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getIcons, isChatReport, isCurrentUserInvoiceReceiver, isInvoiceRoom, navigateToDetailsPage, shouldDisableDetailPage as shouldDisableDetailPageReportUtils} from '@libs/ReportUtils'; import {navigateToConciergeChatAndDeleteReport} from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -31,16 +31,18 @@ function ReportActionItemCreated({reportID, policyID}: ReportActionItemCreatedPr const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); - const [invoiceReceiverPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.invoiceReceiver && 'policyID' in report.invoiceReceiver ? report.invoiceReceiver.policyID : -1}`); + const [invoiceReceiverPolicy] = useOnyx( + `${ONYXKEYS.COLLECTION.POLICY}${report?.invoiceReceiver && 'policyID' in report.invoiceReceiver ? report.invoiceReceiver.policyID : CONST.DEFAULT_NUMBER_ID}`, + ); - if (!ReportUtils.isChatReport(report)) { + if (!isChatReport(report)) { return null; } - let icons = ReportUtils.getIcons(report, personalDetails, null, '', -1, policy, invoiceReceiverPolicy); - const shouldDisableDetailPage = ReportUtils.shouldDisableDetailPage(report); + let icons = getIcons(report, personalDetails, null, '', -1, policy, invoiceReceiverPolicy); + const shouldDisableDetailPage = shouldDisableDetailPageReportUtils(report); - if (ReportUtils.isInvoiceRoom(report) && ReportUtils.isCurrentUserInvoiceReceiver(report)) { + if (isInvoiceRoom(report) && isCurrentUserInvoiceReceiver(report)) { icons = [...icons].reverse(); } @@ -59,7 +61,7 @@ function ReportActionItemCreated({reportID, policyID}: ReportActionItemCreatedPr > ReportUtils.navigateToDetailsPage(report, Navigation.getReportRHPActiveRoute())} + onPress={() => navigateToDetailsPage(report, Navigation.getReportRHPActiveRoute())} style={[styles.mh5, styles.mb3, styles.alignSelfStart, shouldDisableDetailPage && styles.cursorDefault]} accessibilityLabel={translate('common.details')} role={CONST.ROLE.BUTTON} From a75266cc351c046b83ca38f50de73683f2f57b12 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 21 Jan 2025 23:42:06 +0700 Subject: [PATCH 103/179] fix lint --- .../request/step/IOURequestStepCategory.tsx | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepCategory.tsx b/src/pages/iou/request/step/IOURequestStepCategory.tsx index 45da311ccf26..096ec08e21f0 100644 --- a/src/pages/iou/request/step/IOURequestStepCategory.tsx +++ b/src/pages/iou/request/step/IOURequestStepCategory.tsx @@ -15,14 +15,14 @@ import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import {setDraftSplitTransaction, setMoneyRequestCategory, updateMoneyRequestCategory} from '@libs/actions/IOU'; +import {enablePolicyCategories, getPolicyCategories} from '@libs/actions/Policy/Category'; import Navigation from '@libs/Navigation/Navigation'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; -import * as IOU from '@userActions/IOU'; -import * as Category from '@userActions/Policy/Category'; +import {hasEnabledOptions} from '@libs/OptionsListUtils'; +import {isPolicyAdmin} from '@libs/PolicyUtils'; +import {isMoneyRequestAction} from '@libs/ReportActionsUtils'; +import {canEditMoneyRequest, getTransactionDetails, isGroupPolicy, isReportInGroupPolicy} from '@libs/ReportUtils'; +import {areRequiredFieldsEmpty} from '@libs/TransactionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -45,11 +45,11 @@ function IOURequestStepCategory({ transaction, }: IOURequestStepCategoryProps) { const [splitDraftTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`); - const [policyReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${IOU.getIOURequestPolicyID(transaction, reportReal)}`); - const [policyDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${IOU.getIOURequestPolicyID(transaction, reportDraft)}`); - const [policyCategoriesReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${IOU.getIOURequestPolicyID(transaction, reportReal)}`); - const [policyCategoriesDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${IOU.getIOURequestPolicyID(transaction, reportDraft)}`); - const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${IOU.getIOURequestPolicyID(transaction, reportReal)}`); + const [policyReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}$getIOURequestPolicyID(transaction, reportReal)}`); + const [policyDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}$getIOURequestPolicyID(transaction, reportDraft)}`); + const [policyCategoriesReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}$getIOURequestPolicyID(transaction, reportReal)}`); + const [policyCategoriesDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}$getIOURequestPolicyID(transaction, reportDraft)}`); + const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}$getIOURequestPolicyID(transaction, reportReal)}`); let reportID = '-1'; if (action === CONST.IOU.ACTION.EDIT && reportReal) { if (iouType === CONST.IOU.TYPE.SPLIT) { @@ -71,28 +71,28 @@ function IOURequestStepCategory({ const isEditing = action === CONST.IOU.ACTION.EDIT; const isEditingSplitBill = isEditing && iouType === CONST.IOU.TYPE.SPLIT; const currentTransaction = isEditingSplitBill && !lodashIsEmpty(splitDraftTransaction) ? splitDraftTransaction : transaction; - const transactionCategory = ReportUtils.getTransactionDetails(currentTransaction)?.category; + const transactionCategory = getTransactionDetails(currentTransaction)?.category; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const reportAction = reportActions?.[report?.parentReportActionID || reportActionID] ?? null; const shouldShowCategory = - (ReportUtils.isReportInGroupPolicy(report) || ReportUtils.isGroupPolicy(policy?.type ?? '')) && + (isReportInGroupPolicy(report) || isGroupPolicy(policy?.type ?? '')) && // The transactionCategory can be an empty string, so to maintain the logic we'd like to keep it in this shape until utils refactor // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - (!!transactionCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); + (!!transactionCategory || hasEnabledOptions(Object.values(policyCategories ?? {}))); const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; - const canEditSplitBill = isSplitBill && reportAction && session?.accountID === reportAction.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(transaction); + const canEditSplitBill = isSplitBill && reportAction && session?.accountID === reportAction.actorAccountID && areRequiredFieldsEmpty(transaction); // eslint-disable-next-line rulesdir/no-negated-variables - const shouldShowNotFoundPage = isEditing && (isSplitBill ? !canEditSplitBill : !ReportActionsUtils.isMoneyRequestAction(reportAction) || !ReportUtils.canEditMoneyRequest(reportAction)); + const shouldShowNotFoundPage = isEditing && (isSplitBill ? !canEditSplitBill : !isMoneyRequestAction(reportAction) || !canEditMoneyRequest(reportAction)); const fetchData = () => { if ((!!policy && !!policyCategories) || !report?.policyID) { return; } - Category.getPolicyCategories(report?.policyID); + getPolicyCategories(report?.policyID); }; const {isOffline} = useNetwork({onReconnect: fetchData}); const isLoading = !isOffline && policyCategories === undefined; @@ -116,19 +116,19 @@ function IOURequestStepCategory({ if (transaction) { // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value if (isEditingSplitBill) { - IOU.setDraftSplitTransaction(transaction.transactionID, {category: updatedCategory}, policy); + setDraftSplitTransaction(transaction.transactionID, {category: updatedCategory}, policy); navigateBack(); return; } if (isEditing && report) { - IOU.updateMoneyRequestCategory(transaction.transactionID, report.reportID, updatedCategory, policy, policyTags, policyCategories, currentSearchHash); + updateMoneyRequestCategory(transaction.transactionID, report.reportID, updatedCategory, policy, policyTags, policyCategories, currentSearchHash); navigateBack(); return; } } - IOU.setMoneyRequestCategory(transactionID, updatedCategory, policy?.id); + setMoneyRequestCategory(transactionID, updatedCategory, policy?.id); if (action === CONST.IOU.ACTION.CATEGORIZE) { if (report?.reportID) { @@ -166,7 +166,7 @@ function IOURequestStepCategory({ subtitle={translate('workspace.categories.emptyCategories.subtitle')} containerStyle={[styles.flex1, styles.justifyContentCenter]} /> - {PolicyUtils.isPolicyAdmin(policy) && ( + {isPolicyAdmin(policy) && ( - )} - {/** - These are the actionable buttons that appear at the bottom of a Concierge message - for example: Invite a user mentioned but not a member of the room - https://github.com/Expensify/App/issues/32741 - */} - {actionableItemButtons.length > 0 && ( - - )} - - ) : ( - - )} - - - - ); - } + } else { } const numberOfThreadReplies = action.childVisibleActionCount ?? 0; const shouldDisplayThreadReplies = shouldDisplayThreadRepliesReportUtils(action, isThreadReportParentAction); From 7b468bc59658a985992e57321ec0e8b94753062b Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Wed, 22 Jan 2025 13:50:11 +0100 Subject: [PATCH 109/179] fix reportactionitem --- .../home/report/PureReportActionItem.tsx | 233 +++++++++++++++++- 1 file changed, 232 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 4b3ea35a3b3a..667c22667435 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -729,7 +729,238 @@ function PureReportActionItem({ checkIfContextMenuActive={toggleContextMenuFromActiveReportAction} /> ); - } else { } + } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) { + children = isClosedExpenseReportWithNoExpenses ? ( + ${translate('parentReportAction.deletedReport')}`} /> + ) : ( + setIsPaymentMethodPopoverActive(true)} + onPaymentOptionsHide={() => setIsPaymentMethodPopoverActive(false)} + isWhisper={isWhisper} + /> + ); + } else if (isTaskAction(action)) { + children = ; + } else if (isCreatedTaskReportAction(action)) { + children = ( + + + + ); + } else if (isReimbursementQueuedAction(action)) { + const linkedReport = isChatThread(report) ? parentReport : report; + const submitterDisplayName = formatPhoneNumber(getDisplayNameOrDefault(personalDetails?.[linkedReport?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID])); + const paymentType = getOriginalMessage(action)?.paymentType ?? ''; + + children = ( + + <> + {missingPaymentMethod === 'bankAccount' && ( + + )} + {/** + These are the actionable buttons that appear at the bottom of a Concierge message + for example: Invite a user mentioned but not a member of the room + https://github.com/Expensify/App/issues/32741 + */} + {actionableItemButtons.length > 0 && ( + + )} + + ) : ( + + )} + + + + ); + } const numberOfThreadReplies = action.childVisibleActionCount ?? 0; const shouldDisplayThreadReplies = shouldDisplayThreadRepliesReportUtils(action, isThreadReportParentAction); From 90a943873350d433030d1c45fb6c79657b70a5db Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Wed, 22 Jan 2025 14:51:24 +0100 Subject: [PATCH 110/179] fix lint --- .../HTMLRenderers/MentionReportRenderer/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/index.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/index.tsx index 89a9fb21d48f..fcae31dd7d2f 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/index.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/index.tsx @@ -60,9 +60,9 @@ function MentionReportRenderer({style, tnode, TDefaultRenderer, ...defaultRender const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); const currentReportID = useCurrentReportID(); - const currentReportIDValue = currentReportIDContext || currentReportID?.currentReportID; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const [currentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${currentReportIDValue || -1}`); + const currentReportIDValue = currentReportIDContext || currentReportID?.currentReportID; + const [currentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${currentReportIDValue}`); // When we invite someone to a room they don't have the policy object, but we still want them to be able to see and click on report mentions, so we only check if the policyID in the report is from a workspace const isGroupPolicyReport = useMemo(() => currentReport && !isEmptyObject(currentReport) && !!currentReport.policyID && currentReport.policyID !== CONST.POLICY.ID_FAKE, [currentReport]); From ef61966d3e46aaed26f7bc53c47d13b4dc5a618f Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Wed, 22 Jan 2025 22:00:24 +0700 Subject: [PATCH 111/179] remove change --- tests/unit/ReportUtilsTest.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 6926a0863e46..ff09dab937fb 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -1969,8 +1969,6 @@ describe('ReportUtils', () => { const result = [categoryapprover2Email, categoryapprover1Email, tagapprover2Email, tagapprover1Email, 'admin@test.com']; expect(getApprovalChain(policyTest, expenseReport)).toStrictEqual(result); }); - - waitForBatchedUpdates(); }); }); }); From 2c6bbe532aa9f176e60e7bb495624cc0e064ffa9 Mon Sep 17 00:00:00 2001 From: Rutika Pawar <183392827+twilight2294@users.noreply.github.com> Date: Wed, 22 Jan 2025 21:00:47 +0530 Subject: [PATCH 112/179] update usage to policyID --- .../workspace/expensifyCard/issueNew/IssueNewCardPage.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx b/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx index 2d25255b136f..7e0e3762646d 100644 --- a/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx @@ -21,11 +21,9 @@ import LimitTypeStep from './LimitTypeStep'; type IssueNewCardPageProps = WithPolicyAndFullscreenLoadingProps & PlatformStackScreenProps; function IssueNewCardPage({policy, route}: IssueNewCardPageProps) { - const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policy?.id}`); - - const {currentStep} = issueNewCard ?? {}; - const policyID = policy?.id; + const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); + const {currentStep} = issueNewCard ?? {}; const backTo = route?.params?.backTo; const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate}); From bb89acdf9b6e421015eae0694e72f02a3bc531e8 Mon Sep 17 00:00:00 2001 From: Corinne Ofstad Date: Wed, 22 Jan 2025 09:32:37 -0600 Subject: [PATCH 113/179] Update Travel FAQ (Travel Policy Link) --- .../travel/Configure-travel-policy-and-preferences.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/articles/new-expensify/travel/Configure-travel-policy-and-preferences.md b/docs/articles/new-expensify/travel/Configure-travel-policy-and-preferences.md index 237aad83169a..d7d9c656a612 100644 --- a/docs/articles/new-expensify/travel/Configure-travel-policy-and-preferences.md +++ b/docs/articles/new-expensify/travel/Configure-travel-policy-and-preferences.md @@ -158,8 +158,16 @@ Flight preferences include multiple sections with different settings: # FAQ -How do travel policy rules interact with Expensify’s [approval flows](https://help.expensify.com/articles/expensify-classic/travel/Approve-travel-expenses)? +## How do travel policy rules interact with Expensify’s [approval flows](https://help.expensify.com/articles/expensify-classic/travel/Approve-travel-expenses)? Travel policy rules define what can and can’t be booked by your employees while they’re making the booking. Once a booking is placed and the travel itself is [approved](https://help.expensify.com/articles/expensify-classic/travel/Approve-travel-expenses), the expense will appear in Expensify. It will then be coded, submitted, pushed through the existing expense approval process as defined by your workspace, and exported to your preferred accounting platform (if applicable). +## Why are some policies not selectable? + +If the travel policy you want to configure is greyed out, it might be linked to a parent policy. To unlink it from the default policy: + +1. Look for the “link” icon next to the dropdown menu. +2. Click the icon to unlink the policy from the parent policy. +3. Once unlinked, you’ll be able to make your selection. + From 888fea0e258866851983a3dde8ce19f6d8e4e52e Mon Sep 17 00:00:00 2001 From: Joseph Kaufman <54866469+joekaufmanexpensify@users.noreply.github.com> Date: Wed, 22 Jan 2025 11:13:00 -0500 Subject: [PATCH 114/179] Delete Expensify Card migration help article --- ...ade-to-the-new-Expensify-Card-from-Visa.md | 55 ------------------- 1 file changed, 55 deletions(-) delete mode 100644 docs/articles/new-expensify/expensify-card/Upgrade-to-the-new-Expensify-Card-from-Visa.md diff --git a/docs/articles/new-expensify/expensify-card/Upgrade-to-the-new-Expensify-Card-from-Visa.md b/docs/articles/new-expensify/expensify-card/Upgrade-to-the-new-Expensify-Card-from-Visa.md deleted file mode 100644 index 782e939e991e..000000000000 --- a/docs/articles/new-expensify/expensify-card/Upgrade-to-the-new-Expensify-Card-from-Visa.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Upgrade to the new Expensify Card from Visa -description: Get the new Expensify Visa® Commercial Card ---- -
- -When you upgrade the Expensify Cards to the new program, you'll have access to even more tools to manage employee spending, including: -- Unlimited [virtual cards](https://use.expensify.com/unlimited-virtual-cards) -- Controlled spending amounts on virtual cards to manage subscriptions -- Tighter controls for managing spend across employees and merchants -- Fixed or monthly spending limits for each card -- Unique naming for each virtual card for simplified expense categorization - -{% include info.html %} -The Expensify Card upgrade must be completed by December 1, 2024. -{% include end-info.html %} - -# Upgrade your company’s Expensify Card program -This process must be completed by a Domain Admin. Any domain Admin can complete the upgrade, but only one admin needs to complete these steps. - -**Before updating the card program:** -- Make sure your employees' address is up-to-date in their Expensify account -- Confirm the employees who should be receiving a new Expensify Card have a card limit set that's greater than $0 - -## Steps to upgrade the Expensify Cards -1. On your Home page, click the task titled _Upgrade to the new and improved Expensify Card._ -2. Review and agree to the Terms of Service. -3. Click **Get the new card**. All existing cardholders with a limit greater than $0 will be automatically mailed a new physical card to the address they have on file. Virtual cards will be automatically issued and available for immediate use. -4. If you have Positive Pay enabled for your settlement account, contact your bank as soon as possible to whitelist the new ACH ID: 2270239450. -5. Remind your employees to update their payment information for recurring charges to their virtual card information. - -New cards will have the same limit as the existing cards. Each cardholder’s current physical and virtual cards will remain active until a Domain Admin or the cardholder deactivates it. - -{% include info.html %} -Cards won’t be issued to any employees who don’t currently have them. In this case, you’ll need to [issue a new card](https://help.expensify.com/articles/expensify-classic/expensify-card/Set-Up-the-Expensify-Visa%C2%AE-Commercial-Card-for-your-Company) -{% include end-info.html %} - -{% include faq-begin.md %} - -## Why don’t I see the task to agree to new terms on my Home page? - -There are a few reasons why you might not see the task on your Home page: -- You may not be a Domain Admin -- Another domain admin has already accepted the terms -- The task may be hidden. To find hidden tasks, scroll to the bottom of the Home page and click **Show Hidden Tasks** to see all of your available tasks. - -## Will this affect the continuous reconciliation process? - -No. During the transition period, you may have some employees with old cards and some with new cards, so you’ll have two different debits (settlements) made to your settlement account for each settlement period. Once all spending has transitioned to the new cards, you’ll only see one debit/settlement. - -## Do I have to upgrade to the new Expensify Visa® Commercial Card? - -Yes, the Expensify Cards will not work on the old program. This must be completed by November 1, 2024. -{% include faq-end.md %} -
From c168d4e7bade9084e71b962ca137585f8a851ba9 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 23 Jan 2025 10:03:23 +0700 Subject: [PATCH 115/179] remove unchange --- src/pages/home/ReportScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 820c122b0cbc..a1a2c3f3f6ac 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -645,7 +645,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // If you already have a report open and are deeplinking to a new report on native, // the ReportScreen never actually unmounts and the reportID in the route also doesn't change. // Therefore, we need to compare if the existing reportID is the same as the one in the route - // before deciding that we shouldn't call Open + // before deciding that we shouldn't call OpenReport. if (reportIDFromRoute === lastReportIDFromRoute && (!onyxReportID || onyxReportID === reportIDFromRoute)) { return; } From b9dfb4710ad255bd38a131fe10325cf24dd64448 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 23 Jan 2025 15:29:30 +0700 Subject: [PATCH 116/179] add search hash to update tag --- src/libs/actions/IOU.ts | 3 +- .../iou/request/step/IOURequestStepTag.tsx | 47 ++++++++++--------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index d4059a615d02..3805c5987868 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3830,11 +3830,12 @@ function updateMoneyRequestTag( policy: OnyxEntry, policyTagList: OnyxEntry, policyCategories: OnyxEntry, + hash?: number, ) { const transactionChanges: TransactionChanges = { tag, }; - const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTagList, policyCategories); + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTagList, policyCategories, undefined, hash); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_TAG, params, onyxData); } diff --git a/src/pages/iou/request/step/IOURequestStepTag.tsx b/src/pages/iou/request/step/IOURequestStepTag.tsx index 6080a4fa3d8d..b98048c2c94d 100644 --- a/src/pages/iou/request/step/IOURequestStepTag.tsx +++ b/src/pages/iou/request/step/IOURequestStepTag.tsx @@ -3,21 +3,22 @@ import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import FixedFooter from '@components/FixedFooter'; -import * as Illustrations from '@components/Icon/Illustrations'; +import {EmptyStateExpenses} from '@components/Icon/Illustrations'; import {useSession} from '@components/OnyxProvider'; +import {useSearchContext} from '@components/Search/SearchContext'; import TagPicker from '@components/TagPicker'; import Text from '@components/Text'; import WorkspaceEmptyStateSection from '@components/WorkspaceEmptyStateSection'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as IOUUtils from '@libs/IOUUtils'; +import {setDraftSplitTransaction, setMoneyRequestTag, updateMoneyRequestTag} from '@libs/actions/IOU'; +import {insertTagIntoTransactionTagsString} from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import * as TagsOptionsListUtils from '@libs/TagsOptionsListUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; -import * as IOU from '@userActions/IOU'; +import {getTagListName, getTagLists, isPolicyAdmin} from '@libs/PolicyUtils'; +import {isMoneyRequestAction} from '@libs/ReportActionsUtils'; +import {canEditMoneyRequest, isReportInGroupPolicy, OptionData} from '@libs/ReportUtils'; +import {hasEnabledTags} from '@libs/TagsOptionsListUtils'; +import {areRequiredFieldsEmpty, getTag} from '@libs/TransactionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -49,47 +50,47 @@ function IOURequestStepTag({ const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {canEvict: false}); const session = useSession(); const styles = useThemeStyles(); + const {currentSearchHash} = useSearchContext(); const {translate} = useLocalize(); const tagListIndex = Number(rawTagIndex); - const policyTagListName = PolicyUtils.getTagListName(policyTags, tagListIndex); + const policyTagListName = getTagListName(policyTags, tagListIndex); const isEditing = action === CONST.IOU.ACTION.EDIT; const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; const isEditingSplitBill = isEditing && isSplitBill; const currentTransaction = isEditingSplitBill && !isEmptyObject(splitDraftTransaction) ? splitDraftTransaction : transaction; - const transactionTag = TransactionUtils.getTag(currentTransaction); - const tag = TransactionUtils.getTag(currentTransaction, tagListIndex); + const transactionTag = getTag(currentTransaction); + const tag = getTag(currentTransaction, tagListIndex); const reportAction = reportActions?.[report?.parentReportActionID ?? reportActionID] ?? null; - const canEditSplitBill = isSplitBill && reportAction && session?.accountID === reportAction.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(transaction); - const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); + const canEditSplitBill = isSplitBill && reportAction && session?.accountID === reportAction.actorAccountID && areRequiredFieldsEmpty(transaction); + const policyTagLists = useMemo(() => getTagLists(policyTags), [policyTags]); - const shouldShowTag = transactionTag || TagsOptionsListUtils.hasEnabledTags(policyTagLists); + const shouldShowTag = transactionTag || hasEnabledTags(policyTagLists); // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundPage = - !ReportUtils.isReportInGroupPolicy(report) || - (isEditing && (isSplitBill ? !canEditSplitBill : !ReportActionsUtils.isMoneyRequestAction(reportAction) || !ReportUtils.canEditMoneyRequest(reportAction))); + !isReportInGroupPolicy(report) || (isEditing && (isSplitBill ? !canEditSplitBill : !isMoneyRequestAction(reportAction) || !canEditMoneyRequest(reportAction))); const navigateBack = () => { Navigation.goBack(backTo); }; - const updateTag = (selectedTag: Partial) => { + const updateTag = (selectedTag: Partial) => { const isSelectedTag = selectedTag.searchText === tag; const searchText = selectedTag.searchText ?? ''; - const updatedTag = IOUUtils.insertTagIntoTransactionTagsString(transactionTag, isSelectedTag ? '' : searchText, tagListIndex); + const updatedTag = insertTagIntoTransactionTagsString(transactionTag, isSelectedTag ? '' : searchText, tagListIndex); if (isEditingSplitBill) { - IOU.setDraftSplitTransaction(transactionID, {tag: updatedTag}); + setDraftSplitTransaction(transactionID, {tag: updatedTag}); navigateBack(); return; } if (isEditing) { - IOU.updateMoneyRequestTag(transactionID, report?.reportID ?? '-1', updatedTag, policy, policyTags, policyCategories); + updateMoneyRequestTag(transactionID, report?.reportID ?? '-1', updatedTag, policy, policyTags, policyCategories, currentSearchHash); navigateBack(); return; } - IOU.setMoneyRequestTag(transactionID, updatedTag); + setMoneyRequestTag(transactionID, updatedTag); navigateBack(); }; @@ -105,12 +106,12 @@ function IOURequestStepTag({ - {PolicyUtils.isPolicyAdmin(policy) && ( + {isPolicyAdmin(policy) && (