From 00a7c235b3c37b96f022ada724f3aa6a0e6f2087 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 23 Oct 2023 15:41:54 +0500 Subject: [PATCH 001/142] perf: memoize components --- src/components/AttachmentModal.js | 4 ++-- src/components/EmojiPicker/EmojiPickerButton.js | 4 ++-- src/components/ExceededCommentLength.js | 4 ++-- src/pages/home/report/ReportActionCompose/SendButton.js | 4 ++-- src/pages/home/report/ReportTypingIndicator.js | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 61b138747950..9cceaab5e63e 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -1,4 +1,4 @@ -import React, {useState, useCallback, useRef, useMemo} from 'react'; +import React, {useState, useCallback, useRef, useMemo, memo} from 'react'; import PropTypes from 'prop-types'; import {View, Animated, Keyboard} from 'react-native'; import Str from 'expensify-common/lib/str'; @@ -534,4 +534,4 @@ export default compose( key: ONYXKEYS.SESSION, }, }), -)(AttachmentModal); +)(memo(AttachmentModal)); diff --git a/src/components/EmojiPicker/EmojiPickerButton.js b/src/components/EmojiPicker/EmojiPickerButton.js index cbfc3517117c..549e7d75005e 100644 --- a/src/components/EmojiPicker/EmojiPickerButton.js +++ b/src/components/EmojiPicker/EmojiPickerButton.js @@ -1,4 +1,4 @@ -import React, {useEffect, useRef} from 'react'; +import React, {memo, useEffect, useRef} from 'react'; import PropTypes from 'prop-types'; import styles from '../../styles/styles'; import * as StyleUtils from '../../styles/StyleUtils'; @@ -64,4 +64,4 @@ function EmojiPickerButton(props) { EmojiPickerButton.propTypes = propTypes; EmojiPickerButton.defaultProps = defaultProps; EmojiPickerButton.displayName = 'EmojiPickerButton'; -export default withLocalize(EmojiPickerButton); +export default withLocalize(memo(EmojiPickerButton)); diff --git a/src/components/ExceededCommentLength.js b/src/components/ExceededCommentLength.js index 9c785cec0395..587ab11cac37 100644 --- a/src/components/ExceededCommentLength.js +++ b/src/components/ExceededCommentLength.js @@ -1,4 +1,4 @@ -import React, {useEffect, useState, useMemo} from 'react'; +import React, {useEffect, useState, useMemo, memo} from 'react'; import PropTypes from 'prop-types'; import {debounce} from 'lodash'; import {withOnyx} from 'react-native-onyx'; @@ -65,4 +65,4 @@ export default withOnyx({ key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`, initialValue: '', }, -})(ExceededCommentLength); +})(memo(ExceededCommentLength)); diff --git a/src/pages/home/report/ReportActionCompose/SendButton.js b/src/pages/home/report/ReportActionCompose/SendButton.js index a97dd420e181..0e1d10d44092 100644 --- a/src/pages/home/report/ReportActionCompose/SendButton.js +++ b/src/pages/home/report/ReportActionCompose/SendButton.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {memo} from 'react'; import {View} from 'react-native'; import {Gesture, GestureDetector} from 'react-native-gesture-handler'; import PropTypes from 'prop-types'; @@ -61,4 +61,4 @@ function SendButton({isDisabled: isDisabledProp, handleSendMessage}) { SendButton.propTypes = propTypes; SendButton.displayName = 'SendButton'; -export default SendButton; +export default memo(SendButton); diff --git a/src/pages/home/report/ReportTypingIndicator.js b/src/pages/home/report/ReportTypingIndicator.js index db97f712d65f..b4e5bbe8fe9d 100755 --- a/src/pages/home/report/ReportTypingIndicator.js +++ b/src/pages/home/report/ReportTypingIndicator.js @@ -1,4 +1,4 @@ -import React, {useMemo} from 'react'; +import React, {memo, useMemo} from 'react'; import PropTypes from 'prop-types'; import _ from 'underscore'; import {withOnyx} from 'react-native-onyx'; @@ -77,4 +77,4 @@ export default compose( initialValue: {}, }, }), -)(ReportTypingIndicator); +)(memo(ReportTypingIndicator)); From 9c4f2995a3a86c75ea17f039939199396049d379 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 23 Oct 2023 15:43:15 +0500 Subject: [PATCH 002/142] refactor: simplify props --- src/pages/home/ReportScreen.js | 50 ++++++++---- .../SilentCommentUpdater.js | 16 ++-- src/pages/home/report/ReportActionsList.js | 2 +- .../report/ReportActionsListItemRenderer.js | 9 +-- src/pages/home/report/ReportActionsView.js | 10 ++- src/pages/home/report/ReportFooter.js | 81 ++++++++++++------- 6 files changed, 107 insertions(+), 61 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 81000c2dab92..682edb701c14 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -1,10 +1,10 @@ -import React, {useRef, useState, useEffect, useMemo, useCallback} from 'react'; +import React, {useRef, useState, useEffect, useMemo, useCallback, memo} from 'react'; import {withOnyx} from 'react-native-onyx'; import {useFocusEffect} from '@react-navigation/native'; import PropTypes from 'prop-types'; import {View} from 'react-native'; import lodashGet from 'lodash/get'; -import _ from 'underscore'; +import _, {isEqual} from 'underscore'; import styles from '../../styles/styles'; import ScreenWrapper from '../../components/ScreenWrapper'; import HeaderView from './HeaderView'; @@ -163,8 +163,9 @@ function ReportScreen({ const {addWorkspaceRoomOrChatPendingAction, addWorkspaceRoomOrChatErrors} = ReportUtils.getReportOfflinePendingActionAndErrors(report); const screenWrapperStyle = [styles.appContent, styles.flex1, {marginTop: viewportOffsetTop}]; + const isEmptyChat = useMemo(() => _.isEmpty(reportActions), [reportActions]); // There are no reportActions at all to display and we are still in the process of loading the next set of actions. - const isLoadingInitialReportActions = _.isEmpty(reportActions) && reportMetadata.isLoadingReportActions; + const isLoadingInitialReportActions = isEmptyChat && reportMetadata.isLoadingReportActions; const isOptimisticDelete = lodashGet(report, 'statusNum') === CONST.REPORT.STATUS.CLOSED; @@ -173,6 +174,10 @@ function ReportScreen({ const isLoading = !reportID || !isSidebarLoaded || _.isEmpty(personalDetails); const parentReportAction = ReportActionsUtils.getParentReportAction(report); + const lastReportAction = useMemo( + () => _.find([...reportActions, parentReportAction], (action) => ReportUtils.canEditReportAction(action) && !ReportActionsUtils.isMoneyRequestAction(action)), + [reportActions, parentReportAction], + ); const isSingleTransactionView = ReportUtils.isMoneyRequest(report); const policy = policies[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`] || {}; @@ -253,16 +258,6 @@ function ReportScreen({ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(accountManagerReportID)); }, [accountManagerReportID]); - /** - * @param {String} text - */ - const onSubmitComment = useCallback( - (text) => { - Report.addComment(getReportID(route), text); - }, - [route], - ); - useFocusEffect( useCallback(() => { const unsubscribeVisibilityListener = Visibility.onVisibilityChange(() => { @@ -441,14 +436,15 @@ function ReportScreen({ {isReportReadyForDisplay ? ( ) : ( @@ -515,4 +511,24 @@ export default compose( }, true, ), -)(ReportScreen); +)( + memo( + ReportScreen, + (prevProps, nextProps) => + prevProps.isSidebarLoaded === nextProps.isSidebarLoaded && + isEqual(prevProps.reportActions, nextProps.reportActions) && + isEqual(prevProps.reportMetadata, nextProps.reportMetadata) && + prevProps.isComposerFullSize === nextProps.isComposerFullSize && + isEqual(prevProps.betas, nextProps.betas) && + isEqual(prevProps.policies, nextProps.policies) && + prevProps.accountManagerReportID === nextProps.accountManagerReportID && + isEqual(prevProps.personalDetails, nextProps.personalDetails) && + prevProps.userLeavingStatus === nextProps.userLeavingStatus && + prevProps.report.reportID === nextProps.report.reportID && + prevProps.report.policyID === nextProps.report.policyID && + prevProps.report.isOptimisticReport === nextProps.report.isOptimisticReport && + prevProps.report.statusNum === nextProps.report.statusNum && + isEqual(prevProps.report.pendingFields, nextProps.report.pendingFields) && + prevProps.currentReportID === nextProps.currentReportID, + ), +); diff --git a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater.js b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater.js index 09f9d368bdcc..2d7a65ca4cc7 100644 --- a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater.js +++ b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater.js @@ -9,12 +9,6 @@ const propTypes = { /** The comment of the report */ comment: PropTypes.string, - /** The report associated with the comment */ - report: PropTypes.shape({ - /** The ID of the report */ - reportID: PropTypes.string, - }).isRequired, - /** The value of the comment */ value: PropTypes.string.isRequired, @@ -26,6 +20,8 @@ const propTypes = { /** Updates the comment */ updateComment: PropTypes.func.isRequired, + + reportID: PropTypes.string.isRequired, }; const defaultProps = { @@ -38,9 +34,9 @@ const defaultProps = { * re-rendering a UI component for that. That's why the side effect was moved down to a separate component. * @returns {null} */ -function SilentCommentUpdater({comment, commentRef, report, value, updateComment}) { +function SilentCommentUpdater({comment, commentRef, reportID, value, updateComment}) { const prevCommentProp = usePrevious(comment); - const prevReportId = usePrevious(report.reportID); + const prevReportId = usePrevious(reportID); const {preferredLocale} = useLocalize(); const prevPreferredLocale = usePrevious(preferredLocale); @@ -51,12 +47,12 @@ function SilentCommentUpdater({comment, commentRef, report, value, updateComment // As the report IDs change, make sure to update the composer comment as we need to make sure // we do not show incorrect data in there (ie. draft of message from other report). - if (preferredLocale === prevPreferredLocale && report.reportID === prevReportId && !shouldSyncComment) { + if (preferredLocale === prevPreferredLocale && reportID === prevReportId && !shouldSyncComment) { return; } updateComment(comment); - }, [prevCommentProp, prevPreferredLocale, prevReportId, comment, preferredLocale, report.reportID, updateComment, value, commentRef]); + }, [prevCommentProp, prevPreferredLocale, prevReportId, comment, preferredLocale, reportID, updateComment, value, commentRef]); return null; } diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index febcf3cd3507..a0cdee57f600 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -327,7 +327,7 @@ function ReportActionsList({ report={report} linkedReportActionID={linkedReportActionID} hasOutstandingIOU={hasOutstandingIOU} - sortedReportActions={sortedReportActions} + displayAsGroup={ReportActionsUtils.isConsecutiveActionMadeByPreviousActor(sortedReportActions, index)} mostRecentIOUReportActionID={mostRecentIOUReportActionID} shouldHideThreadDividerLine={shouldHideThreadDividerLine} shouldDisplayNewMarker={shouldDisplayNewMarker(reportAction, index)} diff --git a/src/pages/home/report/ReportActionsListItemRenderer.js b/src/pages/home/report/ReportActionsListItemRenderer.js index 40b9ee9142b7..e8e1f630ceb6 100644 --- a/src/pages/home/report/ReportActionsListItemRenderer.js +++ b/src/pages/home/report/ReportActionsListItemRenderer.js @@ -22,9 +22,6 @@ const propTypes = { /** Whether the option has an outstanding IOU */ hasOutstandingIOU: PropTypes.bool, - /** Sorted actions prepared for display */ - sortedReportActions: PropTypes.arrayOf(PropTypes.shape(reportActionPropTypes)).isRequired, - /** The ID of the most recent IOU report action connected with the shown report */ mostRecentIOUReportActionID: PropTypes.string, @@ -36,6 +33,8 @@ const propTypes = { /** Linked report action ID */ linkedReportActionID: PropTypes.string, + + displayAsGroup: PropTypes.bool.isRequired, }; const defaultProps = { @@ -49,7 +48,7 @@ function ReportActionsListItemRenderer({ index, report, hasOutstandingIOU, - sortedReportActions, + displayAsGroup, mostRecentIOUReportActionID, shouldHideThreadDividerLine, shouldDisplayNewMarker, @@ -73,7 +72,7 @@ function ReportActionsListItemRenderer({ report={report} action={reportAction} linkedReportActionID={linkedReportActionID} - displayAsGroup={ReportActionsUtils.isConsecutiveActionMadeByPreviousActor(sortedReportActions, index)} + displayAsGroup={displayAsGroup} shouldDisplayNewMarker={shouldDisplayNewMarker} shouldShowSubscriptAvatar={ (ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isExpenseReport(report)) && diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index a3671faf194c..db9b6e1c3ecf 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -179,6 +179,14 @@ function ReportActionsView(props) { } }; + const report = useMemo( + () => ({ + lastReadTime: props.report.lastReadTime, + reportID: props.report.reportID, + }), + [props.report.lastReadTime, props.report.reportID], + ); + // Comments have not loaded at all yet do nothing if (!_.size(props.reportActions)) { return null; @@ -187,7 +195,7 @@ function ReportActionsView(props) { return ( <> {}, pendingAction: null, personalDetails: {}, - shouldShowComposeInput: true, shouldDisableCompose: false, listHeight: 0, isReportReadyForDisplay: true, + lastReportAction: null, + isEmptyChat: true, }; function ReportFooter(props) { @@ -72,6 +67,33 @@ function ReportFooter(props) { const isSmallSizeLayout = props.windowWidth - (props.isSmallScreenWidth ? 0 : variables.sideBarWidth) < variables.anonymousReportFooterBreakpoint; const hideComposer = ReportUtils.shouldDisableWriteActions(props.report); + const [shouldShowComposeInput, setShouldShowComposeInput] = useState(false); + + useEffect(() => { + // eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs + const connID = Onyx.connect({ + key: ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT, + callback: (val) => { + if (val === shouldShowComposeInput) { + return; + } + setShouldShowComposeInput(val); + }, + }); + + return () => { + Onyx.disconnect(connID); + }; + }, [shouldShowComposeInput]); + + const onSubmitComment = useCallback( + (text) => { + Report.addComment(props.reportID, text); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + return ( <> {hideComposer && ( @@ -89,14 +111,14 @@ function ReportFooter(props) { )} )} - {!hideComposer && (props.shouldShowComposeInput || !props.isSmallScreenWidth) && ( + {!hideComposer && (shouldShowComposeInput || !props.isSmallScreenWidth) && ( + isEqual(prevProps.report, nextProps.report) && + isEqual(prevProps.reportActions, nextProps.reportActions) && + isEqual(prevProps.personalDetails, nextProps.personalDetails) && + prevProps.pendingAction === nextProps.pendingAction && + prevProps.shouldDisableCompose === nextProps.shouldDisableCompose && + prevProps.listHeight === nextProps.listHeight && + prevProps.isReportReadyForDisplay === nextProps.isReportReadyForDisplay, + ), +); From 75ad6135c5391135cdd39d1f6e2b23a77825e0eb Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 23 Oct 2023 15:43:42 +0500 Subject: [PATCH 003/142] perf: reduce re-renders --- .../ComposerWithSuggestions.js | 100 +++++++++++------- .../ReportActionCompose.js | 68 ++++-------- 2 files changed, 83 insertions(+), 85 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js index e194d0870885..bb5109073cfd 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js @@ -1,9 +1,10 @@ -import React, {useEffect, useCallback, useState, useRef, useMemo, useImperativeHandle} from 'react'; +import React, {useEffect, useCallback, useState, useRef, useMemo, useImperativeHandle, memo} from 'react'; import {View, NativeModules, findNodeHandle} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import lodashGet from 'lodash/get'; import {useIsFocused, useNavigation} from '@react-navigation/native'; +import {runOnJS, useAnimatedRef} from 'react-native-reanimated'; import styles from '../../../../styles/styles'; import themeColors from '../../../../styles/themes/default'; import Composer from '../../../../components/Composer'; @@ -23,7 +24,6 @@ import * as EmojiUtils from '../../../../libs/EmojiUtils'; import * as User from '../../../../libs/actions/User'; import * as ReportUtils from '../../../../libs/ReportUtils'; import * as SuggestionUtils from '../../../../libs/SuggestionUtils'; -import * as ReportActionsUtils from '../../../../libs/ReportActionsUtils'; import canFocusInputOnScreenFocus from '../../../../libs/canFocusInputOnScreenFocus'; import SilentCommentUpdater from './SilentCommentUpdater'; import Suggestions from './Suggestions'; @@ -36,6 +36,10 @@ import focusWithDelay from '../../../../libs/focusWithDelay'; import useDebounce from '../../../../hooks/useDebounce'; import updateMultilineInputRange from '../../../../libs/UpdateMultilineInputRange'; import * as InputFocus from '../../../../libs/actions/InputFocus'; +import SendButton from './SendButton'; +import updatePropsPaperWorklet from '../../../../libs/updatePropsPaperWorklet'; +import EmojiPickerButton from '../../../../components/EmojiPicker/EmojiPickerButton'; +import * as DeviceCapabilities from '../../../../libs/DeviceCapabilities'; const {RNTextInputReset} = NativeModules; @@ -70,14 +74,14 @@ function ComposerWithSuggestions({ // Onyx modal, preferredSkinTone, - parentReportActions, numberOfLines, // HOCs isKeyboardShown, // Props: Report reportID, - report, - reportActions, + includeChronos, + isEmptyChat, + lastReportAction, // Focus onFocus, onBlur, @@ -92,14 +96,13 @@ function ComposerWithSuggestions({ disabled, isFullComposerAvailable, setIsFullComposerAvailable, - setIsCommentEmpty, + isSendDisabled, handleSendMessage, shouldShowComposeInput, measureParentContainer, listHeight, // Refs suggestionsRef, - animatedRef, forwardedRef, isNextModalWillOpenRef, editFocused, @@ -108,19 +111,21 @@ function ComposerWithSuggestions({ const isFocused = useIsFocused(); const navigation = useNavigation(); const emojisPresentBefore = useRef([]); + + const draftComment = getDraftComment(reportID) || ''; + const [isCommentEmpty, setIsCommentEmpty] = useState(() => !draftComment || !!draftComment.match(/^(\s)*$/)); + const animatedRef = useAnimatedRef(); const [value, setValue] = useState(() => { - const draft = getDraftComment(reportID) || ''; - if (draft) { - emojisPresentBefore.current = EmojiUtils.extractEmojis(draft); + if (draftComment) { + emojisPresentBefore.current = EmojiUtils.extractEmojis(draftComment); } - return draft; + return draftComment; }); const commentRef = useRef(value); - const {isSmallScreenWidth} = useWindowDimensions(); + const {isSmallScreenWidth, isMediumScreenWidth} = useWindowDimensions(); const maxComposerLines = isSmallScreenWidth ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES; - const isEmptyChat = useMemo(() => _.size(reportActions) === 1, [reportActions]); const shouldAutoFocus = !modal.isVisible && (shouldFocusInputOnScreenFocus || isEmptyChat) && shouldShowComposeInput; const valueRef = useRef(value); @@ -202,6 +207,20 @@ function ComposerWithSuggestions({ [], ); + const sendMessage = useCallback(() => { + 'worklet'; + + if (isCommentEmpty) { + return; + } + + runOnJS(handleSendMessage)(); + const viewTag = animatedRef(); + const viewName = 'RCTMultilineTextInputView'; + const updates = {text: ''}; + updatePropsPaperWorklet(viewTag, viewName, updates); // clears native text input on the UI thread + }, [animatedRef, handleSendMessage, isCommentEmpty]); + /** * Update the value of the comment in Onyx * @@ -225,7 +244,13 @@ function ComposerWithSuggestions({ } } emojisPresentBefore.current = emojis; - setIsCommentEmpty(!!newComment.match(/^(\s)*$/)); + const isNewCommentEmpty = !!newComment.match(/^(\s)*$/); + const isPrevCommentEmpty = !!commentRef.current.match(/^(\s)*$/); + + /** Only update isCommentEmpty state if it's different from previous one */ + if (isNewCommentEmpty !== isPrevCommentEmpty) { + setIsCommentEmpty(isNewCommentEmpty); + } setValue(newComment); if (commentValue !== newComment) { const remainder = ComposerUtils.getCommonSuffixLength(commentValue, newComment); @@ -338,26 +363,20 @@ function ComposerWithSuggestions({ // Submit the form when Enter is pressed if (e.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey && !e.shiftKey) { e.preventDefault(); - handleSendMessage(); + sendMessage(); } // Trigger the edit box for last sent message if ArrowUp is pressed and the comment is empty and Chronos is not in the participants const valueLength = valueRef.current.length; - if (e.key === CONST.KEYBOARD_SHORTCUTS.ARROW_UP.shortcutKey && textInputRef.current.selectionStart === 0 && valueLength === 0 && !ReportUtils.chatIncludesChronos(report)) { + if (e.key === CONST.KEYBOARD_SHORTCUTS.ARROW_UP.shortcutKey && textInputRef.current.selectionStart === 0 && valueLength === 0 && !includeChronos) { e.preventDefault(); - const parentReportActionID = lodashGet(report, 'parentReportActionID', ''); - const parentReportAction = lodashGet(parentReportActions, [parentReportActionID], {}); - const lastReportAction = _.find( - [...reportActions, parentReportAction], - (action) => ReportUtils.canEditReportAction(action) && !ReportActionsUtils.isMoneyRequestAction(action), - ); if (lastReportAction) { Report.saveReportActionDraft(reportID, lastReportAction, _.last(lastReportAction.message).html); } } }, - [isKeyboardShown, isSmallScreenWidth, parentReportActions, report, reportActions, reportID, handleSendMessage, suggestionsRef, valueRef], + [isSmallScreenWidth, isKeyboardShown, suggestionsRef, includeChronos, sendMessage, lastReportAction, reportID], ); const onSelectionChange = useCallback( @@ -561,6 +580,19 @@ function ComposerWithSuggestions({ }} onScroll={hideSuggestionMenu} /> + + {DeviceCapabilities.canUseTouchScreen() && isMediumScreenWidth ? null : ( + replaceSelectionWithText(...args)} + emojiPickerID={reportID} + /> + )} + `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, - canEvict: false, - initWithStoredValues: false, - }, }), )( - React.forwardRef((props, ref) => ( - - )), + memo( + React.forwardRef((props, ref) => ( + + )), + ), ); diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js index dd4d51653546..8796fd1c1b85 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js @@ -1,10 +1,10 @@ -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import PropTypes from 'prop-types'; import {View} from 'react-native'; import _ from 'underscore'; import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; -import {runOnJS, useAnimatedRef} from 'react-native-reanimated'; +import {runOnJS} from 'react-native-reanimated'; import {PortalHost} from '@gorhom/portal'; import styles from '../../../../styles/styles'; import ONYXKEYS from '../../../../ONYXKEYS'; @@ -21,23 +21,17 @@ import ParticipantLocalTime from '../ParticipantLocalTime'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes, withCurrentUserPersonalDetailsDefaultProps} from '../../../../components/withCurrentUserPersonalDetails'; import {withNetwork} from '../../../../components/OnyxProvider'; import * as User from '../../../../libs/actions/User'; -import EmojiPickerButton from '../../../../components/EmojiPicker/EmojiPickerButton'; -import * as DeviceCapabilities from '../../../../libs/DeviceCapabilities'; import OfflineIndicator from '../../../../components/OfflineIndicator'; import ExceededCommentLength from '../../../../components/ExceededCommentLength'; import ReportDropUI from '../ReportDropUI'; import reportPropTypes from '../../../reportPropTypes'; import OfflineWithFeedback from '../../../../components/OfflineWithFeedback'; -import SendButton from './SendButton'; import AttachmentPickerWithMenuItems from './AttachmentPickerWithMenuItems'; import ComposerWithSuggestions from './ComposerWithSuggestions'; -import reportActionPropTypes from '../reportActionPropTypes'; import useLocalize from '../../../../hooks/useLocalize'; import getModalState from '../../../../libs/getModalState'; import useWindowDimensions from '../../../../hooks/useWindowDimensions'; import * as EmojiPickerActions from '../../../../libs/actions/EmojiPickerAction'; -import getDraftComment from '../../../../libs/ComposerUtils/getDraftComment'; -import updatePropsPaperWorklet from '../../../../libs/updatePropsPaperWorklet'; const propTypes = { /** A method to call when the form is submitted */ @@ -46,9 +40,6 @@ const propTypes = { /** The ID of the report actions will be created for */ reportID: PropTypes.string.isRequired, - /** Array of report actions for this report */ - reportActions: PropTypes.arrayOf(PropTypes.shape(reportActionPropTypes)), - /** Personal details of all the users */ personalDetails: PropTypes.objectOf(participantPropTypes), @@ -111,14 +102,14 @@ function ReportActionCompose({ personalDetails, report, reportID, - reportActions, + isEmptyChat, + lastReportAction, listHeight, shouldShowComposeInput, isReportReadyForDisplay, }) { const {translate} = useLocalize(); - const {isMediumScreenWidth, isSmallScreenWidth} = useWindowDimensions(); - const animatedRef = useAnimatedRef(); + const {isSmallScreenWidth} = useWindowDimensions(); const actionButtonRef = useRef(null); /** @@ -134,10 +125,6 @@ function ReportActionCompose({ * Updates the should clear state of the composer */ const [textInputShouldClear, setTextInputShouldClear] = useState(false); - const [isCommentEmpty, setIsCommentEmpty] = useState(() => { - const draftComment = getDraftComment(reportID); - return !draftComment || !!draftComment.match(/^(\s)*$/); - }); /** * Updates the visibility state of the menu @@ -164,7 +151,9 @@ function ReportActionCompose({ [personalDetails, report, currentUserPersonalDetails.accountID, isComposerFullSize], ); - const isBlockedFromConcierge = useMemo(() => ReportUtils.chatIncludesConcierge(report) && User.isBlockedFromConcierge(blockedFromConcierge), [report, blockedFromConcierge]); + const includesConcierge = useMemo(() => ReportUtils.chatIncludesConcierge({participantAccountIDs: report.participantAccountIDs}), [report.participantAccountIDs]); + const userBlockedFromConcierge = useMemo(() => User.isBlockedFromConcierge(blockedFromConcierge), [blockedFromConcierge]); + const isBlockedFromConcierge = useMemo(() => includesConcierge && userBlockedFromConcierge, [includesConcierge, userBlockedFromConcierge]); // If we are on a small width device then don't show last 3 items from conciergePlaceholderOptions const conciergePlaceholderRandomIndex = useMemo( @@ -175,8 +164,8 @@ function ReportActionCompose({ // Placeholder to display in the chat input. const inputPlaceholder = useMemo(() => { - if (ReportUtils.chatIncludesConcierge(report)) { - if (User.isBlockedFromConcierge(blockedFromConcierge)) { + if (includesConcierge) { + if (userBlockedFromConcierge) { return translate('reportActionCompose.blockedFromConcierge'); } @@ -184,7 +173,7 @@ function ReportActionCompose({ } return translate('reportActionCompose.writeSomething'); - }, [report, blockedFromConcierge, translate, conciergePlaceholderRandomIndex]); + }, [includesConcierge, translate, userBlockedFromConcierge, conciergePlaceholderRandomIndex]); const focus = () => { if (composerRef === null || composerRef.current === null) { @@ -322,24 +311,16 @@ function ReportActionCompose({ const hasReportRecipient = _.isObject(reportRecipient) && !_.isEmpty(reportRecipient); - const isSendDisabled = isCommentEmpty || isBlockedFromConcierge || disabled || hasExceededMaxCommentLength; + const isSendDisabled = isBlockedFromConcierge || disabled || hasExceededMaxCommentLength; const handleSendMessage = useCallback(() => { - 'worklet'; - if (isSendDisabled || !isReportReadyForDisplay) { return; } - const viewTag = animatedRef(); - const viewName = 'RCTMultilineTextInputView'; - const updates = {text: ''}; - // We are setting the isCommentEmpty flag to true so the status of it will be in sync of the native text input state - runOnJS(setIsCommentEmpty)(true); runOnJS(resetFullComposerSize)(); - updatePropsPaperWorklet(viewTag, viewName, updates); // clears native text input on the UI thread runOnJS(submitForm)(); - }, [isSendDisabled, resetFullComposerSize, submitForm, animatedRef, isReportReadyForDisplay]); + }, [isSendDisabled, resetFullComposerSize, submitForm, isReportReadyForDisplay]); return ( { @@ -427,18 +409,6 @@ function ReportActionCompose({ )} - {DeviceCapabilities.canUseTouchScreen() && isMediumScreenWidth ? null : ( - composerRef.current.replaceSelectionWithText(...args)} - emojiPickerID={report.reportID} - /> - )} - Date: Tue, 24 Oct 2023 17:04:11 +0500 Subject: [PATCH 004/142] refactor: make action prop lightweight --- .../report/ReportActionsListItemRenderer.js | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionsListItemRenderer.js b/src/pages/home/report/ReportActionsListItemRenderer.js index e8e1f630ceb6..a7ec10a30eea 100644 --- a/src/pages/home/report/ReportActionsListItemRenderer.js +++ b/src/pages/home/report/ReportActionsListItemRenderer.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import React, {memo} from 'react'; +import React, {memo, useMemo} from 'react'; import _ from 'underscore'; import CONST from '../../../CONST'; import * as ReportActionsUtils from '../../../libs/ReportActionsUtils'; @@ -59,6 +59,41 @@ function ReportActionsListItemRenderer({ ReportUtils.isChatThread(report) && !ReportActionsUtils.isTransactionThread(ReportActionsUtils.getParentReportAction(report)); + const action = useMemo( + () => ({ + reportActionID: reportAction.reportActionID, + message: reportAction.message, + pendingAction: reportAction.pendingAction, + actionName: reportAction.actionName, + errors: reportAction.errors, + originalMessage: reportAction.originalMessage, + childCommenterCount: reportAction.childCommenterCount, + linkMetadata: reportAction.linkMetadata, + childReportID: reportAction.childReportID, + childLastVisibleActionCreated: reportAction.childLastVisibleActionCreated, + whisperedToAccountIDs: reportAction.whisperedToAccountIDs, + error: reportAction.error, + created: reportAction.created, + actorAccountID: reportAction.actorAccountID, + }), + [ + reportAction.actionName, + reportAction.childCommenterCount, + reportAction.childLastVisibleActionCreated, + reportAction.childReportID, + reportAction.created, + reportAction.error, + reportAction.errors, + reportAction.linkMetadata, + reportAction.message, + reportAction.originalMessage, + reportAction.pendingAction, + reportAction.reportActionID, + reportAction.whisperedToAccountIDs, + reportAction.actorAccountID, + ], + ); + return shouldDisplayParentAction ? ( Date: Wed, 25 Oct 2023 18:25:16 +0500 Subject: [PATCH 005/142] perf: add Interaction Manager --- src/libs/actions/OnyxUpdates.ts | 29 +++++++++++++++------------ src/libs/actions/PersistedRequests.ts | 19 ++++++++++-------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index 39a20ae9362a..5f13d8133f16 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -1,5 +1,6 @@ import Onyx, {OnyxEntry} from 'react-native-onyx'; import {Merge} from 'type-fest'; +import {InteractionManager} from 'react-native'; import PusherUtils from '../PusherUtils'; import ONYXKEYS from '../../ONYXKEYS'; import * as QueuedOnyxUpdates from './QueuedOnyxUpdates'; @@ -61,19 +62,21 @@ function apply({lastUpdateID, type, request, response, updates}: Merge | undefined { console.debug(`[OnyxUpdateManager] Applying update type: ${type} with lastUpdateID: ${lastUpdateID}`, {request, response, updates}); - if (lastUpdateID && lastUpdateIDAppliedToClient && Number(lastUpdateID) < lastUpdateIDAppliedToClient) { - console.debug('[OnyxUpdateManager] Update received was older than current state, returning without applying the updates'); - return Promise.resolve(); - } - if (lastUpdateID && (lastUpdateIDAppliedToClient === null || Number(lastUpdateID) > lastUpdateIDAppliedToClient)) { - Onyx.merge(ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT, Number(lastUpdateID)); - } - if (type === CONST.ONYX_UPDATE_TYPES.HTTPS && request && response) { - return applyHTTPSOnyxUpdates(request, response); - } - if (type === CONST.ONYX_UPDATE_TYPES.PUSHER && updates) { - return applyPusherOnyxUpdates(updates); - } + InteractionManager.runAfterInteractions(() => { + if (lastUpdateID && lastUpdateIDAppliedToClient && Number(lastUpdateID) < lastUpdateIDAppliedToClient) { + console.debug('[OnyxUpdateManager] Update received was older than current state, returning without applying the updates'); + return Promise.resolve(); + } + if (lastUpdateID && (lastUpdateIDAppliedToClient === null || Number(lastUpdateID) > lastUpdateIDAppliedToClient)) { + Onyx.merge(ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT, Number(lastUpdateID)); + } + if (type === CONST.ONYX_UPDATE_TYPES.HTTPS && request && response) { + return applyHTTPSOnyxUpdates(request, response); + } + if (type === CONST.ONYX_UPDATE_TYPES.PUSHER && updates) { + return applyPusherOnyxUpdates(updates); + } + }); } /** diff --git a/src/libs/actions/PersistedRequests.ts b/src/libs/actions/PersistedRequests.ts index d9f4ed020109..1e1a147cecd3 100644 --- a/src/libs/actions/PersistedRequests.ts +++ b/src/libs/actions/PersistedRequests.ts @@ -1,5 +1,6 @@ import Onyx from 'react-native-onyx'; import isEqual from 'lodash/isEqual'; +import {InteractionManager} from 'react-native'; import ONYXKEYS from '../../ONYXKEYS'; import {Request} from '../../types/onyx'; @@ -33,14 +34,16 @@ function remove(requestToRemove: Request) { * We only remove the first matching request because the order of requests matters. * If we were to remove all matching requests, we can end up with a final state that is different than what the user intended. */ - const requests = [...persistedRequests]; - const index = requests.findIndex((persistedRequest) => isEqual(persistedRequest, requestToRemove)); - if (index === -1) { - return; - } - requests.splice(index, 1); - persistedRequests = requests; - Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests); + InteractionManager.runAfterInteractions(() => { + const requests = [...persistedRequests]; + const index = requests.findIndex((persistedRequest) => isEqual(persistedRequest, requestToRemove)); + if (index === -1) { + return; + } + requests.splice(index, 1); + persistedRequests = requests; + Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests); + }); } function update(oldRequestIndex: number, newRequest: Request) { From 60d3034b9c91aaeb31626282f89e61e587416cbd Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 25 Oct 2023 18:30:54 +0500 Subject: [PATCH 006/142] perf: reference bindings of functions --- .../ComposerWithSuggestions.js | 33 ++++++++++++++----- .../composerWithSuggestionsProps.js | 18 ---------- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js index 4c3136ff6328..97435e510389 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js @@ -40,6 +40,7 @@ import SendButton from './SendButton'; import updatePropsPaperWorklet from '../../../../libs/updatePropsPaperWorklet'; import EmojiPickerButton from '../../../../components/EmojiPicker/EmojiPickerButton'; import * as DeviceCapabilities from '../../../../libs/DeviceCapabilities'; +import * as ReportActionsUtils from '../../../../libs/ReportActionsUtils'; const {RNTextInputReset} = NativeModules; @@ -540,6 +541,26 @@ function ComposerWithSuggestions({ [blur, focus, prepareCommentAndResetComposer, replaceSelectionWithText], ); + const onLayout = useCallback( + (e) => { + const composerLayoutHeight = e.nativeEvent.layout.height; + if (composerHeight === composerLayoutHeight) { + return; + } + setComposerHeight(composerLayoutHeight); + }, + [composerHeight], + ); + + const onClear = useCallback(() => { + setTextInputShouldClear(false); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const onChangeText = useCallback((text) => { + updateComment(text, true); + }, []); + return ( <> @@ -551,7 +572,7 @@ function ComposerWithSuggestions({ textAlignVertical="top" placeholder={inputPlaceholder} placeholderTextColor={themeColors.placeholderText} - onChangeText={(commentValue) => updateComment(commentValue, true)} + onChangeText={onChangeText} onKeyPress={triggerHotkeyActions} style={[styles.textInputCompose, isComposerFullSize ? styles.textInputFullCompose : styles.flex4]} maxLines={maxComposerLines} @@ -560,7 +581,7 @@ function ComposerWithSuggestions({ onClick={setShouldBlockSuggestionCalcToFalse} onPasteFile={displayFileInModal} shouldClear={textInputShouldClear} - onClear={() => setTextInputShouldClear(false)} + onClear={onClear} isDisabled={isBlockedFromConcierge || disabled} isReportActionCompose selection={selection} @@ -572,13 +593,7 @@ function ComposerWithSuggestions({ numberOfLines={numberOfLines} onNumberOfLinesChange={updateNumberOfLines} shouldCalculateCaretPosition - onLayout={(e) => { - const composerLayoutHeight = e.nativeEvent.layout.height; - if (composerHeight === composerLayoutHeight) { - return; - } - setComposerHeight(composerLayoutHeight); - }} + onLayout={onLayout} onScroll={hideSuggestionMenu} /> diff --git a/src/pages/home/report/ReportActionCompose/composerWithSuggestionsProps.js b/src/pages/home/report/ReportActionCompose/composerWithSuggestionsProps.js index 017b5aecb4ae..246399d334d6 100644 --- a/src/pages/home/report/ReportActionCompose/composerWithSuggestionsProps.js +++ b/src/pages/home/report/ReportActionCompose/composerWithSuggestionsProps.js @@ -1,5 +1,4 @@ import PropTypes from 'prop-types'; -import reportActionPropTypes from '../reportActionPropTypes'; import CONST from '../../../../CONST'; const propTypes = { @@ -18,20 +17,9 @@ const propTypes = { /** Whether the keyboard is open or not */ isKeyboardShown: PropTypes.bool.isRequired, - /** The actions from the parent report */ - parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), - - /** Array of report actions for this report */ - reportActions: PropTypes.arrayOf(PropTypes.shape(reportActionPropTypes)), - /** The ID of the report */ reportID: PropTypes.string.isRequired, - /** The report currently being looked at */ - report: PropTypes.shape({ - parentReportID: PropTypes.string, - }).isRequired, - /** Callback when the input is focused */ onFocus: PropTypes.func.isRequired, @@ -68,9 +56,6 @@ const propTypes = { /** Function to set whether the full composer is available or not */ setIsFullComposerAvailable: PropTypes.func.isRequired, - /** Function to set whether the comment is empty or not */ - setIsCommentEmpty: PropTypes.func.isRequired, - /** A method to call when the form is submitted */ handleSendMessage: PropTypes.func.isRequired, @@ -97,9 +82,6 @@ const propTypes = { }), }).isRequired, - /** Ref for the animated view (text input) */ - animatedRef: PropTypes.func.isRequired, - /** Ref for the composer */ forwardedRef: PropTypes.shape({current: PropTypes.shape({})}), From 851c61f1edc0291d022b3480d22778e97a492ce9 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 26 Oct 2023 15:04:22 +0500 Subject: [PATCH 007/142] perf: optimise rendering --- .../ComposerWithSuggestions.js | 5 ---- .../ReportActionCompose/SuggestionMention.js | 27 +++++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js index 97435e510389..c41589fe4b68 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js @@ -283,14 +283,9 @@ function ComposerWithSuggestions({ } }, [ - debouncedUpdateFrequentlyUsedEmojis, - preferredLocale, preferredSkinTone, reportID, - setIsCommentEmpty, suggestionsRef, - raiseIsScrollLikelyLayoutTriggered, - debouncedSaveReportComment, ], ); diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.js b/src/pages/home/report/ReportActionCompose/SuggestionMention.js index 67d6ac0632eb..237b1e956966 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.js +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.js @@ -1,7 +1,7 @@ import React, {useState, useCallback, useRef, useImperativeHandle, useEffect} from 'react'; import PropTypes from 'prop-types'; import _ from 'underscore'; -import {withOnyx} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; import CONST from '../../../../CONST'; import useArrowKeyFocusManager from '../../../../hooks/useArrowKeyFocusManager'; import MentionSuggestions from '../../../../components/MentionSuggestions'; @@ -29,9 +29,6 @@ const defaultSuggestionsValues = { }; const propTypes = { - /** Personal details of all users */ - personalDetails: PropTypes.objectOf(personalDetailsPropType), - /** A ref to this component */ forwardedRef: PropTypes.shape({current: PropTypes.shape({})}), @@ -39,17 +36,28 @@ const propTypes = { }; const defaultProps = { - personalDetails: {}, forwardedRef: null, }; +/** + * We only need the personalDetails once because as long as the + * user is in the ReportScreen, these details won't be changing, + * hence we don't have to use it with `withOnyx`. + */ +let allPersonalDetails = {}; +Onyx.connect({ + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + callback: (val) => { + allPersonalDetails = val; + }, +}); + function SuggestionMention({ value, setValue, selection, setSelection, isComposerFullSize, - personalDetails, updateComment, composerHeight, forwardedRef, @@ -57,6 +65,7 @@ function SuggestionMention({ measureParentContainer, isComposerFocused, }) { + const personalDetails = allPersonalDetails; const {translate} = useLocalize(); const previousValue = usePrevious(value); const [suggestionValues, setSuggestionValues] = useState(defaultSuggestionsValues); @@ -316,8 +325,4 @@ const SuggestionMentionWithRef = React.forwardRef((props, ref) => ( SuggestionMentionWithRef.displayName = 'SuggestionMentionWithRef'; -export default withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, -})(SuggestionMentionWithRef); +export default SuggestionMentionWithRef; From e613f4278dc5cdfb85cf79670f9f6f575c826918 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 26 Oct 2023 16:28:03 +0500 Subject: [PATCH 008/142] perf: optimise usage of personal detail list --- src/components/AnonymousReportFooter.js | 5 --- src/components/ArchivedReportFooter.js | 13 ++----- src/components/AvatarWithDisplayName.js | 8 +--- src/components/HeaderWithBackButton/index.js | 2 - .../LHNOptionsList/OptionRowLHNData.js | 39 +------------------ src/components/MoneyReportHeader.js | 6 +-- src/components/MoneyRequestHeader.js | 6 +-- .../withCurrentUserPersonalDetails.tsx | 9 +---- src/libs/OptionsListUtils.js | 5 ++- src/libs/PersonalDetailsUtils.js | 27 +++++++++++-- src/libs/ReportUtils.js | 19 +++++---- src/libs/SidebarUtils.js | 10 ++++- src/pages/home/HeaderView.js | 8 +--- src/pages/home/ReportScreen.js | 15 +------ .../ReportActionCompose.js | 17 ++------ src/pages/home/report/ReportActionItem.js | 8 ++-- .../home/report/ReportActionItemCreated.js | 9 +---- .../home/report/ReportActionItemSingle.js | 5 ++- src/pages/home/report/ReportActionsList.js | 11 +----- src/pages/home/report/ReportFooter.js | 6 --- 20 files changed, 72 insertions(+), 156 deletions(-) diff --git a/src/components/AnonymousReportFooter.js b/src/components/AnonymousReportFooter.js index 43933210dc0b..902a31f12ee3 100644 --- a/src/components/AnonymousReportFooter.js +++ b/src/components/AnonymousReportFooter.js @@ -16,16 +16,12 @@ const propTypes = { isSmallSizeLayout: PropTypes.bool, - /** Personal details of all the users */ - personalDetails: PropTypes.objectOf(participantPropTypes), - ...withLocalizePropTypes, }; const defaultProps = { report: {}, isSmallSizeLayout: false, - personalDetails: {}, }; function AnonymousReportFooter(props) { @@ -34,7 +30,6 @@ function AnonymousReportFooter(props) { diff --git a/src/components/ArchivedReportFooter.js b/src/components/ArchivedReportFooter.js index 71d331b68db0..bca5f0b391c8 100644 --- a/src/components/ArchivedReportFooter.js +++ b/src/components/ArchivedReportFooter.js @@ -34,9 +34,6 @@ const propTypes = { /** The archived report */ report: reportPropTypes.isRequired, - /** Personal details of all users */ - personalDetails: PropTypes.objectOf(personalDetailsPropType), - ...withLocalizePropTypes, }; @@ -46,19 +43,18 @@ const defaultProps = { reason: CONST.REPORT.ARCHIVE_REASON.DEFAULT, }, }, - personalDetails: {}, }; function ArchivedReportFooter(props) { const archiveReason = lodashGet(props.reportClosedAction, 'originalMessage.reason', CONST.REPORT.ARCHIVE_REASON.DEFAULT); - let displayName = PersonalDetailsUtils.getDisplayNameOrDefault(props.personalDetails, [props.report.ownerAccountID, 'displayName']); + let displayName = PersonalDetailsUtils.getDisplayNameOrDefault(null, [props.report.ownerAccountID, 'displayName']); let oldDisplayName; if (archiveReason === CONST.REPORT.ARCHIVE_REASON.ACCOUNT_MERGED) { const newAccountID = props.reportClosedAction.originalMessage.newAccountID; const oldAccountID = props.reportClosedAction.originalMessage.oldAccountID; - displayName = PersonalDetailsUtils.getDisplayNameOrDefault(props.personalDetails, [newAccountID, 'displayName']); - oldDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(props.personalDetails, [oldAccountID, 'displayName']); + displayName = PersonalDetailsUtils.getDisplayNameOrDefault(null, [newAccountID, 'displayName']); + oldDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(null, [oldAccountID, 'displayName']); } const shouldRenderHTML = archiveReason !== CONST.REPORT.ARCHIVE_REASON.DEFAULT; @@ -92,9 +88,6 @@ ArchivedReportFooter.displayName = 'ArchivedReportFooter'; export default compose( withLocalize, withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, reportClosedAction: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, canEvict: false, diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index 03ae8f51bfb6..3a4ebee0aa42 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -37,9 +37,6 @@ const propTypes = { /** The size of the avatar */ size: PropTypes.oneOf(_.values(CONST.AVATAR_SIZE)), - /** Personal details of all the users */ - personalDetails: PropTypes.objectOf(participantPropTypes), - /** Whether if it's an unauthenticated user */ isAnonymous: PropTypes.bool, @@ -50,7 +47,6 @@ const propTypes = { }; const defaultProps = { - personalDetails: {}, policy: {}, report: {}, isAnonymous: false, @@ -93,8 +89,8 @@ function AvatarWithDisplayName(props) { const subtitle = ReportUtils.getChatRoomSubtitle(props.report); const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(props.report); const isMoneyRequestOrReport = ReportUtils.isMoneyRequestReport(props.report) || ReportUtils.isMoneyRequest(props.report); - const icons = ReportUtils.getIcons(props.report, props.personalDetails, props.policy); - const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs([props.report.ownerAccountID], props.personalDetails); + const icons = ReportUtils.getIcons(props.report, null, props.policy); + const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs([props.report.ownerAccountID]); const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(_.values(ownerPersonalDetails), false); const shouldShowSubscriptAvatar = ReportUtils.shouldReportShowSubscript(props.report); const isExpenseRequest = ReportUtils.isExpenseRequest(props.report); diff --git a/src/components/HeaderWithBackButton/index.js b/src/components/HeaderWithBackButton/index.js index 6a02ce02237d..8e5fed15ff7e 100755 --- a/src/components/HeaderWithBackButton/index.js +++ b/src/components/HeaderWithBackButton/index.js @@ -28,7 +28,6 @@ function HeaderWithBackButton({ onThreeDotsButtonPress = () => {}, report = null, policy = {}, - personalDetails = {}, shouldShowAvatarWithDisplay = false, shouldShowBackButton = true, shouldShowBorderBottom = false, @@ -85,7 +84,6 @@ function HeaderWithBackButton({ ) : ( diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index e93e3690138e..2ddf8a687cfd 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -9,11 +9,9 @@ import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; import OptionRowLHN, {propTypes as basePropTypes, defaultProps as baseDefaultProps} from './OptionRowLHN'; import * as Report from '../../libs/actions/Report'; -import * as UserUtils from '../../libs/UserUtils'; import * as ReportActionsUtils from '../../libs/ReportActionsUtils'; import * as TransactionUtils from '../../libs/TransactionUtils'; -import participantPropTypes from '../participantPropTypes'; import CONST from '../../CONST'; import reportActionPropTypes from '../../pages/home/report/reportActionPropTypes'; @@ -21,9 +19,6 @@ const propTypes = { /** Whether row should be focused */ isFocused: PropTypes.bool, - /** List of users' personal details */ - personalDetails: PropTypes.objectOf(participantPropTypes), - /** The preferred language for the app */ preferredLocale: PropTypes.string, @@ -54,7 +49,6 @@ const propTypes = { const defaultProps = { isFocused: false, - personalDetails: {}, fullReport: {}, policy: {}, parentReportActions: {}, @@ -73,7 +67,6 @@ function OptionRowLHNData({ isFocused, fullReport, reportActions, - personalDetails, preferredLocale, comment, policy, @@ -97,7 +90,7 @@ function OptionRowLHNData({ const optionItem = useMemo(() => { // Note: ideally we'd have this as a dependent selector in onyx! - const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale, policy, parentReportAction); + const item = SidebarUtils.getOptionData(fullReport, reportActions, preferredLocale, policy, parentReportAction); if (deepEqual(item, optionItemRef.current)) { return optionItemRef.current; } @@ -106,7 +99,7 @@ function OptionRowLHNData({ // Listen parentReportAction to update title of thread report when parentReportAction changed // Listen to transaction to update title of transaction report when transaction changed // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transaction]); + }, [fullReport, linkedTransaction, reportActions, preferredLocale, policy, parentReportAction, transaction]); useEffect(() => { if (!optionItem || optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { @@ -130,30 +123,6 @@ OptionRowLHNData.propTypes = propTypes; OptionRowLHNData.defaultProps = defaultProps; OptionRowLHNData.displayName = 'OptionRowLHNData'; -/** - * @param {Object} [personalDetails] - * @returns {Object|undefined} - */ -const personalDetailsSelector = (personalDetails) => - _.reduce( - personalDetails, - (finalPersonalDetails, personalData, accountID) => { - // It's OK to do param-reassignment in _.reduce() because we absolutely know the starting state of finalPersonalDetails - // eslint-disable-next-line no-param-reassign - finalPersonalDetails[accountID] = { - accountID: Number(accountID), - login: personalData.login, - displayName: personalData.displayName, - firstName: personalData.firstName, - status: personalData.status, - avatar: UserUtils.getAvatar(personalData.avatar, personalData.accountID), - fallbackIcon: personalData.fallbackIcon, - }; - return finalPersonalDetails; - }, - {}, - ); - /** * This component is rendered in a list. * On scroll we want to avoid that a item re-renders @@ -174,10 +143,6 @@ export default React.memo( key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, canEvict: false, }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - selector: personalDetailsSelector, - }, preferredLocale: { key: ONYXKEYS.NVP_PREFERRED_LOCALE, }, diff --git a/src/components/MoneyReportHeader.js b/src/components/MoneyReportHeader.js index ab0b77c21653..211acaf033ce 100644 --- a/src/components/MoneyReportHeader.js +++ b/src/components/MoneyReportHeader.js @@ -46,9 +46,6 @@ const propTypes = { /** The next step for the report */ nextStep: nextStepPropTypes, - /** Personal details so we can get the ones for the report participants */ - personalDetails: PropTypes.objectOf(participantPropTypes).isRequired, - /** Session info for the currently logged in user. */ session: PropTypes.shape({ /** Currently logged in user email */ @@ -67,7 +64,7 @@ const defaultProps = { policy: {}, }; -function MoneyReportHeader({session, personalDetails, policy, chatReport, nextStep, report: moneyRequestReport, isSmallScreenWidth}) { +function MoneyReportHeader({session, policy, chatReport, nextStep, report: moneyRequestReport, isSmallScreenWidth}) { const {translate} = useLocalize(); const reimbursableTotal = ReportUtils.getMoneyRequestReimbursableTotal(moneyRequestReport); const isApproved = ReportUtils.isReportApproved(moneyRequestReport); @@ -102,7 +99,6 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt shouldShowPinButton={false} report={moneyRequestReport} policy={policy} - personalDetails={personalDetails} shouldShowBackButton={isSmallScreenWidth} onBackButtonPress={() => Navigation.goBack(ROUTES.HOME, false, true)} // Shows border if no buttons or next steps are showing below the header diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 086e1429baef..d9be47ad312f 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -35,9 +35,6 @@ const propTypes = { name: PropTypes.string, }), - /** Personal details so we can get the ones for the report participants */ - personalDetails: PropTypes.objectOf(participantPropTypes).isRequired, - /* Onyx Props */ /** Session info for the currently logged in user. */ session: PropTypes.shape({ @@ -65,7 +62,7 @@ const defaultProps = { policy: {}, }; -function MoneyRequestHeader({session, parentReport, report, parentReportAction, transaction, policy, personalDetails}) { +function MoneyRequestHeader({session, parentReport, report, parentReportAction, transaction, policy}) { const {translate} = useLocalize(); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const moneyRequestReport = parentReport; @@ -125,7 +122,6 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, ownerEmail: lodashGet(parentReport, 'ownerEmail', null), }} policy={policy} - personalDetails={personalDetails} shouldShowBackButton={isSmallScreenWidth} onBackButtonPress={() => Navigation.goBack(ROUTES.HOME, false, true)} /> diff --git a/src/components/withCurrentUserPersonalDetails.tsx b/src/components/withCurrentUserPersonalDetails.tsx index e1472f280f17..fa81c658bc78 100644 --- a/src/components/withCurrentUserPersonalDetails.tsx +++ b/src/components/withCurrentUserPersonalDetails.tsx @@ -4,13 +4,11 @@ import getComponentDisplayName from '../libs/getComponentDisplayName'; import ONYXKEYS from '../ONYXKEYS'; import personalDetailsPropType from '../pages/personalDetailsPropType'; import type {PersonalDetails, Session} from '../types/onyx'; +import { getPersonalDetailsByAccountID } from '../libs/PersonalDetailsUtils'; type CurrentUserPersonalDetails = PersonalDetails | Record; type OnyxProps = { - /** Personal details of all the users, including current user */ - personalDetails: OnyxEntry>; - /** Session of the current user */ session: OnyxEntry; }; @@ -35,7 +33,7 @@ export default function ( ): ComponentType & RefAttributes, keyof OnyxProps>> { function WithCurrentUserPersonalDetails(props: Omit, ref: ForwardedRef) { const accountID = props.session?.accountID ?? 0; - const accountPersonalDetails = props.personalDetails?.[accountID]; + const accountPersonalDetails: PersonalDetails = getPersonalDetailsByAccountID(accountID); const currentUserPersonalDetails: CurrentUserPersonalDetails = useMemo( () => (accountPersonalDetails ? {...accountPersonalDetails, accountID} : {}), [accountPersonalDetails, accountID], @@ -55,9 +53,6 @@ export default function ( const withCurrentUserPersonalDetails = React.forwardRef(WithCurrentUserPersonalDetails); return withOnyx & RefAttributes, OnyxProps>({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, session: { key: ONYXKEYS.SESSION, }, diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 79c480711c4d..4948c4e8ef90 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -152,11 +152,12 @@ function getAvatarsForAccountIDs(accountIDs, personalDetails, defaultValues = {} * Returns the personal details for an array of accountIDs * * @param {Array} accountIDs - * @param {Object} personalDetails + * @param {Object} passedPersonalDetails * @returns {Object} – keys of the object are emails, values are PersonalDetails objects. */ -function getPersonalDetailsForAccountIDs(accountIDs, personalDetails) { +function getPersonalDetailsForAccountIDs(accountIDs, passedPersonalDetails) { const personalDetailsForAccountIDs = {}; + const personalDetails = passedPersonalDetails || allPersonalDetails; if (!personalDetails) { return personalDetailsForAccountIDs; } diff --git a/src/libs/PersonalDetailsUtils.js b/src/libs/PersonalDetailsUtils.js index 29c49427bc81..78066c6b1d9d 100644 --- a/src/libs/PersonalDetailsUtils.js +++ b/src/libs/PersonalDetailsUtils.js @@ -23,8 +23,7 @@ Onyx.connect({ * @returns {String} */ function getDisplayNameOrDefault(passedPersonalDetails, pathToDisplayName, defaultValue) { - const displayName = lodashGet(passedPersonalDetails, pathToDisplayName); - + const displayName = lodashGet(passedPersonalDetails || allPersonalDetails, pathToDisplayName); return displayName || defaultValue || Localize.translateLocal('common.hidden'); } @@ -177,4 +176,26 @@ function getFormattedAddress(privatePersonalDetails) { return formattedAddress.trim().replace(/,$/, ''); } -export {getDisplayNameOrDefault, getPersonalDetailsByIDs, getAccountIDsByLogins, getLoginsByAccountIDs, getNewPersonalDetailsOnyxData, getFormattedAddress}; +function getPersonalDetailsByAccountID(accountID) { + return allPersonalDetails[accountID]; +} + +function getWhisperedToPersonalDetails(whisperedToAccountIDs) { + return _.filter(allPersonalDetails, (details) => _.includes(whisperedToAccountIDs, details.accountID)); +} + +function isPersonalDetailsEmpty() { + return !personalDetails.length; +} + +export { + getDisplayNameOrDefault, + getPersonalDetailsByIDs, + getAccountIDsByLogins, + getLoginsByAccountIDs, + getNewPersonalDetailsOnyxData, + getFormattedAddress, + getPersonalDetailsByAccountID, + getWhisperedToPersonalDetails, + isPersonalDetailsEmpty, +}; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 79c1c500b837..74d744efb837 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -913,15 +913,13 @@ function getReportRecipientAccountIDs(report, currentLoginAccountID) { /** * Whether the time row should be shown for a report. - * @param {Array} personalDetails * @param {Object} report - * @param {Number} accountID * @return {Boolean} */ -function canShowReportRecipientLocalTime(personalDetails, report, accountID) { - const reportRecipientAccountIDs = getReportRecipientAccountIDs(report, accountID); +function canShowReportRecipientLocalTime(report) { + const reportRecipientAccountIDs = getReportRecipientAccountIDs(report, currentUserAccountID); const hasMultipleParticipants = reportRecipientAccountIDs.length > 1; - const reportRecipient = personalDetails[reportRecipientAccountIDs[0]]; + const reportRecipient = allPersonalDetails[reportRecipientAccountIDs[0]]; const reportRecipientTimezone = lodashGet(reportRecipient, 'timezone', CONST.DEFAULT_TIME_ZONE); const isReportParticipantValidated = lodashGet(reportRecipient, 'validated', false); return Boolean( @@ -977,13 +975,13 @@ function getWorkspaceAvatar(report) { * The Avatar sources can be URLs or Icon components according to the chat type. * * @param {Array} participants - * @param {Object} personalDetails + * @param {Object} passedPersonalDetails * @returns {Array<*>} */ -function getIconsForParticipants(participants, personalDetails) { +function getIconsForParticipants(participants, passedPersonalDetails) { const participantDetails = []; const participantsList = participants || []; - + const personalDetails = passedPersonalDetails || allPersonalDetails; for (let i = 0; i < participantsList.length; i++) { const accountID = participantsList[i]; const avatarSource = UserUtils.getAvatar(lodashGet(personalDetails, [accountID, 'avatar'], ''), accountID); @@ -1046,14 +1044,15 @@ function getWorkspaceIcon(report, policy = undefined) { * The Avatar sources can be URLs or Icon components according to the chat type. * * @param {Object} report - * @param {Object} personalDetails + * @param {Object} passedPersonalDetails * @param {*} [defaultIcon] * @param {String} [defaultName] * @param {Number} [defaultAccountID] * @param {Object} [policy] * @returns {Array<*>} */ -function getIcons(report, personalDetails, defaultIcon = null, defaultName = '', defaultAccountID = -1, policy = undefined) { +function getIcons(report, passedPersonalDetails, defaultIcon = null, defaultName = '', defaultAccountID = -1, policy = undefined) { + const personalDetails = passedPersonalDetails || allPersonalDetails; if (_.isEmpty(report)) { const fallbackIcon = { source: defaultIcon || Expensicons.FallbackAvatar, diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js index 6b9e6d10fd16..5d7dd5bc1ed7 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.js @@ -55,6 +55,12 @@ Onyx.connect({ }, }); +let allPersonalDetails; +Onyx.connect({ + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + callback: (val) => (allPersonalDetails = val), +}); + let resolveSidebarIsReadyPromise; let sidebarIsReadyPromise = new Promise((resolve) => { @@ -218,16 +224,16 @@ function getOrderedReportIDs(currentReportId, allReportsDict, betas, policies, p * * @param {Object} report * @param {Object} reportActions - * @param {Object} personalDetails * @param {String} preferredLocale * @param {Object} [policy] * @param {Object} parentReportAction * @returns {Object} */ -function getOptionData(report, reportActions, personalDetails, preferredLocale, policy, parentReportAction) { +function getOptionData(report, reportActions, preferredLocale, policy, parentReportAction) { // When a user signs out, Onyx is cleared. Due to the lazy rendering with a virtual list, it's possible for // this method to be called after the Onyx data has been cleared out. In that case, it's fine to do // a null check here and return early. + const personalDetails = allPersonalDetails; if (!report || !personalDetails) { return; } diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index e88f6cd0b756..d36937b0f420 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -43,9 +43,6 @@ const propTypes = { /** The report currently being looked at */ report: reportPropTypes, - /** Personal details of all the users */ - personalDetails: PropTypes.objectOf(participantPropTypes), - /** Onyx Props */ parentReport: reportPropTypes, @@ -62,7 +59,6 @@ const propTypes = { }; const defaultProps = { - personalDetails: {}, report: null, guideCalendarLink: null, parentReport: {}, @@ -73,7 +69,7 @@ const defaultProps = { function HeaderView(props) { const participants = lodashGet(props.report, 'participantAccountIDs', []); - const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, props.personalDetails); + const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants); const isMultipleParticipant = participants.length > 1; const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(participantPersonalDetails, isMultipleParticipant); const isChatThread = ReportUtils.isChatThread(props.report); @@ -167,7 +163,7 @@ function HeaderView(props) { const shouldShowSubscript = ReportUtils.shouldReportShowSubscript(props.report); const defaultSubscriptSize = ReportUtils.isExpenseRequest(props.report) ? CONST.AVATAR_SIZE.SMALL_NORMAL : CONST.AVATAR_SIZE.DEFAULT; - const icons = ReportUtils.getIcons(reportHeaderData, props.personalDetails); + const icons = ReportUtils.getIcons(reportHeaderData); const brickRoadIndicator = ReportUtils.hasReportNameError(props.report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; const shouldShowBorderBottom = !isTaskReport || !props.isSmallScreenWidth; const shouldDisableDetailPage = ReportUtils.shouldDisableDetailPage(props.report); diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 64641ab473fb..d6caff573d25 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -40,6 +40,7 @@ import usePrevious from '../../hooks/usePrevious'; import CONST from '../../CONST'; import withCurrentReportID, {withCurrentReportIDPropTypes, withCurrentReportIDDefaultProps} from '../../components/withCurrentReportID'; import reportWithoutHasDraftSelector from '../../libs/OnyxSelectors/reportWithoutHasDraftSelector'; +import { isPersonalDetailsEmpty } from '../../libs/PersonalDetailsUtils'; const propTypes = { /** Navigation route context info provided by react navigation */ @@ -86,9 +87,6 @@ const propTypes = { /** The account manager report ID */ accountManagerReportID: PropTypes.string, - /** All of the personal details for everyone */ - personalDetails: PropTypes.objectOf(personalDetailsPropType), - /** Onyx function that marks the component ready for hydration */ markReadyForHydration: PropTypes.func, @@ -115,7 +113,6 @@ const defaultProps = { policies: {}, accountManagerReportID: null, userLeavingStatus: false, - personalDetails: {}, markReadyForHydration: null, ...withCurrentReportIDDefaultProps, }; @@ -140,7 +137,6 @@ function ReportScreen({ reportMetadata, reportActions, accountManagerReportID, - personalDetails, markReadyForHydration, policies, isSidebarLoaded, @@ -173,7 +169,7 @@ function ReportScreen({ const shouldHideReport = !ReportUtils.canAccessReport(report, policies, betas); - const isLoading = !reportID || !isSidebarLoaded || _.isEmpty(personalDetails); + const isLoading = !reportID || !isSidebarLoaded || isPersonalDetailsEmpty(); const parentReportAction = ReportActionsUtils.getParentReportAction(report); const lastReportAction = useMemo( @@ -191,7 +187,6 @@ function ReportScreen({ Navigation.goBack(ROUTES.HOME, false, true)} - personalDetails={personalDetails} report={report} /> ); @@ -201,7 +196,6 @@ function ReportScreen({ @@ -213,7 +207,6 @@ function ReportScreen({ @@ -445,7 +438,6 @@ function ReportScreen({ isComposerFullSize={isComposerFullSize} policies={policies} listHeight={listHeight} - personalDetails={personalDetails} isEmptyChat={isEmptyChat} lastReportAction={lastReportAction} /> @@ -506,9 +498,6 @@ export default compose( key: ONYXKEYS.ACCOUNT_MANAGER_REPORT_ID, initialValue: null, }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, userLeavingStatus: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${getReportID(route)}`, initialValue: false, diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js index 2721329919c7..dc515c2495ff 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js @@ -16,7 +16,6 @@ import willBlurTextInputOnTapOutsideFunc from '../../../../libs/willBlurTextInpu import canFocusInputOnScreenFocus from '../../../../libs/canFocusInputOnScreenFocus'; import CONST from '../../../../CONST'; import * as ReportUtils from '../../../../libs/ReportUtils'; -import participantPropTypes from '../../../../components/participantPropTypes'; import ParticipantLocalTime from '../ParticipantLocalTime'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes, withCurrentUserPersonalDetailsDefaultProps} from '../../../../components/withCurrentUserPersonalDetails'; import {withNetwork} from '../../../../components/OnyxProvider'; @@ -33,6 +32,7 @@ import getModalState from '../../../../libs/getModalState'; import useWindowDimensions from '../../../../hooks/useWindowDimensions'; import * as EmojiPickerActions from '../../../../libs/actions/EmojiPickerAction'; import * as ReportActionsUtils from '../../../../libs/ReportActionsUtils'; +import {getPersonalDetailsByAccountID} from '../../../../libs/PersonalDetailsUtils'; const propTypes = { /** A method to call when the form is submitted */ @@ -41,9 +41,6 @@ const propTypes = { /** The ID of the report actions will be created for */ reportID: PropTypes.string.isRequired, - /** Personal details of all the users */ - personalDetails: PropTypes.objectOf(participantPropTypes), - /** The report currently being looked at */ report: reportPropTypes, @@ -76,7 +73,6 @@ const propTypes = { const defaultProps = { report: {}, blockedFromConcierge: {}, - personalDetails: {}, preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE, isComposerFullSize: false, pendingAction: null, @@ -100,7 +96,6 @@ function ReportActionCompose({ network, onSubmit, pendingAction, - personalDetails, report, reportID, isEmptyChat, @@ -147,10 +142,7 @@ function ReportActionCompose({ [currentUserPersonalDetails.accountID, report], ); - const shouldShowReportRecipientLocalTime = useMemo( - () => ReportUtils.canShowReportRecipientLocalTime(personalDetails, report, currentUserPersonalDetails.accountID) && !isComposerFullSize, - [personalDetails, report, currentUserPersonalDetails.accountID, isComposerFullSize], - ); + const shouldShowReportRecipientLocalTime = useMemo(() => ReportUtils.canShowReportRecipientLocalTime(report) && !isComposerFullSize, [report, isComposerFullSize]); const includesConcierge = useMemo(() => ReportUtils.chatIncludesConcierge({participantAccountIDs: report.participantAccountIDs}), [report.participantAccountIDs]); const userBlockedFromConcierge = useMemo(() => User.isBlockedFromConcierge(blockedFromConcierge), [blockedFromConcierge]); @@ -307,7 +299,7 @@ function ReportActionCompose({ ); const reportRecipientAcountIDs = ReportUtils.getReportRecipientAccountIDs(report, currentUserPersonalDetails.accountID); - const reportRecipient = personalDetails[reportRecipientAcountIDs[0]]; + const reportRecipient = getPersonalDetailsByAccountID(reportRecipientAcountIDs[0]); const shouldUseFocusedColor = !isBlockedFromConcierge && !disabled && isFocused; const hasReportRecipient = _.isObject(reportRecipient) && !_.isEmpty(reportRecipient); @@ -443,9 +435,6 @@ export default compose( blockedFromConcierge: { key: ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE, }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, shouldShowComposeInput: { key: ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT, }, diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 3afdb437a49a..2697d4b5551d 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -133,7 +133,6 @@ const defaultProps = { }; function ReportActionItem(props) { - const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(props.action.reportActionID)); const [isHidden, setIsHidden] = useState(false); const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED); @@ -303,6 +302,7 @@ function ReportActionItem(props) { */ const renderItemContent = (hovered = false, isWhisper = false, hasErrors = false) => { let children; + const _submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(null, [props.report.ownerAccountID, 'displayName'], props.report.ownerEmail); // Show the MoneyRequestPreview for when request was created, bill was split or money was sent if ( @@ -360,7 +360,7 @@ function ReportActionItem(props) { /> ); } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTQUEUED) { - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, [props.report.ownerAccountID, 'displayName'], props.report.ownerEmail); + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(null, [props.report.ownerAccountID, 'displayName'], props.report.ownerEmail); const paymentType = lodashGet(props.action, 'originalMessage.paymentType', ''); const isSubmitterOfUnsettledReport = ReportUtils.isCurrentUserSubmitter(props.report.reportID) && !ReportUtils.isSettled(props.report.reportID); @@ -506,7 +506,7 @@ function ReportActionItem(props) { numberOfReplies={numberOfThreadReplies} mostRecentReply={`${props.action.childLastVisibleActionCreated}`} isHovered={hovered} - icons={ReportUtils.getIconsForParticipants(oldestFourAccountIDs, personalDetails)} + icons={ReportUtils.getIconsForParticipants(oldestFourAccountIDs)} onSecondaryInteraction={showPopover} /> @@ -638,7 +638,7 @@ function ReportActionItem(props) { const isWhisper = whisperedToAccountIDs.length > 0; const isMultipleParticipant = whisperedToAccountIDs.length > 1; const isWhisperOnlyVisibleByUser = isWhisper && ReportUtils.isCurrentUserTheOnlyParticipant(whisperedToAccountIDs); - const whisperedToPersonalDetails = isWhisper ? _.filter(personalDetails, (details) => _.includes(whisperedToAccountIDs, details.accountID)) : []; + const whisperedToPersonalDetails = isWhisper ? PersonalDetailsUtils.getWhisperedToPersonalDetails(whisperedToAccountIDs) : []; const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; return ( `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, selector: reportWithoutHasDraftSelector, }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, }, diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index fc189a3aef36..faa9b4c6fbbc 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -31,6 +31,7 @@ import ONYXKEYS from '../../../ONYXKEYS'; import Text from '../../../components/Text'; import Tooltip from '../../../components/Tooltip'; import DateUtils from '../../../libs/DateUtils'; +import { getPersonalDetailsByAccountID } from '../../../libs/PersonalDetailsUtils'; const propTypes = { /** All the data of the action */ @@ -83,8 +84,8 @@ const showWorkspaceDetails = (reportID) => { }; function ReportActionItemSingle(props) { - const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const actorAccountID = props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && props.iouReport ? props.iouReport.managerID : props.action.actorAccountID; + const personalDetails = getPersonalDetailsByAccountID(actorAccountID); let {displayName} = personalDetails[actorAccountID] || {}; const {avatar, login, pendingFields, status, fallbackIcon} = personalDetails[actorAccountID] || {}; let actorHint = (login || displayName || '').replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, ''); @@ -122,7 +123,7 @@ function ReportActionItemSingle(props) { id: secondaryAccountId, }; } else if (!isWorkspaceActor) { - secondaryAvatar = ReportUtils.getIcons(props.report, {})[props.report.isOwnPolicyExpenseChat ? 0 : 1]; + secondaryAvatar = ReportUtils.getIcons(props.report)[props.report.isOwnPolicyExpenseChat ? 0 : 1]; } const icon = {source: avatarSource, type: isWorkspaceActor ? CONST.ICON_TYPE_WORKSPACE : CONST.ICON_TYPE_AVATAR, name: primaryDisplayName, id: isWorkspaceActor ? '' : actorAccountID}; diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index 531a11ce7252..6515dc652bdc 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -6,8 +6,6 @@ import {useRoute} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import CONST from '../../../CONST'; import InvertedFlatList from '../../../components/InvertedFlatList'; -import {withPersonalDetails} from '../../../components/OnyxProvider'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '../../../components/withCurrentUserPersonalDetails'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; import useNetwork from '../../../hooks/useNetwork'; import useLocalize from '../../../hooks/useLocalize'; @@ -66,17 +64,14 @@ const propTypes = { }), ...windowDimensionsPropTypes, - ...withCurrentUserPersonalDetailsPropTypes, }; const defaultProps = { - personalDetails: {}, onScroll: () => {}, mostRecentIOUReportActionID: '', isLoadingInitialReportActions: false, isLoadingOlderReportActions: false, isLoadingNewerReportActions: false, - ...withCurrentUserPersonalDetailsDefaultProps, }; const VERTICAL_OFFSET_THRESHOLD = 200; @@ -122,8 +117,6 @@ function ReportActionsList({ onScroll, mostRecentIOUReportActionID, isSmallScreenWidth, - personalDetailsList, - currentUserPersonalDetails, hasOutstandingIOU, loadNewerChats, loadOlderChats, @@ -350,7 +343,7 @@ function ReportActionsList({ // To notify there something changes we can use extraData prop to flatlist const extraData = [isSmallScreenWidth ? currentUnreadMarker : undefined, ReportUtils.isArchivedRoom(report)]; const hideComposer = ReportUtils.shouldDisableWriteActions(report); - const shouldShowReportRecipientLocalTime = ReportUtils.canShowReportRecipientLocalTime(personalDetailsList, report, currentUserPersonalDetails.accountID) && !isComposerFullSize; + const shouldShowReportRecipientLocalTime = ReportUtils.canShowReportRecipientLocalTime(report) && !isComposerFullSize; const contentContainerStyle = useMemo( () => [styles.chatContentScrollView, isLoadingNewerReportActions ? styles.chatContentScrollViewWithHeaderLoader : {}], @@ -435,4 +428,4 @@ ReportActionsList.propTypes = propTypes; ReportActionsList.defaultProps = defaultProps; ReportActionsList.displayName = 'ReportActionsList'; -export default compose(withWindowDimensions, withPersonalDetails(), withCurrentUserPersonalDetails)(ReportActionsList); +export default compose(withWindowDimensions)(ReportActionsList); diff --git a/src/pages/home/report/ReportFooter.js b/src/pages/home/report/ReportFooter.js index 5af46b14142d..710eed2b86df 100644 --- a/src/pages/home/report/ReportFooter.js +++ b/src/pages/home/report/ReportFooter.js @@ -33,9 +33,6 @@ const propTypes = { /** The pending action when we are adding a chat */ pendingAction: PropTypes.string, - /** Personal details of all the users */ - personalDetails: PropTypes.objectOf(participantPropTypes), - /** Whether user interactions should be disabled */ shouldDisableCompose: PropTypes.bool, @@ -51,7 +48,6 @@ const propTypes = { const defaultProps = { report: {reportID: '0'}, pendingAction: null, - personalDetails: {}, shouldDisableCompose: false, listHeight: 0, isReportReadyForDisplay: true, @@ -111,7 +107,6 @@ function ReportFooter(props) { )} {isArchivedRoom && } @@ -151,7 +146,6 @@ export default withWindowDimensions( (prevProps, nextProps) => isEqual(prevProps.report, nextProps.report) && isEqual(prevProps.reportActions, nextProps.reportActions) && - isEqual(prevProps.personalDetails, nextProps.personalDetails) && prevProps.pendingAction === nextProps.pendingAction && prevProps.shouldDisableCompose === nextProps.shouldDisableCompose && prevProps.listHeight === nextProps.listHeight && From 47c4717c3ee8553a96b1bb8540df9d5779097f5f Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 26 Oct 2023 16:54:08 +0500 Subject: [PATCH 009/142] fix: linting --- src/components/AnonymousReportFooter.js | 1 - src/components/ArchivedReportFooter.js | 1 - src/components/AvatarWithDisplayName.js | 1 - src/components/MoneyReportHeader.js | 1 - src/components/MoneyRequestHeader.js | 1 - src/pages/home/HeaderView.js | 1 - src/pages/home/ReportScreen.js | 5 ++--- .../ReportActionCompose/ReportActionCompose.js | 4 ++-- .../ReportActionCompose/SuggestionMention.js | 2 +- src/pages/home/report/ReportActionItem.js | 3 +-- src/pages/home/report/ReportActionItemCreated.js | 1 - src/pages/home/report/ReportActionItemSingle.js | 15 +++++++-------- src/pages/home/report/ReportActionsList.js | 4 ++-- src/pages/home/report/ReportFooter.js | 1 - 14 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/components/AnonymousReportFooter.js b/src/components/AnonymousReportFooter.js index 902a31f12ee3..51b9752294e9 100644 --- a/src/components/AnonymousReportFooter.js +++ b/src/components/AnonymousReportFooter.js @@ -8,7 +8,6 @@ import withLocalize, {withLocalizePropTypes} from './withLocalize'; import reportPropTypes from '../pages/reportPropTypes'; import styles from '../styles/styles'; import * as Session from '../libs/actions/Session'; -import participantPropTypes from './participantPropTypes'; const propTypes = { /** The report currently being looked at */ diff --git a/src/components/ArchivedReportFooter.js b/src/components/ArchivedReportFooter.js index bca5f0b391c8..41cf0f65487e 100644 --- a/src/components/ArchivedReportFooter.js +++ b/src/components/ArchivedReportFooter.js @@ -7,7 +7,6 @@ import CONST from '../CONST'; import Banner from './Banner'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import compose from '../libs/compose'; -import personalDetailsPropType from '../pages/personalDetailsPropType'; import ONYXKEYS from '../ONYXKEYS'; import * as ReportUtils from '../libs/ReportUtils'; import reportPropTypes from '../pages/reportPropTypes'; diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index 3a4ebee0aa42..8623f6bba4bc 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -5,7 +5,6 @@ import PropTypes from 'prop-types'; import lodashGet from 'lodash/get'; import CONST from '../CONST'; import reportPropTypes from '../pages/reportPropTypes'; -import participantPropTypes from './participantPropTypes'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import styles from '../styles/styles'; diff --git a/src/components/MoneyReportHeader.js b/src/components/MoneyReportHeader.js index 211acaf033ce..55a31c0a9bdc 100644 --- a/src/components/MoneyReportHeader.js +++ b/src/components/MoneyReportHeader.js @@ -8,7 +8,6 @@ import useLocalize from '../hooks/useLocalize'; import HeaderWithBackButton from './HeaderWithBackButton'; import iouReportPropTypes from '../pages/iouReportPropTypes'; import * as ReportUtils from '../libs/ReportUtils'; -import participantPropTypes from './participantPropTypes'; import styles from '../styles/styles'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import compose from '../libs/compose'; diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index d9be47ad312f..c106e1aa9bfd 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -8,7 +8,6 @@ import iouReportPropTypes from '../pages/iouReportPropTypes'; import * as ReportUtils from '../libs/ReportUtils'; import compose from '../libs/compose'; import * as Expensicons from './Icon/Expensicons'; -import participantPropTypes from './participantPropTypes'; import styles from '../styles/styles'; import Navigation from '../libs/Navigation/Navigation'; import ROUTES from '../ROUTES'; diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index d36937b0f420..3f9776ca4946 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -19,7 +19,6 @@ import TaskHeaderActionButton from '../../components/TaskHeaderActionButton'; import Text from '../../components/Text'; import ThreeDotsMenu from '../../components/ThreeDotsMenu'; import Tooltip from '../../components/Tooltip'; -import participantPropTypes from '../../components/participantPropTypes'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions'; import * as OptionsListUtils from '../../libs/OptionsListUtils'; diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index d6caff573d25..3340c4ca5ee3 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -28,7 +28,6 @@ import reportMetadataPropTypes from '../reportMetadataPropTypes'; import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoundView'; import withViewportOffsetTop from '../../components/withViewportOffsetTop'; import * as ReportActionsUtils from '../../libs/ReportActionsUtils'; -import personalDetailsPropType from '../personalDetailsPropType'; import getIsReportFullyVisible from '../../libs/getIsReportFullyVisible'; import MoneyRequestHeader from '../../components/MoneyRequestHeader'; import MoneyReportHeader from '../../components/MoneyReportHeader'; @@ -40,7 +39,7 @@ import usePrevious from '../../hooks/usePrevious'; import CONST from '../../CONST'; import withCurrentReportID, {withCurrentReportIDPropTypes, withCurrentReportIDDefaultProps} from '../../components/withCurrentReportID'; import reportWithoutHasDraftSelector from '../../libs/OnyxSelectors/reportWithoutHasDraftSelector'; -import { isPersonalDetailsEmpty } from '../../libs/PersonalDetailsUtils'; +import * as PersonalDetailsUtils from '../../libs/PersonalDetailsUtils'; const propTypes = { /** Navigation route context info provided by react navigation */ @@ -169,7 +168,7 @@ function ReportScreen({ const shouldHideReport = !ReportUtils.canAccessReport(report, policies, betas); - const isLoading = !reportID || !isSidebarLoaded || isPersonalDetailsEmpty(); + const isLoading = !reportID || !isSidebarLoaded || PersonalDetailsUtils.isPersonalDetailsEmpty(); const parentReportAction = ReportActionsUtils.getParentReportAction(report); const lastReportAction = useMemo( diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js index dc515c2495ff..fa7dfc8418fa 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js @@ -32,7 +32,7 @@ import getModalState from '../../../../libs/getModalState'; import useWindowDimensions from '../../../../hooks/useWindowDimensions'; import * as EmojiPickerActions from '../../../../libs/actions/EmojiPickerAction'; import * as ReportActionsUtils from '../../../../libs/ReportActionsUtils'; -import {getPersonalDetailsByAccountID} from '../../../../libs/PersonalDetailsUtils'; +import * as PersonalDetailsUtils from '../../../../libs/PersonalDetailsUtils'; const propTypes = { /** A method to call when the form is submitted */ @@ -299,7 +299,7 @@ function ReportActionCompose({ ); const reportRecipientAcountIDs = ReportUtils.getReportRecipientAccountIDs(report, currentUserPersonalDetails.accountID); - const reportRecipient = getPersonalDetailsByAccountID(reportRecipientAcountIDs[0]); + const reportRecipient = PersonalDetailsUtils.getPersonalDetailsByAccountID(reportRecipientAcountIDs[0]); const shouldUseFocusedColor = !isBlockedFromConcierge && !disabled && isFocused; const hasReportRecipient = _.isObject(reportRecipient) && !_.isEmpty(reportRecipient); diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.js b/src/pages/home/report/ReportActionCompose/SuggestionMention.js index 237b1e956966..0172bfe20038 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.js +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.js @@ -11,7 +11,6 @@ import * as SuggestionsUtils from '../../../../libs/SuggestionUtils'; import useLocalize from '../../../../hooks/useLocalize'; import usePrevious from '../../../../hooks/usePrevious'; import ONYXKEYS from '../../../../ONYXKEYS'; -import personalDetailsPropType from '../../../personalDetailsPropType'; import * as SuggestionProps from './suggestionProps'; /** @@ -45,6 +44,7 @@ const defaultProps = { * hence we don't have to use it with `withOnyx`. */ let allPersonalDetails = {}; +// eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs Onyx.connect({ key: ONYXKEYS.PERSONAL_DETAILS_LIST, callback: (val) => { diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 2697d4b5551d..7c955b0a88e3 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -28,7 +28,7 @@ import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMe import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; import * as ContextMenuActions from './ContextMenu/ContextMenuActions'; import * as EmojiPickerAction from '../../../libs/actions/EmojiPickerAction'; -import {usePersonalDetails, withBlockedFromConcierge, withNetwork, withReportActionsDrafts} from '../../../components/OnyxProvider'; +import {withBlockedFromConcierge, withNetwork, withReportActionsDrafts} from '../../../components/OnyxProvider'; import RenameAction from '../../../components/ReportActionItem/RenameAction'; import InlineSystemMessage from '../../../components/InlineSystemMessage'; import styles from '../../../styles/styles'; @@ -302,7 +302,6 @@ function ReportActionItem(props) { */ const renderItemContent = (hovered = false, isWhisper = false, hasErrors = false) => { let children; - const _submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(null, [props.report.ownerAccountID, 'displayName'], props.report.ownerEmail); // Show the MoneyRequestPreview for when request was created, bill was split or money was sent if ( diff --git a/src/pages/home/report/ReportActionItemCreated.js b/src/pages/home/report/ReportActionItemCreated.js index 9b428bf90ea6..2e7bce92f053 100644 --- a/src/pages/home/report/ReportActionItemCreated.js +++ b/src/pages/home/report/ReportActionItemCreated.js @@ -5,7 +5,6 @@ import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; import ONYXKEYS from '../../../ONYXKEYS'; import ReportWelcomeText from '../../../components/ReportWelcomeText'; -import participantPropTypes from '../../../components/participantPropTypes'; import * as ReportUtils from '../../../libs/ReportUtils'; import styles from '../../../styles/styles'; import OfflineWithFeedback from '../../../components/OfflineWithFeedback'; diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index faa9b4c6fbbc..5450b457e53f 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -13,7 +13,6 @@ import compose from '../../../libs/compose'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import Navigation from '../../../libs/Navigation/Navigation'; import ROUTES from '../../../ROUTES'; -import {usePersonalDetails} from '../../../components/OnyxProvider'; import ControlSelection from '../../../libs/ControlSelection'; import * as ReportUtils from '../../../libs/ReportUtils'; import OfflineWithFeedback from '../../../components/OfflineWithFeedback'; @@ -31,7 +30,7 @@ import ONYXKEYS from '../../../ONYXKEYS'; import Text from '../../../components/Text'; import Tooltip from '../../../components/Tooltip'; import DateUtils from '../../../libs/DateUtils'; -import { getPersonalDetailsByAccountID } from '../../../libs/PersonalDetailsUtils'; +import * as PersonalDetailsUtils from '../../../libs/PersonalDetailsUtils'; const propTypes = { /** All the data of the action */ @@ -85,22 +84,22 @@ const showWorkspaceDetails = (reportID) => { function ReportActionItemSingle(props) { const actorAccountID = props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && props.iouReport ? props.iouReport.managerID : props.action.actorAccountID; - const personalDetails = getPersonalDetailsByAccountID(actorAccountID); - let {displayName} = personalDetails[actorAccountID] || {}; - const {avatar, login, pendingFields, status, fallbackIcon} = personalDetails[actorAccountID] || {}; + const personalDetails = PersonalDetailsUtils.getPersonalDetailsByAccountID(actorAccountID); + let {displayName} = personalDetails || {}; + const {avatar, login, pendingFields, status, fallbackIcon} = personalDetails || {}; let actorHint = (login || displayName || '').replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, ''); const displayAllActors = useMemo(() => props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && props.iouReport, [props.action.actionName, props.iouReport]); const isWorkspaceActor = ReportUtils.isPolicyExpenseChat(props.report) && (!actorAccountID || displayAllActors); let avatarSource = UserUtils.getAvatar(avatar, actorAccountID); + const delegateDetails = PersonalDetailsUtils.getPersonalDetailsByAccountID(props.action.delegateAccountID); if (isWorkspaceActor) { displayName = ReportUtils.getPolicyName(props.report); actorHint = displayName; avatarSource = ReportUtils.getWorkspaceAvatar(props.report); - } else if (props.action.delegateAccountID && personalDetails[props.action.delegateAccountID]) { + } else if (props.action.delegateAccountID && delegateDetails) { // We replace the actor's email, name, and avatar with the Copilot manually for now. And only if we have their // details. This will be improved upon when the Copilot feature is implemented. - const delegateDetails = personalDetails[props.action.delegateAccountID]; const delegateDisplayName = delegateDetails.displayName; actorHint = `${delegateDisplayName} (${props.translate('reportAction.asCopilot')} ${displayName})`; displayName = actorHint; @@ -113,7 +112,7 @@ function ReportActionItemSingle(props) { if (displayAllActors) { // The ownerAccountID and actorAccountID can be the same if the a user requests money back from the IOU's original creator, in that case we need to use managerID to avoid displaying the same user twice const secondaryAccountId = props.iouReport.ownerAccountID === actorAccountID ? props.iouReport.managerID : props.iouReport.ownerAccountID; - const secondaryUserDetails = personalDetails[secondaryAccountId] || {}; + const secondaryUserDetails = PersonalDetailsUtils.getPersonalDetailsByAccountID(secondaryAccountId); const secondaryDisplayName = lodashGet(secondaryUserDetails, 'displayName', ''); displayName = `${primaryDisplayName} & ${secondaryDisplayName}`; secondaryAvatar = { diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index 6515dc652bdc..8127e40a3aa2 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -13,7 +13,6 @@ import useReportScrollManager from '../../../hooks/useReportScrollManager'; import DateUtils from '../../../libs/DateUtils'; import * as ReportUtils from '../../../libs/ReportUtils'; import * as Report from '../../../libs/actions/Report'; -import compose from '../../../libs/compose'; import styles from '../../../styles/styles'; import variables from '../../../styles/variables'; import reportPropTypes from '../../reportPropTypes'; @@ -72,6 +71,7 @@ const defaultProps = { isLoadingInitialReportActions: false, isLoadingOlderReportActions: false, isLoadingNewerReportActions: false, + policy: {} }; const VERTICAL_OFFSET_THRESHOLD = 200; @@ -428,4 +428,4 @@ ReportActionsList.propTypes = propTypes; ReportActionsList.defaultProps = defaultProps; ReportActionsList.displayName = 'ReportActionsList'; -export default compose(withWindowDimensions)(ReportActionsList); +export default withWindowDimensions(ReportActionsList); diff --git a/src/pages/home/report/ReportFooter.js b/src/pages/home/report/ReportFooter.js index 710eed2b86df..760f67dbc310 100644 --- a/src/pages/home/report/ReportFooter.js +++ b/src/pages/home/report/ReportFooter.js @@ -18,7 +18,6 @@ import reportActionPropTypes from './reportActionPropTypes'; import reportPropTypes from '../../reportPropTypes'; import * as ReportUtils from '../../../libs/ReportUtils'; import * as Session from '../../../libs/actions/Session'; -import participantPropTypes from '../../../components/participantPropTypes'; import * as Report from '../../../libs/actions/Report'; import useReportScrollManager from '../../../hooks/useReportScrollManager'; From 9562e8ca37be9b0dd961d9e6f7ea84df753f277a Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 27 Oct 2023 14:44:54 +0500 Subject: [PATCH 010/142] refactor: remove not needed code --- src/libs/PersonalDetailsUtils.js | 16 +++++++++- src/libs/actions/OnyxUpdates.ts | 29 +++++++++---------- src/libs/actions/PersistedRequests.ts | 19 +++++------- src/pages/home/ReportScreen.js | 3 +- .../report/ReportActionsListItemRenderer.js | 4 +++ src/pages/home/report/ReportActionsView.js | 7 ++++- src/pages/home/report/ReportFooter.js | 5 ++-- 7 files changed, 49 insertions(+), 34 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.js b/src/libs/PersonalDetailsUtils.js index c2841b139422..acbdeca80a9d 100644 --- a/src/libs/PersonalDetailsUtils.js +++ b/src/libs/PersonalDetailsUtils.js @@ -176,14 +176,28 @@ function getFormattedAddress(privatePersonalDetails) { return formattedAddress.trim().replace(/,$/, ''); } +/** + * Get personal detail for an accountID + * @param {String} accountID + * @returns {PersonalDetail} personal detail object + */ function getPersonalDetailsByAccountID(accountID) { - return allPersonalDetails[accountID]; + return allPersonalDetails ? allPersonalDetails[accountID] : {}; } +/** + * Get whispered personal details for array of accountIDs + * @param {Array} whisperedToAccountIDs + * @returns {PersonalDetails} personal details + */ function getWhisperedToPersonalDetails(whisperedToAccountIDs) { return _.filter(allPersonalDetails, (details) => _.includes(whisperedToAccountIDs, details.accountID)); } +/** + * Whether personal details is empty + * @returns {Boolean} true if personal details is empty + */ function isPersonalDetailsEmpty() { return !personalDetails.length; } diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index 5f13d8133f16..39a20ae9362a 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -1,6 +1,5 @@ import Onyx, {OnyxEntry} from 'react-native-onyx'; import {Merge} from 'type-fest'; -import {InteractionManager} from 'react-native'; import PusherUtils from '../PusherUtils'; import ONYXKEYS from '../../ONYXKEYS'; import * as QueuedOnyxUpdates from './QueuedOnyxUpdates'; @@ -62,21 +61,19 @@ function apply({lastUpdateID, type, request, response, updates}: Merge | undefined { console.debug(`[OnyxUpdateManager] Applying update type: ${type} with lastUpdateID: ${lastUpdateID}`, {request, response, updates}); - InteractionManager.runAfterInteractions(() => { - if (lastUpdateID && lastUpdateIDAppliedToClient && Number(lastUpdateID) < lastUpdateIDAppliedToClient) { - console.debug('[OnyxUpdateManager] Update received was older than current state, returning without applying the updates'); - return Promise.resolve(); - } - if (lastUpdateID && (lastUpdateIDAppliedToClient === null || Number(lastUpdateID) > lastUpdateIDAppliedToClient)) { - Onyx.merge(ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT, Number(lastUpdateID)); - } - if (type === CONST.ONYX_UPDATE_TYPES.HTTPS && request && response) { - return applyHTTPSOnyxUpdates(request, response); - } - if (type === CONST.ONYX_UPDATE_TYPES.PUSHER && updates) { - return applyPusherOnyxUpdates(updates); - } - }); + if (lastUpdateID && lastUpdateIDAppliedToClient && Number(lastUpdateID) < lastUpdateIDAppliedToClient) { + console.debug('[OnyxUpdateManager] Update received was older than current state, returning without applying the updates'); + return Promise.resolve(); + } + if (lastUpdateID && (lastUpdateIDAppliedToClient === null || Number(lastUpdateID) > lastUpdateIDAppliedToClient)) { + Onyx.merge(ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT, Number(lastUpdateID)); + } + if (type === CONST.ONYX_UPDATE_TYPES.HTTPS && request && response) { + return applyHTTPSOnyxUpdates(request, response); + } + if (type === CONST.ONYX_UPDATE_TYPES.PUSHER && updates) { + return applyPusherOnyxUpdates(updates); + } } /** diff --git a/src/libs/actions/PersistedRequests.ts b/src/libs/actions/PersistedRequests.ts index 1e1a147cecd3..d9f4ed020109 100644 --- a/src/libs/actions/PersistedRequests.ts +++ b/src/libs/actions/PersistedRequests.ts @@ -1,6 +1,5 @@ import Onyx from 'react-native-onyx'; import isEqual from 'lodash/isEqual'; -import {InteractionManager} from 'react-native'; import ONYXKEYS from '../../ONYXKEYS'; import {Request} from '../../types/onyx'; @@ -34,16 +33,14 @@ function remove(requestToRemove: Request) { * We only remove the first matching request because the order of requests matters. * If we were to remove all matching requests, we can end up with a final state that is different than what the user intended. */ - InteractionManager.runAfterInteractions(() => { - const requests = [...persistedRequests]; - const index = requests.findIndex((persistedRequest) => isEqual(persistedRequest, requestToRemove)); - if (index === -1) { - return; - } - requests.splice(index, 1); - persistedRequests = requests; - Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests); - }); + const requests = [...persistedRequests]; + const index = requests.findIndex((persistedRequest) => isEqual(persistedRequest, requestToRemove)); + if (index === -1) { + return; + } + requests.splice(index, 1); + persistedRequests = requests; + Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests); } function update(oldRequestIndex: number, newRequest: Request) { diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 76be144150c3..ed1fd91b9834 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -172,7 +172,7 @@ function ReportScreen({ const parentReportAction = ReportActionsUtils.getParentReportAction(report); const lastReportAction = useMemo( - () => _.find([...reportActions, parentReportAction], (action) => ReportUtils.canEditReportAction(action) && !ReportActionsUtils.isMoneyRequestAction(action)), + () => reportActions.length ? _.find([...reportActions, parentReportAction], (action) => ReportUtils.canEditReportAction(action) && !ReportActionsUtils.isMoneyRequestAction(action)) : {}, [reportActions, parentReportAction], ); const isSingleTransactionView = ReportUtils.isMoneyRequest(report); @@ -437,7 +437,6 @@ function ReportScreen({ ({ reportActionID: reportAction.reportActionID, diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 415b8b6fbd1b..f1bd30f1a8b9 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -216,12 +216,17 @@ function ReportActionsView(props) { } }; + /** + * Create a lightweight Report so as to keep the re-rendering as light as possible by + * passing in only the required props. + */ const report = useMemo( () => ({ lastReadTime: props.report.lastReadTime, reportID: props.report.reportID, + policyID: props.report.policyID, }), - [props.report.lastReadTime, props.report.reportID], + [props.report.lastReadTime, props.report.reportID, props.report.policyID], ); // Comments have not loaded at all yet do nothing diff --git a/src/pages/home/report/ReportFooter.js b/src/pages/home/report/ReportFooter.js index 760f67dbc310..6f061cb3d015 100644 --- a/src/pages/home/report/ReportFooter.js +++ b/src/pages/home/report/ReportFooter.js @@ -85,7 +85,7 @@ function ReportFooter(props) { const onSubmitComment = useCallback( (text) => { - Report.addComment(props.reportID, text); + Report.addComment(props.report.reportID, text); // We need to scroll to the bottom of the list after the comment is added const refID = setTimeout(() => { @@ -119,7 +119,7 @@ function ReportFooter(props) { isEqual(prevProps.report, nextProps.report) && - isEqual(prevProps.reportActions, nextProps.reportActions) && prevProps.pendingAction === nextProps.pendingAction && prevProps.shouldDisableCompose === nextProps.shouldDisableCompose && prevProps.listHeight === nextProps.listHeight && From ad1b337ebc433635371547a8c8c8bbeeff800618 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 27 Oct 2023 16:15:37 +0500 Subject: [PATCH 011/142] fix: linting and test --- src/libs/PersonalDetailsUtils.js | 2 +- .../report/ReportActionCompose/ComposerWithSuggestions.js | 2 ++ tests/unit/ReportActionItemSingleTest.js | 6 ++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.js b/src/libs/PersonalDetailsUtils.js index acbdeca80a9d..965f1b927095 100644 --- a/src/libs/PersonalDetailsUtils.js +++ b/src/libs/PersonalDetailsUtils.js @@ -178,7 +178,7 @@ function getFormattedAddress(privatePersonalDetails) { /** * Get personal detail for an accountID - * @param {String} accountID + * @param {Number} accountID * @returns {PersonalDetail} personal detail object */ function getPersonalDetailsByAccountID(accountID) { diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js index 79c53650f27a..115cf404e589 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js @@ -283,6 +283,7 @@ function ComposerWithSuggestions({ debouncedBroadcastUserIsTyping(reportID); } }, + // eslint-disable-next-line react-hooks/exhaustive-deps [ preferredSkinTone, reportID, @@ -555,6 +556,7 @@ function ComposerWithSuggestions({ const onChangeText = useCallback((text) => { updateComment(text, true); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( diff --git a/tests/unit/ReportActionItemSingleTest.js b/tests/unit/ReportActionItemSingleTest.js index d6b46eb55414..799f6363d778 100644 --- a/tests/unit/ReportActionItemSingleTest.js +++ b/tests/unit/ReportActionItemSingleTest.js @@ -1,5 +1,5 @@ import Onyx from 'react-native-onyx'; -import {cleanup, screen} from '@testing-library/react-native'; +import {cleanup, screen, waitFor} from '@testing-library/react-native'; import * as LHNTestUtils from '../utils/LHNTestUtils'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; @@ -70,7 +70,9 @@ describe('ReportActionItemSingle', () => { const expectedSecondaryIconTestId = 'SvgDefaultAvatar_w Icon'; return setup().then(() => { - expect(screen.getByTestId(expectedSecondaryIconTestId)).toBeDefined(); + waitFor(() => { + expect(screen.getByTestId(expectedSecondaryIconTestId)).toBeDefined(); + }); }); }); From 9a27bede1cd7a13f7f61bc32afdc23e91113026b Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 27 Oct 2023 17:08:19 +0500 Subject: [PATCH 012/142] fix: failing unread indicator test --- tests/ui/UnreadIndicatorsTest.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/ui/UnreadIndicatorsTest.js b/tests/ui/UnreadIndicatorsTest.js index a9ffe258ac7f..34e2c0322cde 100644 --- a/tests/ui/UnreadIndicatorsTest.js +++ b/tests/ui/UnreadIndicatorsTest.js @@ -524,13 +524,12 @@ describe('Unread Indicators', () => { .then(() => { // Simulate the response from the server so that the comment can be deleted in this test lastReportAction = {...CollectionUtils.lastItem(reportActions)}; - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, { + return Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, { lastMessageText: lastReportAction.message[0].text, lastVisibleActionCreated: DateUtils.getDBTime(lastReportAction.timestamp), lastActorAccountID: lastReportAction.actorAccountID, reportID: REPORT_ID, }); - return waitForBatchedUpdates(); }) .then(() => { // Verify the chat preview text matches the last comment from the current user From 9f294367e0293535250ba3857e9b121dd0a215b4 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 27 Oct 2023 17:15:08 +0500 Subject: [PATCH 013/142] fix: prettier issues --- src/components/LHNOptionsList/OptionRowLHNData.js | 13 +------------ src/components/withCurrentUserPersonalDetails.tsx | 2 +- src/libs/PersonalDetailsUtils.js | 4 ++-- src/libs/SidebarUtils.ts | 2 +- src/pages/home/ReportScreen.js | 5 ++++- .../ReportActionCompose/ComposerWithSuggestions.js | 8 ++------ src/pages/home/report/ReportActionsList.js | 2 +- 7 files changed, 12 insertions(+), 24 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index 2ddf8a687cfd..89142febf679 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -63,18 +63,7 @@ const defaultProps = { * The OptionRowLHN component is memoized, so it will only * re-render if the data really changed. */ -function OptionRowLHNData({ - isFocused, - fullReport, - reportActions, - preferredLocale, - comment, - policy, - receiptTransactions, - parentReportActions, - transaction, - ...propsToForward -}) { +function OptionRowLHNData({isFocused, fullReport, reportActions, preferredLocale, comment, policy, receiptTransactions, parentReportActions, transaction, ...propsToForward}) { const reportID = propsToForward.reportID; const parentReportAction = parentReportActions[fullReport.parentReportActionID]; diff --git a/src/components/withCurrentUserPersonalDetails.tsx b/src/components/withCurrentUserPersonalDetails.tsx index fa81c658bc78..a54f3f08c8b8 100644 --- a/src/components/withCurrentUserPersonalDetails.tsx +++ b/src/components/withCurrentUserPersonalDetails.tsx @@ -4,7 +4,7 @@ import getComponentDisplayName from '../libs/getComponentDisplayName'; import ONYXKEYS from '../ONYXKEYS'; import personalDetailsPropType from '../pages/personalDetailsPropType'; import type {PersonalDetails, Session} from '../types/onyx'; -import { getPersonalDetailsByAccountID } from '../libs/PersonalDetailsUtils'; +import {getPersonalDetailsByAccountID} from '../libs/PersonalDetailsUtils'; type CurrentUserPersonalDetails = PersonalDetails | Record; diff --git a/src/libs/PersonalDetailsUtils.js b/src/libs/PersonalDetailsUtils.js index 965f1b927095..930ba19ad77b 100644 --- a/src/libs/PersonalDetailsUtils.js +++ b/src/libs/PersonalDetailsUtils.js @@ -178,7 +178,7 @@ function getFormattedAddress(privatePersonalDetails) { /** * Get personal detail for an accountID - * @param {Number} accountID + * @param {Number} accountID * @returns {PersonalDetail} personal detail object */ function getPersonalDetailsByAccountID(accountID) { @@ -187,7 +187,7 @@ function getPersonalDetailsByAccountID(accountID) { /** * Get whispered personal details for array of accountIDs - * @param {Array} whisperedToAccountIDs + * @param {Array} whisperedToAccountIDs * @returns {PersonalDetails} personal details */ function getWhisperedToPersonalDetails(whisperedToAccountIDs) { diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 6db4c5aa8241..c71548a483db 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1,5 +1,5 @@ /* eslint-disable rulesdir/prefer-underscore-method */ -import Onyx, { OnyxEntry } from 'react-native-onyx'; +import Onyx, {OnyxEntry} from 'react-native-onyx'; import Str from 'expensify-common/lib/str'; import {ValueOf} from 'type-fest'; import ONYXKEYS from '../ONYXKEYS'; diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index ed1fd91b9834..31763e064c1d 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -172,7 +172,10 @@ function ReportScreen({ const parentReportAction = ReportActionsUtils.getParentReportAction(report); const lastReportAction = useMemo( - () => reportActions.length ? _.find([...reportActions, parentReportAction], (action) => ReportUtils.canEditReportAction(action) && !ReportActionsUtils.isMoneyRequestAction(action)) : {}, + () => + reportActions.length + ? _.find([...reportActions, parentReportAction], (action) => ReportUtils.canEditReportAction(action) && !ReportActionsUtils.isMoneyRequestAction(action)) + : {}, [reportActions, parentReportAction], ); const isSingleTransactionView = ReportUtils.isMoneyRequest(report); diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js index 115cf404e589..26e33a08417e 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js @@ -284,11 +284,7 @@ function ComposerWithSuggestions({ } }, // eslint-disable-next-line react-hooks/exhaustive-deps - [ - preferredSkinTone, - reportID, - suggestionsRef, - ], + [preferredSkinTone, reportID, suggestionsRef], ); /** @@ -556,7 +552,7 @@ function ComposerWithSuggestions({ const onChangeText = useCallback((text) => { updateComment(text, true); - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index 8127e40a3aa2..6945cf82d158 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -71,7 +71,7 @@ const defaultProps = { isLoadingInitialReportActions: false, isLoadingOlderReportActions: false, isLoadingNewerReportActions: false, - policy: {} + policy: {}, }; const VERTICAL_OFFSET_THRESHOLD = 200; From 352227f178903ec5efe723578c47a12b3ff2ed95 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 27 Oct 2023 19:08:09 +0500 Subject: [PATCH 014/142] fix: failing test --- src/pages/home/report/ReportActionsView.js | 70 ++++------------------ tests/ui/UnreadIndicatorsTest.js | 20 ++++--- 2 files changed, 25 insertions(+), 65 deletions(-) diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index f1bd30f1a8b9..266ed6e2e346 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -116,7 +116,7 @@ function ReportActionsView(props) { // update ref with current network state prevNetworkRef.current = props.network; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.network, props.report, isReportFullyVisible]); + }, [props.network, isReportFullyVisible]); useEffect(() => { const prevIsSmallScreenWidth = prevIsSmallScreenWidthRef.current; @@ -130,7 +130,7 @@ function ReportActionsView(props) { // update ref with current state prevIsSmallScreenWidthRef.current = props.isSmallScreenWidth; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.isSmallScreenWidth, props.report, props.reportActions, isReportFullyVisible]); + }, [props.isSmallScreenWidth, props.reportActions, isReportFullyVisible]); useEffect(() => { // Ensures subscription event succeeds when the report/workspace room is created optimistically. @@ -142,7 +142,7 @@ function ReportActionsView(props) { Report.subscribeToReportTypingEvents(reportID); didSubscribeToReportTypingEvents.current = true; } - }, [props.report, didSubscribeToReportTypingEvents, reportID]); + }, [props.report.pendingFields, didSubscribeToReportTypingEvents, reportID]); /** * Retrieves the next set of report actions for the chat once we are nearing the end of what we are currently @@ -225,8 +225,9 @@ function ReportActionsView(props) { lastReadTime: props.report.lastReadTime, reportID: props.report.reportID, policyID: props.report.policyID, + lastVisibleActionCreated: props.report.lastVisibleActionCreated, }), - [props.report.lastReadTime, props.report.reportID, props.report.policyID], + [props.report.lastReadTime, props.report.reportID, props.report.policyID, props.report.lastVisibleActionCreated], ); // Comments have not loaded at all yet do nothing @@ -262,14 +263,6 @@ function arePropsEqual(oldProps, newProps) { return false; } - if (!_.isEqual(oldProps.report.pendingFields, newProps.report.pendingFields)) { - return false; - } - - if (!_.isEqual(oldProps.report.errorFields, newProps.report.errorFields)) { - return false; - } - if (lodashGet(oldProps.network, 'isOffline') !== lodashGet(newProps.network, 'isOffline')) { return false; } @@ -286,26 +279,14 @@ function arePropsEqual(oldProps, newProps) { return false; } - if (oldProps.report.lastReadTime !== newProps.report.lastReadTime) { - return false; - } - if (newProps.isSmallScreenWidth !== oldProps.isSmallScreenWidth) { return false; } - if (lodashGet(newProps.report, 'hasOutstandingIOU') !== lodashGet(oldProps.report, 'hasOutstandingIOU')) { - return false; - } - if (newProps.isComposerFullSize !== oldProps.isComposerFullSize) { return false; } - if (lodashGet(newProps.report, 'statusNum') !== lodashGet(oldProps.report, 'statusNum') || lodashGet(newProps.report, 'stateNum') !== lodashGet(oldProps.report, 'stateNum')) { - return false; - } - if (lodashGet(newProps, 'policy.avatar') !== lodashGet(oldProps, 'policy.avatar')) { return false; } @@ -314,39 +295,14 @@ function arePropsEqual(oldProps, newProps) { return false; } - if (lodashGet(newProps, 'report.reportName') !== lodashGet(oldProps, 'report.reportName')) { - return false; - } - - if (lodashGet(newProps, 'report.description') !== lodashGet(oldProps, 'report.description')) { - return false; - } - - if (lodashGet(newProps, 'report.managerID') !== lodashGet(oldProps, 'report.managerID')) { - return false; - } - - if (lodashGet(newProps, 'report.managerEmail') !== lodashGet(oldProps, 'report.managerEmail')) { - return false; - } - - if (lodashGet(newProps, 'report.total') !== lodashGet(oldProps, 'report.total')) { - return false; - } - - if (lodashGet(newProps, 'report.nonReimbursableTotal') !== lodashGet(oldProps, 'report.nonReimbursableTotal')) { - return false; - } - - if (lodashGet(newProps, 'report.writeCapability') !== lodashGet(oldProps, 'report.writeCapability')) { - return false; - } - - if (lodashGet(newProps, 'report.participantAccountIDs', 0) !== lodashGet(oldProps, 'report.participantAccountIDs', 0)) { - return false; - } - - return _.isEqual(lodashGet(newProps.report, 'icons', []), lodashGet(oldProps.report, 'icons', [])); + return ( + oldProps.report.lastReadTime === newProps.report.lastReadTime && + oldProps.report.reportID === newProps.report.reportID && + oldProps.report.policyID === newProps.report.policyID && + oldProps.report.lastVisibleActionCreated === newProps.report.lastVisibleActionCreated && + oldProps.report.isOptimisticReport === newProps.report.isOptimisticReport && + _.isEqual(oldProps.report.pendingFields, newProps.report.pendingFields) + ); } const MemoizedReportActionsView = React.memo(ReportActionsView, arePropsEqual); diff --git a/tests/ui/UnreadIndicatorsTest.js b/tests/ui/UnreadIndicatorsTest.js index 34e2c0322cde..0fdee8aa63f7 100644 --- a/tests/ui/UnreadIndicatorsTest.js +++ b/tests/ui/UnreadIndicatorsTest.js @@ -130,13 +130,15 @@ function signInAndGetAppWithUnreadChat() { // Render the App and sign in as a test user. render(); return waitForBatchedUpdatesWithAct() - .then(() => { - const hintText = Localize.translateLocal('loginForm.loginForm'); - const loginForm = screen.queryAllByLabelText(hintText); - expect(loginForm).toHaveLength(1); - - return TestHelper.signInWithTestUser(USER_A_ACCOUNT_ID, USER_A_EMAIL, undefined, undefined, 'A'); - }) + .then(() => + waitFor(() => { + const hintText = Localize.translateLocal('loginForm.loginForm'); + const loginForm = screen.queryAllByLabelText(hintText); + expect(loginForm).toHaveLength(1); + + return TestHelper.signInWithTestUser(USER_A_ACCOUNT_ID, USER_A_EMAIL, undefined, undefined, 'A'); + }), + ) .then(() => { User.subscribeToUserEvents(); return waitForBatchedUpdates(); @@ -524,12 +526,14 @@ describe('Unread Indicators', () => { .then(() => { // Simulate the response from the server so that the comment can be deleted in this test lastReportAction = {...CollectionUtils.lastItem(reportActions)}; - return Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, { + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, { lastMessageText: lastReportAction.message[0].text, lastVisibleActionCreated: DateUtils.getDBTime(lastReportAction.timestamp), lastActorAccountID: lastReportAction.actorAccountID, reportID: REPORT_ID, }); + + return waitForBatchedUpdates(); }) .then(() => { // Verify the chat preview text matches the last comment from the current user From 3820a4561ad4464d85adfbbda493ff776c37f01f Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 6 Nov 2023 17:13:07 +0500 Subject: [PATCH 015/142] fix: reassure failures reported --- src/pages/home/ReportScreen.js | 22 ------------------- .../home/report/ReportActionItemSingle.js | 2 +- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index ceeae027f522..a2d76192820c 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -1,4 +1,3 @@ -import {useFocusEffect} from '@react-navigation/core'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; @@ -20,13 +19,11 @@ import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useWindowDimensions from '@hooks/useWindowDimensions'; import compose from '@libs/compose'; -import getIsReportFullyVisible from '@libs/getIsReportFullyVisible'; import Navigation from '@libs/Navigation/Navigation'; import reportWithoutHasDraftSelector from '@libs/OnyxSelectors/reportWithoutHasDraftSelector'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import Visibility from '@libs/Visibility'; import reportMetadataPropTypes from '@pages/reportMetadataPropTypes'; import reportPropTypes from '@pages/reportPropTypes'; import styles from '@styles/styles'; @@ -259,25 +256,6 @@ function ReportScreen({ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(accountManagerReportID)); }, [accountManagerReportID]); - useFocusEffect( - useCallback(() => { - const unsubscribeVisibilityListener = Visibility.onVisibilityChange(() => { - const isTopMostReportID = Navigation.getTopmostReportId() === getReportID(route); - // If the report is not fully visible (AKA on small screen devices and LHR is open) or the report is optimistic (AKA not yet created) - // we don't need to call openReport - if (!getIsReportFullyVisible(isTopMostReportID) || report.isOptimisticReport) { - return; - } - - Report.openReport(report.reportID); - }); - - return () => unsubscribeVisibilityListener(); - // The effect should run only on the first focus to attach listener - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []), - ); - useEffect(() => { fetchReportIfNeeded(); ComposerActions.setShouldShowComposeInput(true); diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 634db2793710..cab7011e88fe 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -112,7 +112,7 @@ function ReportActionItemSingle(props) { if (displayAllActors) { // The ownerAccountID and actorAccountID can be the same if the a user requests money back from the IOU's original creator, in that case we need to use managerID to avoid displaying the same user twice const secondaryAccountId = props.iouReport.ownerAccountID === actorAccountID ? props.iouReport.managerID : props.iouReport.ownerAccountID; - const secondaryUserDetails = PersonalDetailsUtils.getPersonalDetailsByAccountID(secondaryAccountId); + const secondaryUserDetails = PersonalDetailsUtils.getPersonalDetailsByAccountID(secondaryAccountId) || {}; const secondaryDisplayName = lodashGet(secondaryUserDetails, 'displayName', ''); displayName = `${primaryDisplayName} & ${secondaryDisplayName}`; secondaryAvatar = { From ddf53d3c22e832d8d1ebecff9d08d0626909d067 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 7 Nov 2023 13:49:08 +0500 Subject: [PATCH 016/142] fix: reported reassure issues --- src/components/AttachmentModal.js | 23 ++++++- src/pages/home/ReportScreen.js | 1 - .../ReportActionCompose.js | 62 ++++++++++--------- src/pages/home/report/ReportFooter.js | 50 ++++++--------- 4 files changed, 75 insertions(+), 61 deletions(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 7fe342127272..8de07139e022 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -2,7 +2,7 @@ import Str from 'expensify-common/lib/str'; import lodashExtend from 'lodash/extend'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import {Animated, Keyboard, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -393,6 +393,14 @@ function AttachmentModal(props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [isAttachmentReceipt, props.parentReport, props.parentReportActions, props.policy, props.transaction]); + useImperativeHandle( + props.forwardedRef, + () => ({ + displayFileInModal: validateAndDisplayFileToUpload, + }), + [validateAndDisplayFileToUpload], + ); + return ( <> ( + +)); + +AttachmentModalWithRef.displayName = 'AttachmentModalWithRef'; + export default compose( withWindowDimensions, withLocalize, @@ -539,4 +558,4 @@ export default compose( key: ONYXKEYS.SESSION, }, }), -)(memo(AttachmentModal)); +)(memo(AttachmentModalWithRef)); diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index a2d76192820c..506e635b2336 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -419,7 +419,6 @@ function ReportScreen({ report={report} pendingAction={addWorkspaceRoomOrChatPendingAction} isComposerFullSize={isComposerFullSize} - policies={policies} listHeight={listHeight} isEmptyChat={isEmptyChat} lastReportAction={lastReportAction} diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js index 420422534ba7..8b61aa0fa199 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js @@ -136,6 +136,7 @@ function ReportActionCompose({ const suggestionsRef = useRef(null); const composerRef = useRef(null); + const attachementModalRef = useRef(null); const reportParticipantIDs = useMemo( () => _.without(lodashGet(report, 'participantAccountIDs', []), currentUserPersonalDetails.accountID), @@ -316,6 +317,10 @@ function ReportActionCompose({ runOnJS(submitForm)(); }, [isSendDisabled, resetFullComposerSize, submitForm, isReportReadyForDisplay]); + const onDisplayFileInModal = () => { + attachementModalRef.current.displayFileInModal(); + }; + return ( @@ -339,6 +344,7 @@ function ReportActionCompose({ ]} > setIsAttachmentPreviewActive(true)} @@ -365,34 +371,6 @@ function ReportActionCompose({ onItemSelected={onItemSelected} actionButtonRef={actionButtonRef} /> - { if (isAttachmentPreviewActive) { @@ -405,6 +383,34 @@ function ReportActionCompose({ )} + { - // eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs - const connID = Onyx.connect({ - key: ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT, - callback: (val) => { - if (val === shouldShowComposeInput) { - return; - } - setShouldShowComposeInput(val); - }, - }); - - return () => { - Onyx.disconnect(connID); - }; - }, [shouldShowComposeInput]); - const onSubmitComment = useCallback( (text) => { Report.addComment(props.report.reportID, text); @@ -114,7 +95,7 @@ function ReportFooter(props) { )} )} - {!hideComposer && (shouldShowComposeInput || !props.isSmallScreenWidth) && ( + {!hideComposer && (props.shouldShowComposeInput || !props.isSmallScreenWidth) && ( @@ -139,14 +119,24 @@ ReportFooter.displayName = 'ReportFooter'; ReportFooter.propTypes = propTypes; ReportFooter.defaultProps = defaultProps; -export default withWindowDimensions( +export default compose( + withWindowDimensions, + withOnyx({ + shouldShowComposeInput: { + key: ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT, + }, + }), +)( memo( ReportFooter, (prevProps, nextProps) => isEqual(prevProps.report, nextProps.report) && prevProps.pendingAction === nextProps.pendingAction && - prevProps.shouldDisableCompose === nextProps.shouldDisableCompose && prevProps.listHeight === nextProps.listHeight && + prevProps.isComposerFullSize === nextProps.isComposerFullSize && + prevProps.isEmptyChat === nextProps.isEmptyChat && + prevProps.lastReportAction === nextProps.lastReportAction && + prevProps.shouldShowComposeInput === nextProps.shouldShowComposeInput && prevProps.isReportReadyForDisplay === nextProps.isReportReadyForDisplay, ), ); From 5e2f21f8b62472cb67715f5bcd5f26e9e2b70e0e Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 7 Nov 2023 14:26:07 +0500 Subject: [PATCH 017/142] fix: reported reassure issues --- src/components/LHNOptionsList/LHNOptionsList.js | 14 +------------- .../ComposerWithSuggestions.js | 2 +- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 3986773aca87..2603c0128af3 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -4,10 +4,8 @@ import React, {useCallback} from 'react'; import {FlatList, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; -import participantPropTypes from '@components/participantPropTypes'; import withCurrentReportID, {withCurrentReportIDDefaultProps, withCurrentReportIDPropTypes} from '@components/withCurrentReportID'; import compose from '@libs/compose'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; import reportPropTypes from '@pages/reportPropTypes'; import styles from '@styles/styles'; @@ -56,9 +54,6 @@ const propTypes = { /** Indicates which locale the user currently has selected */ preferredLocale: PropTypes.string, - /** List of users' personal details */ - personalDetails: PropTypes.objectOf(participantPropTypes), - /** The transaction from the parent report action */ transactions: PropTypes.objectOf( PropTypes.shape({ @@ -78,7 +73,6 @@ const defaultProps = { reports: {}, policy: {}, preferredLocale: CONST.LOCALES.DEFAULT, - personalDetails: {}, transactions: {}, draftComments: {}, ...withCurrentReportIDDefaultProps, @@ -97,7 +91,6 @@ function LHNOptionsList({ reportActions, policy, preferredLocale, - personalDetails, transactions, draftComments, currentReportID, @@ -144,7 +137,6 @@ function LHNOptionsList({ '', )}`; const itemComment = draftComments[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] || ''; - const participantPersonalDetailList = _.values(OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport.participantAccountIDs, personalDetails)); return ( ); }, - [currentReportID, draftComments, onSelectRow, optionMode, personalDetails, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], + [currentReportID, draftComments, onSelectRow, optionMode, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], ); return ( @@ -206,9 +197,6 @@ export default compose( preferredLocale: { key: ONYXKEYS.NVP_PREFERRED_LOCALE, }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, transactions: { key: ONYXKEYS.COLLECTION.TRANSACTION, }, diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index cf772230d2bf..7c5d0cfe2f2a 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -29,6 +29,7 @@ import * as SuggestionUtils from '@libs/SuggestionUtils'; import updateMultilineInputRange from '@libs/UpdateMultilineInputRange'; import updatePropsPaperWorklet from '@libs/updatePropsPaperWorklet'; import willBlurTextInputOnTapOutsideFunc from '@libs/willBlurTextInputOnTapOutside'; +import SendButton from '@pages/home/report/ReportActionCompose/SendButton'; import SilentCommentUpdater from '@pages/home/report/ReportActionCompose/SilentCommentUpdater'; import Suggestions from '@pages/home/report/ReportActionCompose/Suggestions'; import containerComposeStyles from '@styles/containerComposeStyles'; @@ -40,7 +41,6 @@ import * as Report from '@userActions/Report'; import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import SendButton from '@pages/home/report/ReportActionCompose/SendButton'; import {defaultProps, propTypes} from './composerWithSuggestionsProps'; const {RNTextInputReset} = NativeModules; From 46c9d60399162fb2f2ff30ac5edd9760d2d0e159 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 13 Nov 2023 13:54:36 +0500 Subject: [PATCH 018/142] fix: pr review --- src/components/ArchivedReportFooter.js | 6 ++--- src/components/AttachmentModal.js | 20 ++++------------ src/components/AvatarWithDisplayName.js | 2 +- src/libs/OptionsListUtils.js | 11 +++++++-- src/libs/PersonalDetailsUtils.js | 15 +++++++++--- src/libs/ReportUtils.js | 6 ++--- src/libs/SidebarUtils.ts | 4 ++-- src/libs/actions/Task.js | 2 +- src/pages/ReportDetailsPage.js | 2 +- .../AttachmentPickerWithMenuItems.js | 3 ++- .../composerWithSuggestionsProps.js | 3 ++- .../ReportActionCompose/SuggestionMention.js | 24 ++++++------------- src/pages/home/report/ReportActionItem.js | 2 +- src/pages/home/report/ReportFooter.js | 2 ++ 14 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/components/ArchivedReportFooter.js b/src/components/ArchivedReportFooter.js index a17cdb92136d..8c790f106ae7 100644 --- a/src/components/ArchivedReportFooter.js +++ b/src/components/ArchivedReportFooter.js @@ -46,14 +46,14 @@ const defaultProps = { function ArchivedReportFooter(props) { const archiveReason = lodashGet(props.reportClosedAction, 'originalMessage.reason', CONST.REPORT.ARCHIVE_REASON.DEFAULT); - let displayName = PersonalDetailsUtils.getDisplayNameOrDefault(null, [props.report.ownerAccountID, 'displayName']); + let displayName = PersonalDetailsUtils.getDisplayNameOrDefault([props.report.ownerAccountID, 'displayName']); let oldDisplayName; if (archiveReason === CONST.REPORT.ARCHIVE_REASON.ACCOUNT_MERGED) { const newAccountID = props.reportClosedAction.originalMessage.newAccountID; const oldAccountID = props.reportClosedAction.originalMessage.oldAccountID; - displayName = PersonalDetailsUtils.getDisplayNameOrDefault(null, [newAccountID, 'displayName']); - oldDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(null, [oldAccountID, 'displayName']); + displayName = PersonalDetailsUtils.getDisplayNameOrDefault([newAccountID, 'displayName']); + oldDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault([oldAccountID, 'displayName']); } const shouldRenderHTML = archiveReason !== CONST.REPORT.ARCHIVE_REASON.DEFAULT; diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 24ac0421c5b2..57ed1ff3e131 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -2,7 +2,7 @@ import Str from 'expensify-common/lib/str'; import lodashExtend from 'lodash/extend'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; +import React, {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import {Animated, Keyboard, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -110,7 +110,7 @@ const defaultProps = { isWorkspaceAvatar: false, }; -function AttachmentModal(props) { +const AttachmentModal = forwardRef((props, ref) => { const onModalHideCallbackRef = useRef(null); const [isModalOpen, setIsModalOpen] = useState(props.defaultOpen); const [shouldLoadAttachment, setShouldLoadAttachment] = useState(false); @@ -394,7 +394,7 @@ function AttachmentModal(props) { }, [isAttachmentReceipt, props.parentReport, props.parentReportActions, props.policy, props.transaction, file]); useImperativeHandle( - props.forwardedRef, + ref, () => ({ displayFileInModal: validateAndDisplayFileToUpload, }), @@ -514,22 +514,12 @@ function AttachmentModal(props) { })} ); -} +}); AttachmentModal.propTypes = propTypes; AttachmentModal.defaultProps = defaultProps; AttachmentModal.displayName = 'AttachmentModal'; -const AttachmentModalWithRef = React.forwardRef((props, ref) => ( - -)); - -AttachmentModalWithRef.displayName = 'AttachmentModalWithRef'; - export default compose( withWindowDimensions, withLocalize, @@ -558,4 +548,4 @@ export default compose( key: ONYXKEYS.SESSION, }, }), -)(memo(AttachmentModalWithRef)); +)(memo(AttachmentModal)); diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index 4be31b7d1a45..194a9239fa57 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -88,7 +88,7 @@ function AvatarWithDisplayName(props) { const subtitle = ReportUtils.getChatRoomSubtitle(props.report); const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(props.report); const isMoneyRequestOrReport = ReportUtils.isMoneyRequestReport(props.report) || ReportUtils.isMoneyRequest(props.report); - const icons = ReportUtils.getIcons(props.report, null, props.policy); + const icons = ReportUtils.getIcons(props.report, null, '', -1, props.policy); const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs([props.report.ownerAccountID]); const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(_.values(ownerPersonalDetails), false); const shouldShowSubscriptAvatar = ReportUtils.shouldReportShowSubscript(props.report); diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 485bfbbbf08d..6d5588548636 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -529,7 +529,7 @@ function createOption(accountIDs, personalDetails, report, reportActions = {}, { (lastReportActions[report.reportID] && lastReportActions[report.reportID].originalMessage && lastReportActions[report.reportID].originalMessage.reason) || CONST.REPORT.ARCHIVE_REASON.DEFAULT; lastMessageText = Localize.translate(preferredLocale, `reportArchiveReasons.${archiveReason}`, { - displayName: archiveReason.displayName || PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails, 'displayName'), + displayName: archiveReason.displayName || PersonalDetailsUtils.getDisplayNameOrDefault('displayName', lastActorDetails), policyName: ReportUtils.getPolicyName(report), }); } @@ -561,7 +561,14 @@ function createOption(accountIDs, personalDetails, report, reportActions = {}, { result.text = reportName; result.searchText = getSearchText(report, reportName, personalDetailList, result.isChatRoom || result.isPolicyExpenseChat, result.isThread); - result.icons = ReportUtils.getIcons(report, personalDetails, UserUtils.getAvatar(personalDetail.avatar, personalDetail.accountID), personalDetail.login, personalDetail.accountID); + result.icons = ReportUtils.getIcons( + report, + UserUtils.getAvatar(personalDetail.avatar, personalDetail.accountID), + personalDetail.login, + personalDetail.accountID, + undefined, + personalDetails, + ); result.subtitle = subtitle; return result; diff --git a/src/libs/PersonalDetailsUtils.js b/src/libs/PersonalDetailsUtils.js index 842c06bb3228..bbcfd53454a9 100644 --- a/src/libs/PersonalDetailsUtils.js +++ b/src/libs/PersonalDetailsUtils.js @@ -17,13 +17,13 @@ Onyx.connect({ }); /** - * @param {Object | Null} passedPersonalDetails * @param {Array | String} pathToDisplayName * @param {String} [defaultValue] optional default display name value + * @param {Object | Null} passedPersonalDetails optional default personal details object * @returns {String} */ -function getDisplayNameOrDefault(passedPersonalDetails, pathToDisplayName, defaultValue = '') { - const displayName = lodashGet(passedPersonalDetails || allPersonalDetails, pathToDisplayName); +function getDisplayNameOrDefault(pathToDisplayName, defaultValue = '', passedPersonalDetails = allPersonalDetails) { + const displayName = lodashGet(passedPersonalDetails, pathToDisplayName); return displayName || defaultValue || Localize.translateLocal('common.hidden'); } @@ -203,6 +203,14 @@ function isPersonalDetailsEmpty() { return !personalDetails.length; } +/** + * Get personal details object + * @returns {PersonalDetail} personal detail object + */ +function getPersonalDetails() { + return allPersonalDetails || {}; +} + export { getDisplayNameOrDefault, getPersonalDetailsByIDs, @@ -213,4 +221,5 @@ export { getPersonalDetailsByAccountID, getWhisperedToPersonalDetails, isPersonalDetailsEmpty, + getPersonalDetails, }; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 83cf62f6da04..38846dce5bc0 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1103,15 +1103,15 @@ function getWorkspaceIcon(report, policy = undefined) { * The Avatar sources can be URLs or Icon components according to the chat type. * * @param {Object} report - * @param {Object} passedPersonalDetails * @param {*} [defaultIcon] * @param {String} [defaultName] * @param {Number} [defaultAccountID] * @param {Object} [policy] + * @param {Object} passedPersonalDetails * @returns {Array<*>} */ -function getIcons(report, passedPersonalDetails, defaultIcon = null, defaultName = '', defaultAccountID = -1, policy = undefined) { - const personalDetails = passedPersonalDetails || allPersonalDetails; +function getIcons(report, defaultIcon = null, defaultName = '', defaultAccountID = -1, policy = undefined, passedPersonalDetails = allPersonalDetails) { + const personalDetails = passedPersonalDetails; if (_.isEmpty(report)) { const fallbackIcon = { source: defaultIcon || Expensicons.FallbackAvatar, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 97ae1d25cdd9..826db3995c43 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -425,7 +425,7 @@ function getOptionData( case CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED: { lastMessageText = Localize.translate(preferredLocale, `reportArchiveReasons.${archiveReason}`, { policyName: ReportUtils.getPolicyName(report, false, policy), - displayName: PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails, 'displayName'), + displayName: PersonalDetailsUtils.getDisplayNameOrDefault('displayName', '', lastActorDetails), }); break; } @@ -515,7 +515,7 @@ function getOptionData( result.subtitle = subtitle; result.participantsList = participantPersonalDetailList; - result.icons = ReportUtils.getIcons(report, personalDetails, UserUtils.getAvatar(personalDetail.avatar, personalDetail.accountID), '', -1, policy); + result.icons = ReportUtils.getIcons(report, UserUtils.getAvatar(personalDetail.avatar, personalDetail.accountID), '', -1, policy, personalDetails); result.searchText = OptionsListUtils.getSearchText(report, reportName, participantPersonalDetailList, result.isChatRoom || result.isPolicyExpenseChat, result.isThread); result.displayNamesWithTooltips = displayNamesWithTooltips; diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index e884a4d7a6b3..6329552d67e2 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -699,7 +699,7 @@ function getShareDestination(reportID, reports, personalDetails) { subtitle = ReportUtils.getChatRoomSubtitle(report); } return { - icons: ReportUtils.getIcons(report, personalDetails, Expensicons.FallbackAvatar), + icons: ReportUtils.getIcons(report, Expensicons.FallbackAvatar, '', -1, undefined, personalDetails), displayName: ReportUtils.getReportName(report), subtitle, displayNamesWithTooltips, diff --git a/src/pages/ReportDetailsPage.js b/src/pages/ReportDetailsPage.js index de25fdc3a081..935074462966 100644 --- a/src/pages/ReportDetailsPage.js +++ b/src/pages/ReportDetailsPage.js @@ -163,7 +163,7 @@ function ReportDetailsPage(props) { return ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participants, props.personalDetails), hasMultipleParticipants); }, [participants, props.personalDetails]); - const icons = useMemo(() => ReportUtils.getIcons(props.report, props.personalDetails, props.policies), [props.report, props.personalDetails, props.policies]); + const icons = useMemo(() => ReportUtils.getIcons(props.report, null, '', -1, props.policies, props.personalDetails), [props.report, props.personalDetails, props.policies]); const chatRoomSubtitleText = chatRoomSubtitle ? ( {}, + disabled: false, }; export {propTypes, defaultProps}; diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.js b/src/pages/home/report/ReportActionCompose/SuggestionMention.js index 6d85a0c349de..513e161dda41 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.js +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.js @@ -1,16 +1,15 @@ import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; -import Onyx from 'react-native-onyx'; import _ from 'underscore'; import * as Expensicons from '@components/Icon/Expensicons'; import MentionSuggestions from '@components/MentionSuggestions'; import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; +import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as SuggestionsUtils from '@libs/SuggestionUtils'; import * as UserUtils from '@libs/UserUtils'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import * as SuggestionProps from './suggestionProps'; /** @@ -38,20 +37,6 @@ const defaultProps = { forwardedRef: null, }; -/** - * We only need the personalDetails once because as long as the - * user is in the ReportScreen, these details won't be changing, - * hence we don't have to use it with `withOnyx`. - */ -let allPersonalDetails = {}; -// eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs -Onyx.connect({ - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - callback: (val) => { - allPersonalDetails = val; - }, -}); - function SuggestionMention({ value, setValue, @@ -65,7 +50,12 @@ function SuggestionMention({ measureParentContainer, isComposerFocused, }) { - const personalDetails = allPersonalDetails; + /** + * We only need the personalDetails once because as long as the + * user is in the ReportScreen, these details won't be changing, + * hence we don't have to use it with `withOnyx`. + */ + const personalDetails = PersonalDetailsUtils.getPersonalDetails(); const {translate} = useLocalize(); const previousValue = usePrevious(value); const [suggestionValues, setSuggestionValues] = useState(defaultSuggestionsValues); diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index fe150be8db90..6c056c1b2527 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -365,7 +365,7 @@ function ReportActionItem(props) { ); } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTQUEUED) { - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(null, [props.report.ownerAccountID, 'displayName']); + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault([props.report.ownerAccountID, 'displayName']); const paymentType = lodashGet(props.action, 'originalMessage.paymentType', ''); const isSubmitterOfUnsettledReport = ReportUtils.isCurrentUserSubmitter(props.report.reportID) && !ReportUtils.isSettled(props.report.reportID); diff --git a/src/pages/home/report/ReportFooter.js b/src/pages/home/report/ReportFooter.js index b407605a1c5d..d9490e17a688 100644 --- a/src/pages/home/report/ReportFooter.js +++ b/src/pages/home/report/ReportFooter.js @@ -137,6 +137,8 @@ export default compose( prevProps.isEmptyChat === nextProps.isEmptyChat && prevProps.lastReportAction === nextProps.lastReportAction && prevProps.shouldShowComposeInput === nextProps.shouldShowComposeInput && + prevProps.windowWidth === nextProps.windowWidth && + prevProps.isSmallScreenWidth === nextProps.isSmallScreenWidth && prevProps.isReportReadyForDisplay === nextProps.isReportReadyForDisplay, ), ); From 83d823ef9a37a5214319e54a41733d61061928ec Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 16 Nov 2023 12:16:32 +0500 Subject: [PATCH 019/142] fix: add default value --- src/libs/OptionsListUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index fc306a1f31bf..37b2e0add4ab 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -514,7 +514,7 @@ function createOption(accountIDs, personalDetails, report, reportActions = {}, { (lastReportActions[report.reportID] && lastReportActions[report.reportID].originalMessage && lastReportActions[report.reportID].originalMessage.reason) || CONST.REPORT.ARCHIVE_REASON.DEFAULT; lastMessageText = Localize.translate(preferredLocale, `reportArchiveReasons.${archiveReason}`, { - displayName: archiveReason.displayName || PersonalDetailsUtils.getDisplayNameOrDefault('displayName', lastActorDetails), + displayName: archiveReason.displayName || PersonalDetailsUtils.getDisplayNameOrDefault('displayName', '', lastActorDetails), policyName: ReportUtils.getPolicyName(report), }); } From fbf272901234c71597116a1d2a821f29647c2b71 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 30 Nov 2023 11:41:35 +0500 Subject: [PATCH 020/142] fix: appropriate arguments to getDisplayNameOrDefault --- src/pages/ReportParticipantsPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReportParticipantsPage.js b/src/pages/ReportParticipantsPage.js index ceaa53a41a6b..78350fa2a361 100755 --- a/src/pages/ReportParticipantsPage.js +++ b/src/pages/ReportParticipantsPage.js @@ -60,7 +60,7 @@ const getAllParticipants = (report, personalDetails, translate) => .map((accountID, index) => { const userPersonalDetail = lodashGet(personalDetails, accountID, {displayName: personalDetails.displayName || translate('common.hidden'), avatar: ''}); const userLogin = LocalePhoneNumber.formatPhoneNumber(userPersonalDetail.login || '') || translate('common.hidden'); - const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(userPersonalDetail, 'displayName'); + const displayName = PersonalDetailsUtils.getDisplayNameOrDefault('displayName', '', userPersonalDetail); return { alternateText: userLogin, From 1d115c0fad01f0a47c453f602ddd88345b4e8fd6 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 30 Nov 2023 12:29:55 +0500 Subject: [PATCH 021/142] fix: workspace unavailable avatar --- src/pages/ReportDetailsPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReportDetailsPage.js b/src/pages/ReportDetailsPage.js index 9dcc01258853..c40290f4c3ef 100644 --- a/src/pages/ReportDetailsPage.js +++ b/src/pages/ReportDetailsPage.js @@ -254,7 +254,7 @@ export default compose( key: ONYXKEYS.PERSONAL_DETAILS_LIST, }, policies: { - key: ONYXKEYS.COLLECTION.POLICY, + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, }, }), )(ReportDetailsPage); From 3b9817851eb2c8a30fea1a43f1734f6464016f41 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 30 Nov 2023 12:50:39 +0500 Subject: [PATCH 022/142] fix: pass policy instead of policies to getIcons --- src/pages/ReportDetailsPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/ReportDetailsPage.js b/src/pages/ReportDetailsPage.js index c40290f4c3ef..54cee38c31e8 100644 --- a/src/pages/ReportDetailsPage.js +++ b/src/pages/ReportDetailsPage.js @@ -150,7 +150,7 @@ function ReportDetailsPage(props) { return ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participants, props.personalDetails), hasMultipleParticipants); }, [participants, props.personalDetails]); - const icons = useMemo(() => ReportUtils.getIcons(props.report, null, '', -1, props.policies, props.personalDetails), [props.report, props.personalDetails, props.policies]); + const icons = useMemo(() => ReportUtils.getIcons(props.report, null, '', -1, policy, props.personalDetails), [props.report, props.personalDetails, policy]); const chatRoomSubtitleText = chatRoomSubtitle ? ( `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, + key: ONYXKEYS.COLLECTION.POLICY, }, }), )(ReportDetailsPage); From 33703b37cde9ff69205766ed74d69411d1127740 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 1 Dec 2023 13:00:27 +0500 Subject: [PATCH 023/142] test: fix failing caledarpickertest due to an edge case of last month in the calendar --- tests/unit/CalendarPickerTest.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/unit/CalendarPickerTest.js b/tests/unit/CalendarPickerTest.js index 92092acc4cd7..d82efbf65afd 100644 --- a/tests/unit/CalendarPickerTest.js +++ b/tests/unit/CalendarPickerTest.js @@ -64,7 +64,20 @@ describe('CalendarPicker', () => { fireEvent.press(getByTestId('next-month-arrow')); - const nextMonth = new Date().getMonth() + 1; + /** + * Handle edge case for last month "Decemeber" as + * adding a 1 to it will produce an invalid month + * 12 since the last index supported is 11, so we + * set it to 0 to represent January. + */ + let nextMonth = new Date().getMonth(); + + if (nextMonth !== 11) { + nextMonth += 1; + } else { + nextMonth = 0; + } + expect(getByText(monthNames[nextMonth])).toBeTruthy(); }); From ad2549a25ced3e7f228d33519c48ddf1f8579d6f Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 4 Dec 2023 15:52:49 +0500 Subject: [PATCH 024/142] fix: personal details not updating --- src/components/withCurrentUserPersonalDetails.tsx | 6 ++++-- src/pages/home/HeaderView.js | 12 ++++++++++-- src/pages/home/report/ReportActionItemSingle.js | 14 +++++++------- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/components/withCurrentUserPersonalDetails.tsx b/src/components/withCurrentUserPersonalDetails.tsx index ede90f6a07c7..a97067c32c72 100644 --- a/src/components/withCurrentUserPersonalDetails.tsx +++ b/src/components/withCurrentUserPersonalDetails.tsx @@ -1,10 +1,11 @@ import React, {ComponentType, ForwardedRef, RefAttributes, useMemo} from 'react'; import {OnyxEntry, withOnyx} from 'react-native-onyx'; import getComponentDisplayName from '@libs/getComponentDisplayName'; -import {getPersonalDetailsByAccountID} from '@libs/PersonalDetailsUtils'; import personalDetailsPropType from '@pages/personalDetailsPropType'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetails, Session} from '@src/types/onyx'; +import {usePersonalDetails} from './OnyxProvider'; type CurrentUserPersonalDetails = PersonalDetails | Record; @@ -32,8 +33,9 @@ export default function >, ): ComponentType & RefAttributes, keyof OnyxProps>> { function WithCurrentUserPersonalDetails(props: Omit, ref: ForwardedRef) { + const personalDetails = usePersonalDetails() ?? CONST.EMPTY_OBJECT; const accountID = props.session?.accountID ?? 0; - const accountPersonalDetails: PersonalDetails = getPersonalDetailsByAccountID(accountID); + const accountPersonalDetails = personalDetails?.[accountID]; const currentUserPersonalDetails: CurrentUserPersonalDetails = useMemo( () => (accountPersonalDetails ? {...accountPersonalDetails, accountID} : {}), [accountPersonalDetails, accountID], diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index bfef2e6b50ca..2b6fa35508db 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -11,6 +11,7 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import MultipleAvatars from '@components/MultipleAvatars'; import ParentNavigationSubtitle from '@components/ParentNavigationSubtitle'; +import participantPropTypes from '@components/participantPropTypes'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import ReportHeaderSkeletonView from '@components/ReportHeaderSkeletonView'; import SubscriptAvatar from '@components/SubscriptAvatar'; @@ -43,6 +44,9 @@ const propTypes = { /** The report currently being looked at */ report: reportPropTypes, + /** Personal details of all the users */ + personalDetails: PropTypes.objectOf(participantPropTypes), + /** Onyx Props */ parentReport: reportPropTypes, @@ -65,6 +69,7 @@ const propTypes = { }; const defaultProps = { + personalDetails: {}, report: null, guideCalendarLink: null, parentReport: {}, @@ -80,7 +85,7 @@ function HeaderView(props) { const theme = useTheme(); const styles = useThemeStyles(); const participants = lodashGet(props.report, 'participantAccountIDs', []); - const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants); + const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, props.personalDetails); const isMultipleParticipant = participants.length > 1; const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(participantPersonalDetails, isMultipleParticipant); const isChatThread = ReportUtils.isChatThread(props.report); @@ -178,7 +183,7 @@ function HeaderView(props) { const shouldShowSubscript = ReportUtils.shouldReportShowSubscript(props.report); const defaultSubscriptSize = ReportUtils.isExpenseRequest(props.report) ? CONST.AVATAR_SIZE.SMALL_NORMAL : CONST.AVATAR_SIZE.DEFAULT; - const icons = ReportUtils.getIcons(reportHeaderData); + const icons = ReportUtils.getIcons(reportHeaderData, props.personalDetails); const brickRoadIndicator = ReportUtils.hasReportNameError(props.report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; const shouldShowBorderBottom = !isTaskReport || !isSmallScreenWidth; const shouldDisableDetailPage = ReportUtils.shouldDisableDetailPage(props.report); @@ -306,5 +311,8 @@ export default memo( key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, selector: (policy) => _.pick(policy, ['name', 'avatar', 'pendingAction']), }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, })(HeaderView), ); diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index bc9e7eef1b40..1a00d50197d4 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -6,6 +6,7 @@ import _ from 'underscore'; import Avatar from '@components/Avatar'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import {usePersonalDetails} from '@components/OnyxProvider'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import SubscriptAvatar from '@components/SubscriptAvatar'; import Text from '@components/Text'; @@ -15,7 +16,6 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import ControlSelection from '@libs/ControlSelection'; import DateUtils from '@libs/DateUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as UserUtils from '@libs/UserUtils'; import reportPropTypes from '@pages/reportPropTypes'; @@ -81,24 +81,24 @@ const showWorkspaceDetails = (reportID) => { function ReportActionItemSingle(props) { const styles = useThemeStyles(); const theme = useTheme(); + const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const actorAccountID = props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && props.iouReport ? props.iouReport.managerID : props.action.actorAccountID; - const personalDetails = PersonalDetailsUtils.getPersonalDetailsByAccountID(actorAccountID); let displayName = ReportUtils.getDisplayNameForParticipant(actorAccountID); - const {avatar, login, pendingFields, status, fallbackIcon} = personalDetails || {}; + const {avatar, login, pendingFields, status, fallbackIcon} = personalDetails[actorAccountID] || {}; let actorHint = (login || displayName || '').replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, ''); const displayAllActors = useMemo(() => props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && props.iouReport, [props.action.actionName, props.iouReport]); const isWorkspaceActor = ReportUtils.isPolicyExpenseChat(props.report) && (!actorAccountID || displayAllActors); let avatarSource = UserUtils.getAvatar(avatar, actorAccountID); - const delegateDetails = PersonalDetailsUtils.getPersonalDetailsByAccountID(props.action.delegateAccountID); if (isWorkspaceActor) { displayName = ReportUtils.getPolicyName(props.report); actorHint = displayName; avatarSource = ReportUtils.getWorkspaceAvatar(props.report); - } else if (props.action.delegateAccountID && delegateDetails) { + } else if (props.action.delegateAccountID && personalDetails[props.action.delegateAccountID]) { // We replace the actor's email, name, and avatar with the Copilot manually for now. And only if we have their // details. This will be improved upon when the Copilot feature is implemented. + const delegateDetails = personalDetails[props.action.delegateAccountID]; const delegateDisplayName = delegateDetails.displayName; actorHint = `${delegateDisplayName} (${props.translate('reportAction.asCopilot')} ${displayName})`; displayName = actorHint; @@ -111,8 +111,8 @@ function ReportActionItemSingle(props) { if (displayAllActors) { // The ownerAccountID and actorAccountID can be the same if the a user requests money back from the IOU's original creator, in that case we need to use managerID to avoid displaying the same user twice const secondaryAccountId = props.iouReport.ownerAccountID === actorAccountID ? props.iouReport.managerID : props.iouReport.ownerAccountID; - const secondaryUserDetails = PersonalDetailsUtils.getPersonalDetailsByAccountID(secondaryAccountId) || {}; - const secondaryDisplayName = lodashGet(secondaryUserDetails, 'displayName', ''); + const secondaryUserDetails = personalDetails[secondaryAccountId] || {}; + const secondaryDisplayName = ReportUtils.getDisplayNameForParticipant(secondaryAccountId); displayName = `${primaryDisplayName} & ${secondaryDisplayName}`; secondaryAvatar = { source: UserUtils.getAvatar(secondaryUserDetails.avatar, secondaryAccountId), From a99d653bddbc094cac7f03f4eb5dc803898c053b Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 6 Dec 2023 14:46:15 +0500 Subject: [PATCH 025/142] fix: personalDetails not updating --- src/components/LHNOptionsList/OptionRowLHNData.js | 6 ++++-- src/components/MoneyReportHeader.js | 3 +++ src/components/MoneyRequestHeader.js | 3 +++ src/libs/SidebarUtils.ts | 10 ++-------- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index 77de5b7b87fd..b6c3a6e7ed00 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -2,6 +2,7 @@ import {deepEqual} from 'fast-equals'; import PropTypes from 'prop-types'; import React, {useEffect, useMemo, useRef} from 'react'; import _ from 'underscore'; +import {usePersonalDetails} from '@components/OnyxProvider'; import transactionPropTypes from '@components/transactionPropTypes'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import SidebarUtils from '@libs/SidebarUtils'; @@ -58,6 +59,7 @@ const defaultProps = { * re-render if the data really changed. */ function OptionRowLHNData({isFocused, fullReport, reportActions, preferredLocale, comment, policy, receiptTransactions, parentReportAction, transaction, ...propsToForward}) { + const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const reportID = propsToForward.reportID; const optionItemRef = useRef(); @@ -70,7 +72,7 @@ function OptionRowLHNData({isFocused, fullReport, reportActions, preferredLocale const optionItem = useMemo(() => { // Note: ideally we'd have this as a dependent selector in onyx! - const item = SidebarUtils.getOptionData(fullReport, reportActions, preferredLocale, policy, parentReportAction); + const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale, policy, parentReportAction); if (deepEqual(item, optionItemRef.current)) { return optionItemRef.current; } @@ -79,7 +81,7 @@ function OptionRowLHNData({isFocused, fullReport, reportActions, preferredLocale // Listen parentReportAction to update title of thread report when parentReportAction changed // Listen to transaction to update title of transaction report when transaction changed // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fullReport, linkedTransaction, reportActions, preferredLocale, policy, parentReportAction, transaction]); + }, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transaction]); useEffect(() => { if (!optionItem || optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { diff --git a/src/components/MoneyReportHeader.js b/src/components/MoneyReportHeader.js index 909c52af8912..9cb9d392004d 100644 --- a/src/components/MoneyReportHeader.js +++ b/src/components/MoneyReportHeader.js @@ -20,6 +20,7 @@ import ROUTES from '@src/ROUTES'; import Button from './Button'; import HeaderWithBackButton from './HeaderWithBackButton'; import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar'; +import {usePersonalDetails} from './OnyxProvider'; import SettlementButton from './SettlementButton'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; @@ -64,6 +65,7 @@ const defaultProps = { }; function MoneyReportHeader({session, policy, chatReport, nextStep, report: moneyRequestReport, isSmallScreenWidth}) { + const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const styles = useThemeStyles(); const {translate} = useLocalize(); const reimbursableTotal = ReportUtils.getMoneyRequestReimbursableTotal(moneyRequestReport); @@ -100,6 +102,7 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money shouldShowPinButton={false} report={moneyRequestReport} policy={policy} + personalDetails={personalDetails} shouldShowBackButton={isSmallScreenWidth} onBackButtonPress={() => Navigation.goBack(ROUTES.HOME, false, true)} // Shows border if no buttons or next steps are showing below the header diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 3935cf940cfc..db8ab3c3a3eb 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -22,6 +22,7 @@ import ConfirmModal from './ConfirmModal'; import HeaderWithBackButton from './HeaderWithBackButton'; import * as Expensicons from './Icon/Expensicons'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; +import {usePersonalDetails} from './OnyxProvider'; import transactionPropTypes from './transactionPropTypes'; const propTypes = { @@ -62,6 +63,7 @@ const defaultProps = { }; function MoneyRequestHeader({session, parentReport, report, parentReportAction, transaction, policy}) { + const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const styles = useThemeStyles(); const {translate} = useLocalize(); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); @@ -121,6 +123,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, ownerAccountID: lodashGet(parentReport, 'ownerAccountID', null), }} policy={policy} + personalDetails={personalDetails} shouldShowBackButton={isSmallScreenWidth} onBackButtonPress={() => Navigation.goBack(ROUTES.HOME, false, true)} /> diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 50c930d6050e..9872187fa050 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1,6 +1,6 @@ /* eslint-disable rulesdir/prefer-underscore-method */ import Str from 'expensify-common/lib/str'; -import Onyx, {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import Onyx, {OnyxCollection} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -62,12 +62,6 @@ Onyx.connect({ }, }); -let allPersonalDetails: OnyxEntry>; -Onyx.connect({ - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - callback: (val) => (allPersonalDetails = val), -}); - let resolveSidebarIsReadyPromise: (args?: unknown[]) => void; let sidebarIsReadyPromise = new Promise((resolve) => { @@ -239,6 +233,7 @@ type ActorDetails = { function getOptionData( report: Report, reportActions: Record, + personalDetails: Record, preferredLocale: ValueOf, policy: Policy, parentReportAction: ReportAction, @@ -246,7 +241,6 @@ function getOptionData( // When a user signs out, Onyx is cleared. Due to the lazy rendering with a virtual list, it's possible for // this method to be called after the Onyx data has been cleared out. In that case, it's fine to do // a null check here and return early. - const personalDetails = allPersonalDetails; if (!report || !personalDetails) { return; } From b1257f53c4c2cb4df10069874992f3b61333dabc Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 6 Dec 2023 15:08:01 +0500 Subject: [PATCH 026/142] fix: ts issues --- tests/perf-test/SidebarUtils.perf-test.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index 595f6ad1bbe3..7f9957232cfb 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -3,10 +3,12 @@ import {measureFunction} from 'reassure'; import SidebarUtils from '@libs/SidebarUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import {PersonalDetails} from '@src/types/onyx'; import Policy from '@src/types/onyx/Policy'; import Report from '@src/types/onyx/Report'; import ReportAction, {ReportActions} from '@src/types/onyx/ReportAction'; import createCollection from '../utils/collections/createCollection'; +import createPersonalDetails from '../utils/collections/personalDetails'; import createRandomPolicy from '../utils/collections/policies'; import createRandomReportAction from '../utils/collections/reportActions'; import createRandomReport from '../utils/collections/reports'; @@ -36,6 +38,11 @@ const reportActions = createCollection( (index) => createRandomReportAction(index), ); +const personalDetails = createCollection( + (item) => item.accountID, + (index) => createPersonalDetails(index), +); + const mockedResponseMap = getMockedReports(5000) as Record<`${typeof ONYXKEYS.COLLECTION.REPORT}`, Report>; const runs = CONST.PERFORMANCE_TESTS.RUNS; @@ -50,7 +57,7 @@ test('getOptionData on 5k reports', async () => { }); await waitForBatchedUpdates(); - await measureFunction(() => SidebarUtils.getOptionData(report, reportActions, preferredLocale, policy, parentReportAction), {runs}); + await measureFunction(() => SidebarUtils.getOptionData(report, reportActions, personalDetails, preferredLocale, policy, parentReportAction), {runs}); }); test('getOrderedReportIDs on 5k reports', async () => { From 21749f1dc0b3921b634a9af5f4688308bc16d9c9 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 7 Dec 2023 15:59:59 +0500 Subject: [PATCH 027/142] fix: personaldetails not updating --- src/components/ArchivedReportFooter.tsx | 16 ++++++--- src/components/AvatarWithDisplayName.tsx | 13 ++++++-- src/libs/OptionsListUtils.js | 16 +++------ src/libs/PersonalDetailsUtils.js | 33 ++----------------- src/libs/ReportUtils.ts | 10 +++--- src/libs/SidebarUtils.ts | 4 +-- src/libs/actions/Task.js | 2 +- src/pages/ReportDetailsPage.js | 2 +- src/pages/ReportParticipantsPage.js | 2 +- .../ReportActionCompose.js | 11 ++++--- .../ReportActionCompose/SuggestionMention.js | 9 ++--- src/pages/home/report/ReportActionItem.js | 11 ++++--- src/pages/home/report/ReportActionsList.js | 11 +++++-- 13 files changed, 60 insertions(+), 80 deletions(-) diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx index 4f6de9a1db1b..74901843a05c 100644 --- a/src/components/ArchivedReportFooter.tsx +++ b/src/components/ArchivedReportFooter.tsx @@ -8,12 +8,15 @@ import * as ReportUtils from '@libs/ReportUtils'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Report, ReportAction} from '@src/types/onyx'; +import type {PersonalDetails, Report, ReportAction} from '@src/types/onyx'; import Banner from './Banner'; type ArchivedReportFooterOnyxProps = { /** The reason this report was archived */ reportClosedAction: OnyxEntry; + + /** Personal details of all users */ + personalDetails: OnyxEntry>; }; type ArchivedReportFooterProps = ArchivedReportFooterOnyxProps & { @@ -21,20 +24,20 @@ type ArchivedReportFooterProps = ArchivedReportFooterOnyxProps & { report: Report; }; -function ArchivedReportFooter({report, reportClosedAction}: ArchivedReportFooterProps) { +function ArchivedReportFooter({report, reportClosedAction, personalDetails = CONST.EMPTY_OBJECT}: ArchivedReportFooterProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const originalMessage = reportClosedAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED ? reportClosedAction.originalMessage : null; const archiveReason = originalMessage?.reason ?? CONST.REPORT.ARCHIVE_REASON.DEFAULT; - let displayName = PersonalDetailsUtils.getDisplayNameOrDefault([report.ownerAccountID, 'displayName']); + let displayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, [report.ownerAccountID, 'displayName']); let oldDisplayName: string | undefined; if (archiveReason === CONST.REPORT.ARCHIVE_REASON.ACCOUNT_MERGED) { const newAccountID = originalMessage?.newAccountID; const oldAccountID = originalMessage?.oldAccountID; - displayName = PersonalDetailsUtils.getDisplayNameOrDefault([newAccountID, 'displayName']); - oldDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault([oldAccountID, 'displayName']); + displayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, [newAccountID, 'displayName']); + oldDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, [oldAccountID, 'displayName']); } const shouldRenderHTML = archiveReason !== CONST.REPORT.ARCHIVE_REASON.DEFAULT; @@ -68,6 +71,9 @@ function ArchivedReportFooter({report, reportClosedAction}: ArchivedReportFooter ArchivedReportFooter.displayName = 'ArchivedReportFooter'; export default withOnyx({ + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, reportClosedAction: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, canEvict: false, diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index 76b878988e08..6ffc7bdadd8b 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -11,7 +11,7 @@ import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {Policy, Report, ReportActions} from '@src/types/onyx'; +import {PersonalDetails, Policy, Report, ReportActions} from '@src/types/onyx'; import DisplayNames from './DisplayNames'; import MultipleAvatars from './MultipleAvatars'; import ParentNavigationSubtitle from './ParentNavigationSubtitle'; @@ -22,6 +22,9 @@ import Text from './Text'; type AvatarWithDisplayNamePropsWithOnyx = { /** All of the actions of the report */ parentReportActions: OnyxEntry; + + /** Personal details of all users */ + personalDetails: OnyxEntry>; }; type AvatarWithDisplayNameProps = AvatarWithDisplayNamePropsWithOnyx & { @@ -48,6 +51,7 @@ function AvatarWithDisplayName({ isAnonymous = false, size = CONST.AVATAR_SIZE.DEFAULT, shouldEnableDetailPageNavigation = false, + personalDetails = CONST.EMPTY_OBJECT, }: AvatarWithDisplayNameProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -55,8 +59,8 @@ function AvatarWithDisplayName({ const subtitle = ReportUtils.getChatRoomSubtitle(report); const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(report); const isMoneyRequestOrReport = ReportUtils.isMoneyRequestReport(report) || ReportUtils.isMoneyRequest(report); - const icons = ReportUtils.getIcons(report, null, '', -1, policy); - const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(report?.ownerAccountID ? [report.ownerAccountID] : []); + const icons = ReportUtils.getIcons(report, personalDetails, null, '', -1, policy); + const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(report?.ownerAccountID ? [report.ownerAccountID] : [], personalDetails); const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(Object.values(ownerPersonalDetails), false); const shouldShowSubscriptAvatar = ReportUtils.shouldReportShowSubscript(report); const isExpenseRequest = ReportUtils.isExpenseRequest(report); @@ -175,4 +179,7 @@ export default withOnyx `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`, canEvict: false, }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, })(AvatarWithDisplayName); diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 05214f2cd1b7..4678ce395f76 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -152,12 +152,11 @@ function getAvatarsForAccountIDs(accountIDs, personalDetails, defaultValues = {} * Returns the personal details for an array of accountIDs * * @param {Array} accountIDs - * @param {Object | null} passedPersonalDetails + * @param {Object | null} personalDetails * @returns {Object} – keys of the object are emails, values are PersonalDetails objects. */ -function getPersonalDetailsForAccountIDs(accountIDs, passedPersonalDetails = null) { +function getPersonalDetailsForAccountIDs(accountIDs, personalDetails) { const personalDetailsForAccountIDs = {}; - const personalDetails = passedPersonalDetails || allPersonalDetails; if (!personalDetails) { return personalDetailsForAccountIDs; } @@ -516,7 +515,7 @@ function createOption(accountIDs, personalDetails, report, reportActions = {}, { (lastReportActions[report.reportID] && lastReportActions[report.reportID].originalMessage && lastReportActions[report.reportID].originalMessage.reason) || CONST.REPORT.ARCHIVE_REASON.DEFAULT; lastMessageText = Localize.translate(preferredLocale, `reportArchiveReasons.${archiveReason}`, { - displayName: archiveReason.displayName || PersonalDetailsUtils.getDisplayNameOrDefault('displayName', '', lastActorDetails), + displayName: archiveReason.displayName || PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails, 'displayName'), policyName: ReportUtils.getPolicyName(report), }); } @@ -548,14 +547,7 @@ function createOption(accountIDs, personalDetails, report, reportActions = {}, { result.text = reportName; result.searchText = getSearchText(report, reportName, personalDetailList, result.isChatRoom || result.isPolicyExpenseChat, result.isThread); - result.icons = ReportUtils.getIcons( - report, - UserUtils.getAvatar(personalDetail.avatar, personalDetail.accountID), - personalDetail.login, - personalDetail.accountID, - undefined, - personalDetails, - ); + result.icons = ReportUtils.getIcons(report, personalDetails, UserUtils.getAvatar(personalDetail.avatar, personalDetail.accountID), personalDetail.login, personalDetail.accountID); result.subtitle = subtitle; return result; diff --git a/src/libs/PersonalDetailsUtils.js b/src/libs/PersonalDetailsUtils.js index 3205e01c79c2..838e1671c873 100644 --- a/src/libs/PersonalDetailsUtils.js +++ b/src/libs/PersonalDetailsUtils.js @@ -17,12 +17,12 @@ Onyx.connect({ }); /** + * @param {Object | Null} passedPersonalDetails * @param {Array | String} pathToDisplayName * @param {String} [defaultValue] optional default display name value - * @param {Object | Null} passedPersonalDetails optional default personal details object * @returns {String} */ -function getDisplayNameOrDefault(pathToDisplayName, defaultValue = '', passedPersonalDetails = allPersonalDetails) { +function getDisplayNameOrDefault(passedPersonalDetails, pathToDisplayName, defaultValue = '') { const displayName = lodashGet(passedPersonalDetails, pathToDisplayName); return displayName || defaultValue || Localize.translateLocal('common.hidden'); @@ -197,24 +197,6 @@ function getFormattedAddress(privatePersonalDetails) { return formattedAddress.trim().replace(/,$/, ''); } -/** - * Get personal detail for an accountID - * @param {Number} accountID - * @returns {PersonalDetail} personal detail object - */ -function getPersonalDetailsByAccountID(accountID) { - return allPersonalDetails ? allPersonalDetails[accountID] : {}; -} - -/** - * Get whispered personal details for array of accountIDs - * @param {Array} whisperedToAccountIDs - * @returns {PersonalDetails} personal details - */ -function getWhisperedToPersonalDetails(whisperedToAccountIDs) { - return _.filter(allPersonalDetails, (details) => _.includes(whisperedToAccountIDs, details.accountID)); -} - /** * Whether personal details is empty * @returns {Boolean} true if personal details is empty @@ -223,14 +205,6 @@ function isPersonalDetailsEmpty() { return !personalDetails.length; } -/** - * Get personal details object - * @returns {PersonalDetail} personal detail object - */ -function getPersonalDetails() { - return allPersonalDetails || {}; -} - export { getDisplayNameOrDefault, getPersonalDetailsByIDs, @@ -238,10 +212,7 @@ export { getLoginsByAccountIDs, getNewPersonalDetailsOnyxData, getFormattedAddress, - getPersonalDetailsByAccountID, - getWhisperedToPersonalDetails, isPersonalDetailsEmpty, - getPersonalDetails, getFormattedStreet, getStreetLines, }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1f84075c81c8..9eb66f898d9a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1130,10 +1130,10 @@ function getReportRecipientAccountIDs(report: OnyxEntry, currentLoginAcc /** * Whether the time row should be shown for a report. */ -function canShowReportRecipientLocalTime(report: OnyxEntry, accountID: number): boolean { +function canShowReportRecipientLocalTime(personalDetails: OnyxCollection, report: OnyxEntry, accountID: number): boolean { const reportRecipientAccountIDs = getReportRecipientAccountIDs(report, accountID); const hasMultipleParticipants = reportRecipientAccountIDs.length > 1; - const reportRecipient = allPersonalDetails?.[reportRecipientAccountIDs[0]]; + const reportRecipient = personalDetails?.[reportRecipientAccountIDs[0]]; const reportRecipientTimezone = reportRecipient?.timezone ?? CONST.DEFAULT_TIME_ZONE; const isReportParticipantValidated = reportRecipient?.validated ?? false; return Boolean(!hasMultipleParticipants && !isChatRoom(report) && !isPolicyExpenseChat(report) && reportRecipient && reportRecipientTimezone?.selected && isReportParticipantValidated); @@ -1178,10 +1178,9 @@ function getWorkspaceAvatar(report: OnyxEntry): UserUtils.AvatarSource { * Returns the appropriate icons for the given chat report using the stored personalDetails. * The Avatar sources can be URLs or Icon components according to the chat type. */ -function getIconsForParticipants(participants: number[], passedPersonalDetails: OnyxCollection): Icon[] { +function getIconsForParticipants(participants: number[], personalDetails: OnyxCollection): Icon[] { const participantDetails: ParticipantDetails[] = []; const participantsList = participants || []; - const personalDetails = passedPersonalDetails ?? allPersonalDetails; for (const accountID of participantsList) { const avatarSource = UserUtils.getAvatar(personalDetails?.[accountID]?.avatar ?? '', accountID); @@ -1243,11 +1242,11 @@ function getWorkspaceIcon(report: OnyxEntry, policy: OnyxEntry = */ function getIcons( report: OnyxEntry, + personalDetails: OnyxCollection = allPersonalDetails, defaultIcon: UserUtils.AvatarSource | null = null, defaultName = '', defaultAccountID = -1, policy: OnyxEntry = null, - passedPersonalDetails: OnyxCollection = allPersonalDetails, ): Icon[] { if (isEmptyObject(report)) { const fallbackIcon: Icon = { @@ -1258,7 +1257,6 @@ function getIcons( }; return [fallbackIcon]; } - const personalDetails = passedPersonalDetails; if (isExpenseRequest(report)) { const parentReportAction = ReportActionsUtils.getParentReportAction(report); const workspaceIcon = getWorkspaceIcon(report, policy); diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 9872187fa050..bace29e06d28 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -348,7 +348,7 @@ function getOptionData( case CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED: { lastMessageText = Localize.translate(preferredLocale, `reportArchiveReasons.${archiveReason}`, { policyName: ReportUtils.getPolicyName(report, false, policy), - displayName: PersonalDetailsUtils.getDisplayNameOrDefault('displayName', '', lastActorDetails), + displayName: PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails, 'displayName'), }); break; } @@ -436,7 +436,7 @@ function getOptionData( result.subtitle = subtitle; result.participantsList = participantPersonalDetailList; - result.icons = ReportUtils.getIcons(report, UserUtils.getAvatar(personalDetail.avatar, personalDetail.accountID), '', -1, policy, personalDetails); + result.icons = ReportUtils.getIcons(report, personalDetails, UserUtils.getAvatar(personalDetail.avatar, personalDetail.accountID), '', -1, policy); result.searchText = OptionsListUtils.getSearchText(report, reportName, participantPersonalDetailList, result.isChatRoom || result.isPolicyExpenseChat, result.isThread); result.displayNamesWithTooltips = displayNamesWithTooltips; diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index b12231e26e8f..07a03ed2588a 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -698,7 +698,7 @@ function getShareDestination(reportID, reports, personalDetails) { subtitle = ReportUtils.getChatRoomSubtitle(report); } return { - icons: ReportUtils.getIcons(report, Expensicons.FallbackAvatar, '', -1, undefined, personalDetails), + icons: ReportUtils.getIcons(report, personalDetails, Expensicons.FallbackAvatar, '', -1), displayName: ReportUtils.getReportName(report), subtitle, displayNamesWithTooltips, diff --git a/src/pages/ReportDetailsPage.js b/src/pages/ReportDetailsPage.js index 25dfaddd0e22..563638e7bc78 100644 --- a/src/pages/ReportDetailsPage.js +++ b/src/pages/ReportDetailsPage.js @@ -162,7 +162,7 @@ function ReportDetailsPage(props) { return ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participants, props.personalDetails), hasMultipleParticipants); }, [participants, props.personalDetails]); - const icons = useMemo(() => ReportUtils.getIcons(props.report, null, '', -1, policy, props.personalDetails), [props.report, props.personalDetails, policy]); + const icons = useMemo(() => ReportUtils.getIcons(props.report, props.personalDetails, null, '', -1, policy), [props.report, props.personalDetails, policy]); const chatRoomSubtitleText = chatRoomSubtitle ? ( .map((accountID, index) => { const userPersonalDetail = lodashGet(personalDetails, accountID, {displayName: personalDetails.displayName || translate('common.hidden'), avatar: ''}); const userLogin = LocalePhoneNumber.formatPhoneNumber(userPersonalDetail.login || '') || translate('common.hidden'); - const displayName = PersonalDetailsUtils.getDisplayNameOrDefault('displayName', '', userPersonalDetail); + const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(userPersonalDetail, 'displayName'); return { alternateText: userLogin, diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js index 6a53666a8cae..958fc91ae1e7 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js @@ -10,14 +10,13 @@ import AttachmentModal from '@components/AttachmentModal'; import ExceededCommentLength from '@components/ExceededCommentLength'; import OfflineIndicator from '@components/OfflineIndicator'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import {withNetwork} from '@components/OnyxProvider'; +import {usePersonalDetails, withNetwork} from '@components/OnyxProvider'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useWindowDimensions from '@hooks/useWindowDimensions'; import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus'; import compose from '@libs/compose'; import getModalState from '@libs/getModalState'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import willBlurTextInputOnTapOutsideFunc from '@libs/willBlurTextInputOnTapOutside'; @@ -108,6 +107,7 @@ function ReportActionCompose({ const {translate} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); const actionButtonRef = useRef(null); + const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; /** * Updates the Highlight state of the composer @@ -144,7 +144,10 @@ function ReportActionCompose({ [currentUserPersonalDetails.accountID, report], ); - const shouldShowReportRecipientLocalTime = useMemo(() => ReportUtils.canShowReportRecipientLocalTime(report) && !isComposerFullSize, [report, isComposerFullSize]); + const shouldShowReportRecipientLocalTime = useMemo( + () => ReportUtils.canShowReportRecipientLocalTime(personalDetails, report, currentUserPersonalDetails.accountID) && !isComposerFullSize, + [personalDetails, report, currentUserPersonalDetails.accountID, isComposerFullSize], + ); const includesConcierge = useMemo(() => ReportUtils.chatIncludesConcierge({participantAccountIDs: report.participantAccountIDs}), [report.participantAccountIDs]); const userBlockedFromConcierge = useMemo(() => User.isBlockedFromConcierge(blockedFromConcierge), [blockedFromConcierge]); @@ -296,7 +299,7 @@ function ReportActionCompose({ ); const reportRecipientAcountIDs = ReportUtils.getReportRecipientAccountIDs(report, currentUserPersonalDetails.accountID); - const reportRecipient = PersonalDetailsUtils.getPersonalDetailsByAccountID(reportRecipientAcountIDs[0]); + const reportRecipient = personalDetails[reportRecipientAcountIDs[0]]; const shouldUseFocusedColor = !isBlockedFromConcierge && !disabled && isFocused; const hasReportRecipient = _.isObject(reportRecipient) && !_.isEmpty(reportRecipient); diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.js b/src/pages/home/report/ReportActionCompose/SuggestionMention.js index b4694e85d0ca..e55b96ad99f5 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.js +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.js @@ -3,10 +3,10 @@ import React, {useCallback, useEffect, useImperativeHandle, useRef, useState} fr import _ from 'underscore'; import * as Expensicons from '@components/Icon/Expensicons'; import MentionSuggestions from '@components/MentionSuggestions'; +import {usePersonalDetails} from '@components/OnyxProvider'; import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as SuggestionsUtils from '@libs/SuggestionUtils'; import * as UserUtils from '@libs/UserUtils'; import CONST from '@src/CONST'; @@ -50,12 +50,7 @@ function SuggestionMention({ measureParentContainer, isComposerFocused, }) { - /** - * We only need the personalDetails once because as long as the - * user is in the ReportScreen, these details won't be changing, - * hence we don't have to use it with `withOnyx`. - */ - const personalDetails = PersonalDetailsUtils.getPersonalDetails(); + const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const {translate, formatPhoneNumber} = useLocalize(); const previousValue = usePrevious(value); const [suggestionValues, setSuggestionValues] = useState(defaultSuggestionsValues); diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index fd1c5c508b32..c306d8ab159d 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -12,7 +12,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import InlineSystemMessage from '@components/InlineSystemMessage'; import KYCWall from '@components/KYCWall'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import {withBlockedFromConcierge, withNetwork, withReportActionsDrafts} from '@components/OnyxProvider'; +import {usePersonalDetails, withBlockedFromConcierge, withNetwork, withReportActionsDrafts} from '@components/OnyxProvider'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; import EmojiReactionsPropTypes from '@components/Reactions/EmojiReactionsPropTypes'; import ReportActionItemEmojiReactions from '@components/Reactions/ReportActionItemEmojiReactions'; @@ -137,6 +137,7 @@ const defaultProps = { function ReportActionItem(props) { const theme = useTheme(); const styles = useThemeStyles(); + const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(props.action.reportActionID)); const [isHidden, setIsHidden] = useState(false); @@ -369,7 +370,7 @@ function ReportActionItem(props) { ); } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTQUEUED) { - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault([props.report.ownerAccountID, 'displayName']); + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, [props.report.ownerAccountID, 'displayName']); const paymentType = lodashGet(props.action, 'originalMessage.paymentType', ''); const isSubmitterOfUnsettledReport = ReportUtils.isCurrentUserSubmitter(props.report.reportID) && !ReportUtils.isSettled(props.report.reportID); @@ -417,7 +418,7 @@ function ReportActionItem(props) { ); } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED) { - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(PersonalDetailsUtils.getPersonalDetails(), [props.report.ownerAccountID, 'displayName']); + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, [props.report.ownerAccountID, 'displayName']); const amount = CurrencyUtils.convertToDisplayString(props.report.total, props.report.currency); children = ; @@ -524,7 +525,7 @@ function ReportActionItem(props) { numberOfReplies={numberOfThreadReplies} mostRecentReply={`${props.action.childLastVisibleActionCreated}`} isHovered={hovered} - icons={ReportUtils.getIconsForParticipants(oldestFourAccountIDs)} + icons={ReportUtils.getIconsForParticipants(oldestFourAccountIDs, personalDetails)} onSecondaryInteraction={showPopover} /> @@ -663,7 +664,7 @@ function ReportActionItem(props) { const isWhisper = whisperedToAccountIDs.length > 0; const isMultipleParticipant = whisperedToAccountIDs.length > 1; const isWhisperOnlyVisibleByUser = isWhisper && ReportUtils.isCurrentUserTheOnlyParticipant(whisperedToAccountIDs); - const whisperedToPersonalDetails = isWhisper ? PersonalDetailsUtils.getWhisperedToPersonalDetails(whisperedToAccountIDs) : []; + const whisperedToPersonalDetails = isWhisper ? _.filter(personalDetails, (details) => _.includes(whisperedToAccountIDs, details.accountID)) : []; const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; return ( [styles.chatContentScrollView, isLoadingNewerReportActions ? styles.chatContentScrollViewWithHeaderLoader : {}], @@ -475,4 +482,4 @@ ReportActionsList.propTypes = propTypes; ReportActionsList.defaultProps = defaultProps; ReportActionsList.displayName = 'ReportActionsList'; -export default withWindowDimensions(ReportActionsList); +export default compose(withWindowDimensions, withCurrentUserPersonalDetails)(ReportActionsList); From 411b92ce8118d70a1e2602f500ca1bc8fb41bb69 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 7 Dec 2023 16:41:56 +0500 Subject: [PATCH 028/142] fix: personalDetails not updating in AnonymousReportFooter --- src/components/AnonymousReportFooter.tsx | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/AnonymousReportFooter.tsx b/src/components/AnonymousReportFooter.tsx index b706bd9e8747..0414c67a094a 100644 --- a/src/components/AnonymousReportFooter.tsx +++ b/src/components/AnonymousReportFooter.tsx @@ -1,15 +1,22 @@ import React from 'react'; import {Text, View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; import {OnyxEntry} from 'react-native-onyx/lib/types'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@styles/useThemeStyles'; import * as Session from '@userActions/Session'; -import {Report} from '@src/types/onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import {Policy, Report} from '@src/types/onyx'; import AvatarWithDisplayName from './AvatarWithDisplayName'; import Button from './Button'; import ExpensifyWordmark from './ExpensifyWordmark'; -type AnonymousReportFooterProps = { +type AnonymousReportFooterPropsWithOnyx = { + /** The policy which the user has access to and which the report is tied to */ + policy: OnyxEntry; +}; + +type AnonymousReportFooterProps = AnonymousReportFooterPropsWithOnyx & { /** The report currently being looked at */ report: OnyxEntry; @@ -17,7 +24,7 @@ type AnonymousReportFooterProps = { isSmallSizeLayout?: boolean; }; -function AnonymousReportFooter({isSmallSizeLayout = false, report}: AnonymousReportFooterProps) { +function AnonymousReportFooter({isSmallSizeLayout = false, report, policy = {}}: AnonymousReportFooterProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -28,6 +35,7 @@ function AnonymousReportFooter({isSmallSizeLayout = false, report}: AnonymousRep report={report} isAnonymous shouldEnableDetailPageNavigation + policy={policy} /> @@ -52,4 +60,8 @@ function AnonymousReportFooter({isSmallSizeLayout = false, report}: AnonymousRep AnonymousReportFooter.displayName = 'AnonymousReportFooter'; -export default AnonymousReportFooter; +export default withOnyx({ + policy: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`, + }, +})(AnonymousReportFooter); From 55c900fd7497e87bc76f1504126b0fd9ce82b2ef Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 7 Dec 2023 16:45:16 +0500 Subject: [PATCH 029/142] refactor: cleanup --- src/pages/home/ReportScreen.js | 1 - tests/ui/UnreadIndicatorsTest.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 779ea3d22a8f..06532af0e817 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -506,7 +506,6 @@ export default compose( _.isEqual(prevProps.betas, nextProps.betas) && _.isEqual(prevProps.policies, nextProps.policies) && prevProps.accountManagerReportID === nextProps.accountManagerReportID && - _.isEqual(prevProps.personalDetails, nextProps.personalDetails) && prevProps.userLeavingStatus === nextProps.userLeavingStatus && prevProps.report.reportID === nextProps.report.reportID && prevProps.report.policyID === nextProps.report.policyID && diff --git a/tests/ui/UnreadIndicatorsTest.js b/tests/ui/UnreadIndicatorsTest.js index c6ead318a0db..ed44d3088ae0 100644 --- a/tests/ui/UnreadIndicatorsTest.js +++ b/tests/ui/UnreadIndicatorsTest.js @@ -530,7 +530,6 @@ describe('Unread Indicators', () => { lastActorAccountID: lastReportAction.actorAccountID, reportID: REPORT_ID, }); - return waitForBatchedUpdates(); }) .then(() => { From d8c895a98aba59dbc48e833d52c2681164c5216b Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 7 Dec 2023 16:56:44 +0500 Subject: [PATCH 030/142] refactor: cleanup --- src/components/ArchivedReportFooter.tsx | 2 +- src/libs/actions/Task.js | 2 +- src/pages/home/report/ReportActionItem.js | 1 - src/pages/home/report/ReportActionItemSingle.js | 3 +-- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx index 74901843a05c..3187bf3604e8 100644 --- a/src/components/ArchivedReportFooter.tsx +++ b/src/components/ArchivedReportFooter.tsx @@ -24,7 +24,7 @@ type ArchivedReportFooterProps = ArchivedReportFooterOnyxProps & { report: Report; }; -function ArchivedReportFooter({report, reportClosedAction, personalDetails = CONST.EMPTY_OBJECT}: ArchivedReportFooterProps) { +function ArchivedReportFooter({report, reportClosedAction, personalDetails = {}}: ArchivedReportFooterProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 07a03ed2588a..bf816d0a62a7 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -698,7 +698,7 @@ function getShareDestination(reportID, reports, personalDetails) { subtitle = ReportUtils.getChatRoomSubtitle(report); } return { - icons: ReportUtils.getIcons(report, personalDetails, Expensicons.FallbackAvatar, '', -1), + icons: ReportUtils.getIcons(report, personalDetails, Expensicons.FallbackAvatar), displayName: ReportUtils.getReportName(report), subtitle, displayNamesWithTooltips, diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index c306d8ab159d..92bb370155c9 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -138,7 +138,6 @@ function ReportActionItem(props) { const theme = useTheme(); const styles = useThemeStyles(); const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; - const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(props.action.reportActionID)); const [isHidden, setIsHidden] = useState(false); const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED); diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 11fa212b329a..b13d57ad2976 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -82,7 +82,6 @@ function ReportActionItemSingle(props) { const theme = useTheme(); const styles = useThemeStyles(); const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; - const actorAccountID = props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && props.iouReport ? props.iouReport.managerID : props.action.actorAccountID; let displayName = ReportUtils.getDisplayNameForParticipant(actorAccountID); const {avatar, login, pendingFields, status, fallbackIcon} = personalDetails[actorAccountID] || {}; @@ -122,7 +121,7 @@ function ReportActionItemSingle(props) { }; } else if (!isWorkspaceActor) { const avatarIconIndex = props.report.isOwnPolicyExpenseChat || ReportUtils.isPolicyExpenseChat(props.report) ? 0 : 1; - const reportIcons = ReportUtils.getIcons(props.report); + const reportIcons = ReportUtils.getIcons(props.report, {}); secondaryAvatar = reportIcons[avatarIconIndex]; } From 3036ef8fc83f2cd6450cfc0964afa27bc1cb9076 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 7 Dec 2023 16:57:26 +0500 Subject: [PATCH 031/142] refactor: move personalDetails to LHNOptionsList --- .../LHNOptionsList/LHNOptionsList.js | 13 ++++++++++-- .../LHNOptionsList/OptionRowLHNData.js | 21 ++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index b29db17a91b1..8febd7f247d6 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -5,6 +5,7 @@ import React, {useCallback} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; +import participantPropTypes from '@components/participantPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; import withCurrentReportID, {withCurrentReportIDDefaultProps, withCurrentReportIDPropTypes} from '@components/withCurrentReportID'; import compose from '@libs/compose'; @@ -57,6 +58,9 @@ const propTypes = { /** Indicates which locale the user currently has selected */ preferredLocale: PropTypes.string, + /** List of users' personal details */ + personalDetails: PropTypes.objectOf(participantPropTypes), + /** The transaction from the parent report action */ transactions: PropTypes.objectOf(transactionPropTypes), /** List of draft comments */ @@ -71,6 +75,7 @@ const defaultProps = { reports: {}, policy: {}, preferredLocale: CONST.LOCALES.DEFAULT, + personalDetails: {}, transactions: {}, draftComments: {}, ...withCurrentReportIDDefaultProps, @@ -89,6 +94,7 @@ function LHNOptionsList({ reportActions, policy, preferredLocale, + personalDetails, transactions, draftComments, currentReportID, @@ -114,7 +120,7 @@ function LHNOptionsList({ const itemComment = draftComments[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] || ''; const participants = [...ReportUtils.getParticipantsIDs(itemFullReport), itemFullReport.ownerAccountID]; - const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants); + const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, personalDetails); return ( ); }, - [currentReportID, draftComments, onSelectRow, optionMode, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], + [currentReportID, draftComments, onSelectRow, optionMode, personalDetails, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], ); return ( @@ -174,6 +180,9 @@ export default compose( preferredLocale: { key: ONYXKEYS.NVP_PREFERRED_LOCALE, }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, transactions: { key: ONYXKEYS.COLLECTION.TRANSACTION, }, diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index b6c3a6e7ed00..e11bfc3cab98 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -2,7 +2,7 @@ import {deepEqual} from 'fast-equals'; import PropTypes from 'prop-types'; import React, {useEffect, useMemo, useRef} from 'react'; import _ from 'underscore'; -import {usePersonalDetails} from '@components/OnyxProvider'; +import participantPropTypes from '@components/participantPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import SidebarUtils from '@libs/SidebarUtils'; @@ -16,6 +16,9 @@ const propTypes = { /** Whether row should be focused */ isFocused: PropTypes.bool, + /** List of users' personal details */ + personalDetails: PropTypes.objectOf(participantPropTypes), + /** The preferred language for the app */ preferredLocale: PropTypes.string, @@ -44,6 +47,7 @@ const propTypes = { const defaultProps = { isFocused: false, + personalDetails: {}, fullReport: {}, policy: {}, parentReportAction: {}, @@ -58,8 +62,19 @@ const defaultProps = { * The OptionRowLHN component is memoized, so it will only * re-render if the data really changed. */ -function OptionRowLHNData({isFocused, fullReport, reportActions, preferredLocale, comment, policy, receiptTransactions, parentReportAction, transaction, ...propsToForward}) { - const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; +function OptionRowLHNData({ + isFocused, + fullReport, + reportActions, + personalDetails, + preferredLocale, + comment, + policy, + receiptTransactions, + parentReportAction, + transaction, + ...propsToForward +}) { const reportID = propsToForward.reportID; const optionItemRef = useRef(); From 18cd5fe1571019892ca5840d449bba56ad548d6b Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 7 Dec 2023 17:22:50 +0500 Subject: [PATCH 032/142] fix: typescript issues --- src/components/AnonymousReportFooter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AnonymousReportFooter.tsx b/src/components/AnonymousReportFooter.tsx index 0414c67a094a..9c396db8e517 100644 --- a/src/components/AnonymousReportFooter.tsx +++ b/src/components/AnonymousReportFooter.tsx @@ -24,7 +24,7 @@ type AnonymousReportFooterProps = AnonymousReportFooterPropsWithOnyx & { isSmallSizeLayout?: boolean; }; -function AnonymousReportFooter({isSmallSizeLayout = false, report, policy = {}}: AnonymousReportFooterProps) { +function AnonymousReportFooter({isSmallSizeLayout = false, report, policy}: AnonymousReportFooterProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); From 2e192b379e11718445f51af79e9b0b50714bb0c3 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Tue, 12 Dec 2023 16:19:32 +0100 Subject: [PATCH 033/142] Migrate 'ReportActionItemChronosOOOListActions.js' component to TypeScript --- src/components/OfflineWithFeedback.tsx | 2 +- ...stActions.js => ChronosOOOListActions.tsx} | 52 +++++++++---------- src/libs/DateUtils.ts | 4 +- src/types/onyx/OriginalMessage.ts | 14 ++++- 4 files changed, 41 insertions(+), 31 deletions(-) rename src/components/ReportActionItem/{ChronosOOOListActions.js => ChronosOOOListActions.tsx} (58%) diff --git a/src/components/OfflineWithFeedback.tsx b/src/components/OfflineWithFeedback.tsx index 78c93c250f84..331b818af462 100644 --- a/src/components/OfflineWithFeedback.tsx +++ b/src/components/OfflineWithFeedback.tsx @@ -18,7 +18,7 @@ import MessagesRow from './MessagesRow'; type OfflineWithFeedbackProps = ChildrenProps & { /** The type of action that's pending */ - pendingAction: OnyxCommon.PendingAction; + pendingAction?: OnyxCommon.PendingAction; /** Determine whether to hide the component's children if deletion is pending */ shouldHideOnDelete?: boolean; diff --git a/src/components/ReportActionItem/ChronosOOOListActions.js b/src/components/ReportActionItem/ChronosOOOListActions.tsx similarity index 58% rename from src/components/ReportActionItem/ChronosOOOListActions.js rename to src/components/ReportActionItem/ChronosOOOListActions.tsx index 7c918b6a2d9c..2496e4233111 100644 --- a/src/components/ReportActionItem/ChronosOOOListActions.js +++ b/src/components/ReportActionItem/ChronosOOOListActions.tsx @@ -1,58 +1,57 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; -import _ from 'underscore'; import Button from '@components/Button'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import Text from '@components/Text'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import DateUtils from '@libs/DateUtils'; -import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; import useThemeStyles from '@styles/useThemeStyles'; import * as Chronos from '@userActions/Chronos'; +import type {OriginalMessageChronosOOOList} from '@src/types/onyx/OriginalMessage'; +import type {ReportActionBase} from '@src/types/onyx/ReportAction'; -const propTypes = { +type ChronosOOOListActionsProps = { /** The ID of the report */ - reportID: PropTypes.string.isRequired, + reportID: string; /** All the data of the action */ - action: PropTypes.shape(reportActionPropTypes).isRequired, - - ...withLocalizePropTypes, + action: ReportActionBase & OriginalMessageChronosOOOList; }; -function ChronosOOOListActions(props) { +function ChronosOOOListActions({reportID, action}: ChronosOOOListActionsProps) { const styles = useThemeStyles(); - const events = lodashGet(props.action, 'originalMessage.events', []); + + const {translate, preferredLocale} = useLocalize(); + + const events = action.originalMessage?.events ?? []; if (!events.length) { return ( - + You haven't created any events ); } return ( - - - {_.map(events, (event) => { - const start = DateUtils.getLocalDateFromDatetime(props.preferredLocale, lodashGet(event, 'start.date', '')); - const end = DateUtils.getLocalDateFromDatetime(props.preferredLocale, lodashGet(event, 'end.date', '')); + + + {events.map((event) => { + const start = DateUtils.getLocalDateFromDatetime(preferredLocale, event?.end?.date ?? ''); + const end = DateUtils.getLocalDateFromDatetime(preferredLocale, event?.end?.date ?? ''); return ( - + {event.lengthInDays > 0 - ? props.translate('chronos.oooEventSummaryFullDay', { + ? translate('chronos.oooEventSummaryFullDay', { summary: event.summary, dayCount: event.lengthInDays, date: DateUtils.formatToLongDateWithWeekday(end), }) - : props.translate('chronos.oooEventSummaryPartialDay', { + : translate('chronos.oooEventSummaryPartialDay', { summary: event.summary, timePeriod: `${DateUtils.formatToLocalTime(start)} - ${DateUtils.formatToLocalTime(end)}`, date: DateUtils.formatToLongDateWithWeekday(end), @@ -60,10 +59,10 @@ function ChronosOOOListActions(props) { ); @@ -73,7 +72,6 @@ function ChronosOOOListActions(props) { ); } -ChronosOOOListActions.propTypes = propTypes; ChronosOOOListActions.displayName = 'ChronosOOOListActions'; -export default withLocalize(ChronosOOOListActions); +export default ChronosOOOListActions; diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts index 80eae24d9367..e7eaca2aab6a 100644 --- a/src/libs/DateUtils.ts +++ b/src/libs/DateUtils.ts @@ -210,7 +210,7 @@ function getZoneAbbreviation(datetime: string, selectedTimezone: SelectedTimezon * * @returns Sunday, July 9, 2023 */ -function formatToLongDateWithWeekday(datetime: string): string { +function formatToLongDateWithWeekday(datetime: string | Date): string { return format(new Date(datetime), CONST.DATE.LONG_DATE_FORMAT_WITH_WEEKDAY); } @@ -228,7 +228,7 @@ function formatToDayOfWeek(datetime: string): string { * * @returns 2:30 PM */ -function formatToLocalTime(datetime: string): string { +function formatToLocalTime(datetime: string | Date): string { return format(new Date(datetime), CONST.DATE.LOCAL_TIME_FORMAT); } diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index fc7f8eb8ba31..1b949ac48a3f 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -234,4 +234,16 @@ type OriginalMessage = | OriginalMessageMoved; export default OriginalMessage; -export type {ChronosOOOEvent, Decision, Reaction, ActionName, IOUMessage, Closed, OriginalMessageActionName, ChangeLog, OriginalMessageIOU, OriginalMessageCreated}; +export type { + ChronosOOOEvent, + Decision, + Reaction, + ActionName, + IOUMessage, + Closed, + OriginalMessageActionName, + ChangeLog, + OriginalMessageIOU, + OriginalMessageCreated, + OriginalMessageChronosOOOList, +}; From c632b7ce204f9abd1d23d0b66f5b6b37e60dbd94 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 19 Dec 2023 13:15:52 +0500 Subject: [PATCH 034/142] fix: typecheck for AvatarWithDisplayName --- src/components/AvatarWithDisplayName.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index a409da9ea068..db6608fb1f32 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -11,7 +11,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {PersonalDetails, Policy, Report, ReportActions} from '@src/types/onyx'; +import {PersonalDetailsList, Policy, Report, ReportActions} from '@src/types/onyx'; import DisplayNames from './DisplayNames'; import MultipleAvatars from './MultipleAvatars'; import ParentNavigationSubtitle from './ParentNavigationSubtitle'; @@ -24,7 +24,7 @@ type AvatarWithDisplayNamePropsWithOnyx = { parentReportActions: OnyxEntry; /** Personal details of all users */ - personalDetails: OnyxEntry>; + personalDetails: OnyxEntry; }; type AvatarWithDisplayNameProps = AvatarWithDisplayNamePropsWithOnyx & { @@ -181,6 +181,6 @@ export default withOnyx Date: Tue, 19 Dec 2023 16:00:04 +0500 Subject: [PATCH 035/142] fix: linting --- src/components/AvatarWithDisplayName.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index db6608fb1f32..24beb7f1f16d 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -181,6 +181,6 @@ export default withOnyx Date: Thu, 28 Dec 2023 10:16:58 +0100 Subject: [PATCH 036/142] apply prettier --- src/components/ReportActionItem/ChronosOOOListActions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/ChronosOOOListActions.tsx b/src/components/ReportActionItem/ChronosOOOListActions.tsx index fc83e1be5a30..616404195c2f 100644 --- a/src/components/ReportActionItem/ChronosOOOListActions.tsx +++ b/src/components/ReportActionItem/ChronosOOOListActions.tsx @@ -4,8 +4,8 @@ import Button from '@components/Button'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; -import DateUtils from '@libs/DateUtils'; import useThemeStyles from '@hooks/useThemeStyles'; +import DateUtils from '@libs/DateUtils'; import * as Chronos from '@userActions/Chronos'; import type {OriginalMessageChronosOOOList} from '@src/types/onyx/OriginalMessage'; import type {ReportActionBase} from '@src/types/onyx/ReportAction'; From 15ba30633dd1d523d0bacbb51f3197ed810db188 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 10 Jan 2024 16:45:14 +0500 Subject: [PATCH 037/142] fix: paste not working on web --- .../home/report/ReportActionCompose/ReportActionCompose.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js index fb9ad6c55649..ab9c989473af 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js @@ -343,8 +343,8 @@ function ReportActionCompose({ runOnJS(submitForm)(); }, [isSendDisabled, resetFullComposerSize, submitForm, isReportReadyForDisplay]); - const onDisplayFileInModal = () => { - attachementModalRef.current.displayFileInModal(); + const onDisplayFileInModal = (file) => { + attachementModalRef.current.displayFileInModal(file); }; return ( From 1ab6e1986a4cb14bdeff4e1355876d321b9e6841 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 11 Jan 2024 15:29:36 +0500 Subject: [PATCH 038/142] fix: compose height on mobile --- src/styles/index.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index a6ac2e269eb0..1aff94666fd0 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3,7 +3,7 @@ import type {LineLayerStyleProps} from '@rnmapbox/maps/src/utils/MapboxStyles'; import lodashClamp from 'lodash/clamp'; import type {LineLayer} from 'react-map-gl'; import type {AnimatableNumericValue, Animated, ImageStyle, TextStyle, ViewStyle} from 'react-native'; -import {StyleSheet} from 'react-native'; +import {Platform, StyleSheet} from 'react-native'; import type {CustomAnimation} from 'react-native-animatable'; import type {PickerStyle} from 'react-native-picker-select'; import type {MixedStyleDeclaration, MixedStyleRecord} from 'react-native-render-html'; @@ -1885,6 +1885,14 @@ const styles = (theme: ThemeColors) => height: 40, padding: 10, margin: 3, + ...Platform.select({ + ios: { + marginVertical: 0, + }, + android: { + marginVertical: 0, + }, + }), justifyContent: 'center', }, @@ -1951,9 +1959,17 @@ const styles = (theme: ThemeColors) => alignSelf: 'flex-end', borderRadius: variables.buttonBorderRadius, height: 40, - marginVertical: 3, paddingHorizontal: 10, justifyContent: 'center', + marginVertical: 3, + ...Platform.select({ + ios: { + marginVertical: 0, + }, + android: { + marginVertical: 0, + }, + }), }, editChatItemEmojiWrapper: { From 1139e31d1726e7d89a3f0e39e95714836019e430 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 11 Jan 2024 16:58:54 +0500 Subject: [PATCH 039/142] fix: revert platform specific styles --- src/styles/index.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index cac903f3c093..359b455fb3c3 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3,7 +3,7 @@ import type {LineLayerStyleProps} from '@rnmapbox/maps/src/utils/MapboxStyles'; import lodashClamp from 'lodash/clamp'; import type {LineLayer} from 'react-map-gl'; import type {AnimatableNumericValue, Animated, ImageStyle, TextStyle, ViewStyle} from 'react-native'; -import {Platform, StyleSheet} from 'react-native'; +import {StyleSheet} from 'react-native'; import type {CustomAnimation} from 'react-native-animatable'; import type {PickerStyle} from 'react-native-picker-select'; import type {MixedStyleDeclaration, MixedStyleRecord} from 'react-native-render-html'; @@ -1885,14 +1885,6 @@ const styles = (theme: ThemeColors) => height: 40, padding: 10, margin: 3, - ...Platform.select({ - ios: { - marginVertical: 0, - }, - android: { - marginVertical: 0, - }, - }), justifyContent: 'center', }, @@ -1962,14 +1954,6 @@ const styles = (theme: ThemeColors) => paddingHorizontal: 10, justifyContent: 'center', marginVertical: 3, - ...Platform.select({ - ios: { - marginVertical: 0, - }, - android: { - marginVertical: 0, - }, - }), }, editChatItemEmojiWrapper: { From 2c6f73ad44e10820cb754879dda6b25333c6ba33 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 11 Jan 2024 16:59:09 +0500 Subject: [PATCH 040/142] fix: extra compose height --- .../ComposerWithSuggestions.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index ac4163c311e1..978b9bc69c97 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -597,20 +597,20 @@ function ComposerWithSuggestions({ onScroll={hideSuggestionMenu} shouldContainScroll={Browser.isMobileSafari()} /> + - {DeviceCapabilities.canUseTouchScreen() && isMediumScreenWidth ? null : ( - replaceSelectionWithText(...args)} - emojiPickerID={reportID} - /> - )} - replaceSelectionWithText(...args)} + emojiPickerID={reportID} /> - + )} + Date: Sun, 14 Jan 2024 22:34:32 +0500 Subject: [PATCH 041/142] feat: save the report fields in backend --- .../ReportActionItem/MoneyReportView.tsx | 6 +- src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/Permissions.ts | 2 +- src/libs/ReportUtils.ts | 6 +- src/libs/actions/Report.ts | 61 ++++++++++++++++++- src/pages/EditReportFieldDatePage.tsx | 2 +- src/pages/EditReportFieldDropdownPage.tsx | 9 ++- src/pages/EditReportFieldPage.tsx | 17 +++++- src/pages/EditReportFieldTextPage.tsx | 2 +- 10 files changed, 92 insertions(+), 15 deletions(-) diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx index 8c6e818d2411..ccac50d688b2 100644 --- a/src/components/ReportActionItem/MoneyReportView.tsx +++ b/src/components/ReportActionItem/MoneyReportView.tsx @@ -55,8 +55,8 @@ function MoneyReportView({report, policyReportFields, shouldShowHorizontalRule}: StyleUtils.getColorStyle(theme.textSupporting), ]; - const sortedPolicyReportFields = useMemo( - () => policyReportFields.sort(({orderWeight: firstOrderWeight}, {orderWeight: secondOrderWeight}) => firstOrderWeight - secondOrderWeight), + const sortedPolicyReportFields = useMemo( + (): PolicyReportField[] => policyReportFields.sort(({orderWeight: firstOrderWeight}, {orderWeight: secondOrderWeight}) => firstOrderWeight - secondOrderWeight), [policyReportFields], ); @@ -70,6 +70,8 @@ function MoneyReportView({report, policyReportFields, shouldShowHorizontalRule}: return ( ): boolean { } function canUseReportFields(betas: OnyxEntry): boolean { - return !!betas?.includes(CONST.BETAS.REPORT_FIELDS) || canUseAllBetas(betas); + return true; // !!betas?.includes(CONST.BETAS.REPORT_FIELDS) || canUseAllBetas(betas); } function canUseViolations(betas: OnyxEntry): boolean { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c2d98c1ee00c..e00d8afc1362 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4330,15 +4330,15 @@ function getReportFieldTitle(report: OnyxEntry, reportField: PolicyRepor const value = report?.reportFields?.[reportField.fieldID] ?? reportField.defaultValue; if (reportField.type !== 'formula') { - return value; + return value as string; } - return value.replaceAll(CONST.REGEX.REPORT_FIELD_TITLE, (match, property) => { + return value.replaceAll(CONST.REGEX.REPORT_FIELD_TITLE, (match: string, property: string) => { if (report && property in report) { return report[property as keyof Report]?.toString() ?? match; } return match; - }); + }) as string; } /** diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 55e91834a803..f041e4f50c39 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -31,7 +31,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetails, PersonalDetailsList, ReportActionReactions, ReportUserIsTyping} from '@src/types/onyx'; +import type {PersonalDetails, PersonalDetailsList, PolicyReportField, ReportActionReactions, ReportUserIsTyping} from '@src/types/onyx'; import type {Decision, OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import type {NotificationPreference, WriteCapability} from '@src/types/onyx/Report'; import type Report from '@src/types/onyx/Report'; @@ -1481,6 +1481,64 @@ function toggleSubscribeToChildReport(childReportID = '0', parentReportAction: P } } +function updatePolicyReportField(reportID: string, policyField: PolicyReportField, fieldValue: string) { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: { + reportFields: { + [policyField.fieldID]: fieldValue, + }, + pendingFields: { + [policyField.fieldID]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }, + }, + ]; + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: { + pendingFields: { + [policyField.fieldID]: null, + }, + errorFields: { + [policyField.fieldID]: ErrorUtils.getMicroSecondOnyxError('report.genericUpdateReportFieldFailureMessage'), + }, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: { + pendingFields: { + [policyField.fieldID]: null, + }, + errorFields: { + [policyField.fieldID]: null, + }, + }, + }, + ]; + + type UpdateReportFieldParameters = { + reportID: string; + reportFields: string; + }; + + const parameters: UpdateReportFieldParameters = { + reportID, + reportFields: JSON.stringify({[policyField.fieldID]: {fieldID: policyField.fieldID, value: fieldValue, type: policyField.type, name: policyField.name}}), + }; + + API.write('Report_SetFields', parameters, {optimisticData, failureData, successData}); +} + function updateWelcomeMessage(reportID: string, previousValue: string, newValue: string) { // No change needed, navigate back if (previousValue === newValue) { @@ -2649,4 +2707,5 @@ export { getDraftPrivateNote, updateLastVisitTime, clearNewRoomFormError, + updatePolicyReportField, }; diff --git a/src/pages/EditReportFieldDatePage.tsx b/src/pages/EditReportFieldDatePage.tsx index 5ee86b2bf8e6..8b3a8b74ba0d 100644 --- a/src/pages/EditReportFieldDatePage.tsx +++ b/src/pages/EditReportFieldDatePage.tsx @@ -21,7 +21,7 @@ type EditReportFieldDatePageProps = { fieldID: string; /** Callback to fire when the Save button is pressed */ - onSubmit: () => void; + onSubmit: (form: Record) => void; }; function EditReportFieldDatePage({fieldName, onSubmit, fieldValue, fieldID}: EditReportFieldDatePageProps) { diff --git a/src/pages/EditReportFieldDropdownPage.tsx b/src/pages/EditReportFieldDropdownPage.tsx index 7c16a3dad3f6..bae02482d964 100644 --- a/src/pages/EditReportFieldDropdownPage.tsx +++ b/src/pages/EditReportFieldDropdownPage.tsx @@ -13,14 +13,17 @@ type EditReportFieldDropdownPageProps = { /** Name of the policy report field */ fieldName: string; + /** ID of the policy report field */ + fieldID: string; + /** Options of the policy report field */ fieldOptions: string[]; /** Callback to fire when the Save button is pressed */ - onSubmit: () => void; + onSubmit: (form: Record) => void; }; -function EditReportFieldDropdownPage({fieldName, onSubmit, fieldValue, fieldOptions}: EditReportFieldDropdownPageProps) { +function EditReportFieldDropdownPage({fieldName, onSubmit, fieldID, fieldValue, fieldOptions}: EditReportFieldDropdownPageProps) { const [searchValue, setSearchValue] = useState(''); const styles = useThemeStyles(); const {getSafeAreaMargins} = useStyleUtils(); @@ -66,7 +69,7 @@ function EditReportFieldDropdownPage({fieldName, onSubmit, fieldValue, fieldOpti boldStyle sections={sections} value={searchValue} - onSelectRow={onSubmit} + onSelectRow={(option: Record) => onSubmit({[fieldID]: option.text})} onChangeText={setSearchValue} highlightSelectedOptions isRowMultilineSupported diff --git a/src/pages/EditReportFieldPage.tsx b/src/pages/EditReportFieldPage.tsx index d74582708995..6ed17636d043 100644 --- a/src/pages/EditReportFieldPage.tsx +++ b/src/pages/EditReportFieldPage.tsx @@ -3,6 +3,8 @@ import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ScreenWrapper from '@components/ScreenWrapper'; +import Navigation from '@libs/Navigation/Navigation'; +import * as ReportActions from '@src/libs/actions/Report'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PolicyReportFields, Report} from '@src/types/onyx'; import EditReportFieldDatePage from './EditReportFieldDatePage'; @@ -41,6 +43,14 @@ function EditReportFieldPage({route, report, policyReportFields}: EditReportFiel // Decides whether to allow or disallow editing a money request useEffect(() => {}, []); + const handleReportFieldChange = (form: Record) => { + if (report && policyReportField) { + const value = form[policyReportField.fieldID] || ''; + ReportActions.updatePolicyReportField(report.reportID, policyReportField, value); + } + Navigation.dismissModal(report?.reportID); + }; + if (policyReportField) { if (policyReportField.type === 'text' || policyReportField.type === 'formula') { return ( @@ -48,7 +58,7 @@ function EditReportFieldPage({route, report, policyReportFields}: EditReportFiel fieldName={policyReportField.name} fieldID={policyReportField.fieldID} fieldValue={reportFieldValue ?? policyReportField.defaultValue} - onSubmit={() => {}} + onSubmit={handleReportFieldChange} /> ); } @@ -59,7 +69,7 @@ function EditReportFieldPage({route, report, policyReportFields}: EditReportFiel fieldName={policyReportField.name} fieldID={policyReportField.fieldID} fieldValue={reportFieldValue ?? policyReportField.defaultValue} - onSubmit={() => {}} + onSubmit={handleReportFieldChange} /> ); } @@ -67,10 +77,11 @@ function EditReportFieldPage({route, report, policyReportFields}: EditReportFiel if (policyReportField.type === 'dropdown') { return ( {}} + onSubmit={handleReportFieldChange} /> ); } diff --git a/src/pages/EditReportFieldTextPage.tsx b/src/pages/EditReportFieldTextPage.tsx index b468861e9a27..eac85053448f 100644 --- a/src/pages/EditReportFieldTextPage.tsx +++ b/src/pages/EditReportFieldTextPage.tsx @@ -21,7 +21,7 @@ type EditReportFieldTextPageProps = { fieldID: string; /** Callback to fire when the Save button is pressed */ - onSubmit: () => void; + onSubmit: (form: Record) => void; }; function EditReportFieldTextPage({fieldName, onSubmit, fieldValue, fieldID}: EditReportFieldTextPageProps) { From e08945548b9a0fa77620fa96a78e75d629a12db2 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Sun, 14 Jan 2024 22:42:45 +0500 Subject: [PATCH 042/142] fix: revert canUseReportField beta --- src/libs/Permissions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 47c6ae0f9a2d..ce5e0e674c59 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -19,7 +19,7 @@ function canUseCommentLinking(betas: OnyxEntry): boolean { } function canUseReportFields(betas: OnyxEntry): boolean { - return true; // !!betas?.includes(CONST.BETAS.REPORT_FIELDS) || canUseAllBetas(betas); + return !!betas?.includes(CONST.BETAS.REPORT_FIELDS) || canUseAllBetas(betas); } function canUseViolations(betas: OnyxEntry): boolean { From 7532fffd317f0e57b47606c568861dc31ded8903 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Mon, 15 Jan 2024 00:02:27 +0500 Subject: [PATCH 043/142] fix: lint errors --- src/libs/ReportUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 6892f05b39ab..ca3357c29dd6 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4333,15 +4333,15 @@ function getReportFieldTitle(report: OnyxEntry, reportField: PolicyRepor const value = report?.reportFields?.[reportField.fieldID] ?? reportField.defaultValue; if (reportField.type !== 'formula') { - return value as string; + return value; } - return value.replaceAll(CONST.REGEX.REPORT_FIELD_TITLE, (match: string, property: string) => { + return value.replaceAll(CONST.REGEX.REPORT_FIELD_TITLE, (match, property) => { if (report && property in report) { return report[property as keyof Report]?.toString() ?? match; } return match; - }) as string; + }); } /** From c0751eb959ca2ba7dda7079b582f8ea7a397b7cf Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 15 Jan 2024 10:54:00 +0700 Subject: [PATCH 044/142] fix: svg icons do not show on native --- src/components/AttachmentModal.js | 5 +++++ src/components/Attachments/AttachmentView/index.js | 10 ++++++++-- src/components/AvatarWithImagePicker.js | 1 + src/components/RoomHeaderAvatars.js | 2 ++ src/pages/DetailsPage.js | 1 + src/pages/ProfilePage.js | 1 + 6 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index bd8d535e540f..02ff89e7d0d9 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -92,6 +92,9 @@ const propTypes = { /** Denotes whether it is a workspace avatar or not */ isWorkspaceAvatar: PropTypes.bool, + /** Denotes whether it is an icon (ex: SVG) */ + maybeIcon: PropTypes.bool, + /** Whether it is a receipt attachment or not */ isReceiptAttachment: PropTypes.bool, @@ -114,6 +117,7 @@ const defaultProps = { onModalHide: () => {}, onCarouselAttachmentChange: () => {}, isWorkspaceAvatar: false, + maybeIcon: false, isReceiptAttachment: false, canEditReceipt: false, }; @@ -480,6 +484,7 @@ function AttachmentModal(props) { file={file} onToggleKeyboard={updateConfirmButtonVisibility} isWorkspaceAvatar={props.isWorkspaceAvatar} + maybeIcon={props.maybeIcon} fallbackSource={props.fallbackSource} isUsedInAttachmentModal transactionID={props.transaction.transactionID} diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index b0060afdb813..49951bed54a3 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -44,6 +44,9 @@ const propTypes = { /** Denotes whether it is a workspace avatar or not */ isWorkspaceAvatar: PropTypes.bool, + /** Denotes whether it is an icon (ex: SVG) */ + maybeIcon: PropTypes.bool, + /** The id of the transaction related to the attachment */ // eslint-disable-next-line react/no-unused-prop-types transactionID: PropTypes.string, @@ -56,6 +59,7 @@ const defaultProps = { onToggleKeyboard: () => {}, containerStyles: [], isWorkspaceAvatar: false, + maybeIcon: false, transactionID: '', }; @@ -77,6 +81,7 @@ function AttachmentView({ carouselActiveItemIndex, isUsedInAttachmentModal, isWorkspaceAvatar, + maybeIcon, fallbackSource, transaction, }) { @@ -88,8 +93,9 @@ function AttachmentView({ useNetwork({onReconnect: () => setImageError(false)}); - // Handles case where source is a component (ex: SVG) - if (_.isFunction(source)) { + // Handles case where source is a component (ex: SVG) or a number + // Number may represent a SVG or an image + if ((maybeIcon && typeof source === 'number') || _.isFunction(source)) { let iconFillColor = ''; let additionalStyles = []; if (isWorkspaceAvatar) { diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index 71193147c292..b5dad7fdbef9 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -333,6 +333,7 @@ function AvatarWithImagePicker({ source={previewSource} originalFileName={originalFileName} fallbackSource={fallbackIcon} + maybeIcon={isUsingDefaultAvatar} > {({show}) => ( diff --git a/src/components/RoomHeaderAvatars.js b/src/components/RoomHeaderAvatars.js index a3394fe71539..a216e5f78b8a 100644 --- a/src/components/RoomHeaderAvatars.js +++ b/src/components/RoomHeaderAvatars.js @@ -35,6 +35,7 @@ function RoomHeaderAvatars(props) { isAuthTokenRequired isWorkspaceAvatar={props.icons[0].type === CONST.ICON_TYPE_WORKSPACE} originalFileName={props.icons[0].name} + maybeIcon > {({show}) => ( {({show}) => ( {({show}) => ( {({show}) => ( Date: Mon, 6 Nov 2023 09:32:15 +0200 Subject: [PATCH 045/142] Refactor Image for Web/Desktop with Source Headers - Introduced `BaseImage` component that branches between native and web implementations. - **Native**: Utilizes `FastImage` directly. - **Web**: Minor adjustments made to the `onLoad` event signature for compatibility. - Eliminated `Image/index.native.js` as both native and web components now leverage a unified high-level implementation for image loading and rendering. (cherry picked from commit 2aa37d240e9d53069294ad5d81075c54a969c583) --- src/components/Image/BaseImage.js | 28 +++++++++++ src/components/Image/BaseImage.native.js | 4 ++ src/components/Image/index.js | 52 +++++++------------ src/components/Image/index.native.js | 63 ------------------------ 4 files changed, 51 insertions(+), 96 deletions(-) create mode 100644 src/components/Image/BaseImage.js create mode 100644 src/components/Image/BaseImage.native.js delete mode 100644 src/components/Image/index.native.js diff --git a/src/components/Image/BaseImage.js b/src/components/Image/BaseImage.js new file mode 100644 index 000000000000..199cbb78aab0 --- /dev/null +++ b/src/components/Image/BaseImage.js @@ -0,0 +1,28 @@ +import React, {useCallback} from 'react'; +import {Image as RNImage} from 'react-native'; +import {defaultProps, imagePropTypes} from './imagePropTypes'; + +function BaseImage({onLoad, ...props}) { + const imageLoadedSuccessfully = useCallback( + ({nativeEvent}) => { + // We override `onLoad`, so both web and native have the same signature + const {width, height} = nativeEvent.source; + onLoad({nativeEvent: {width, height}}); + }, + [onLoad], + ); + + return ( + + ); +} + +BaseImage.propTypes = imagePropTypes; +BaseImage.defaultProps = defaultProps; + +export default BaseImage; diff --git a/src/components/Image/BaseImage.native.js b/src/components/Image/BaseImage.native.js new file mode 100644 index 000000000000..228a455b4764 --- /dev/null +++ b/src/components/Image/BaseImage.native.js @@ -0,0 +1,4 @@ +import {Image as ExpoImage} from 'expo-image'; + + +export default ExpoImage; diff --git a/src/components/Image/index.js b/src/components/Image/index.js index ef1a69e19c12..440f7ada7348 100644 --- a/src/components/Image/index.js +++ b/src/components/Image/index.js @@ -1,51 +1,37 @@ import lodashGet from 'lodash/get'; -import React, {useEffect, useMemo} from 'react'; -import {Image as RNImage} from 'react-native'; +import React, {useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import BaseImage from './BaseImage'; import {defaultProps, imagePropTypes} from './imagePropTypes'; import RESIZE_MODES from './resizeModes'; function Image(props) { - const {source: propsSource, isAuthTokenRequired, onLoad, session} = props; - /** - * Check if the image source is a URL - if so the `encryptedAuthToken` is appended - * to the source. - */ + const {source: propsSource, isAuthTokenRequired, session, ...forwardedProps} = props; + + // Update the source to include the auth token if required const source = useMemo(() => { - if (isAuthTokenRequired) { - // There is currently a `react-native-web` bug preventing the authToken being passed - // in the headers of the image request so the authToken is added as a query param. - // On native the authToken IS passed in the image request headers - const authToken = lodashGet(session, 'encryptedAuthToken', null); - return {uri: `${propsSource.uri}?encryptedAuthToken=${encodeURIComponent(authToken)}`}; + if (typeof lodashGet(propsSource, 'uri') === 'number') { + return propsSource.uri; + } + if (typeof propsSource !== 'number' && isAuthTokenRequired) { + const authToken = lodashGet(session, 'encryptedAuthToken'); + return { + ...propsSource, + headers: { + [CONST.CHAT_ATTACHMENT_TOKEN_KEY]: authToken, + }, + }; } + return propsSource; // The session prop is not required, as it causes the image to reload whenever the session changes. For more information, please refer to issue #26034. // eslint-disable-next-line react-hooks/exhaustive-deps }, [propsSource, isAuthTokenRequired]); - /** - * The natural image dimensions are retrieved using the updated source - * and as a result the `onLoad` event needs to be manually invoked to return these dimensions - */ - useEffect(() => { - // If an onLoad callback was specified then manually call it and pass - // the natural image dimensions to match the native API - if (onLoad == null) { - return; - } - RNImage.getSize(source.uri, (width, height) => { - onLoad({nativeEvent: {width, height}}); - }); - }, [onLoad, source]); - - // Omit the props which the underlying RNImage won't use - const forwardedProps = _.omit(props, ['source', 'onLoad', 'session', 'isAuthTokenRequired']); - return ( - { - const {width, height, url} = evt.source; - dimensionsCache.set(url, {width, height}); - if (props.onLoad) { - props.onLoad({nativeEvent: {width, height}}); - } - }} - /> - ); -} - -Image.propTypes = imagePropTypes; -Image.defaultProps = defaultProps; -Image.displayName = 'Image'; -const ImageWithOnyx = withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, -})(Image); -ImageWithOnyx.resizeMode = RESIZE_MODES; -ImageWithOnyx.resolveDimensions = resolveDimensions; - -export default ImageWithOnyx; From 37df3e3dd10cd130ac60b179023ee339e3d3b8d6 Mon Sep 17 00:00:00 2001 From: Peter Velkov Date: Mon, 20 Nov 2023 20:22:57 +0200 Subject: [PATCH 046/142] Add react-native-web patch for image header support (cherry picked from commit 19b605e4e52c1c71d1515065c0ba2de7af0c61b1) --- ...-web+0.19.9+005+image-header-support.patch | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 patches/react-native-web+0.19.9+005+image-header-support.patch diff --git a/patches/react-native-web+0.19.9+005+image-header-support.patch b/patches/react-native-web+0.19.9+005+image-header-support.patch new file mode 100644 index 000000000000..4652e22662f0 --- /dev/null +++ b/patches/react-native-web+0.19.9+005+image-header-support.patch @@ -0,0 +1,200 @@ +diff --git a/node_modules/react-native-web/dist/exports/Image/index.js b/node_modules/react-native-web/dist/exports/Image/index.js +index 95355d5..19109fc 100644 +--- a/node_modules/react-native-web/dist/exports/Image/index.js ++++ b/node_modules/react-native-web/dist/exports/Image/index.js +@@ -135,7 +135,22 @@ function resolveAssetUri(source) { + } + return uri; + } +-var Image = /*#__PURE__*/React.forwardRef((props, ref) => { ++function raiseOnErrorEvent(uri, _ref) { ++ var onError = _ref.onError, ++ onLoadEnd = _ref.onLoadEnd; ++ if (onError) { ++ onError({ ++ nativeEvent: { ++ error: "Failed to load resource " + uri + " (404)" ++ } ++ }); ++ } ++ if (onLoadEnd) onLoadEnd(); ++} ++function hasSourceDiff(a, b) { ++ return a.uri !== b.uri || JSON.stringify(a.headers) !== JSON.stringify(b.headers); ++} ++var BaseImage = /*#__PURE__*/React.forwardRef((props, ref) => { + var ariaLabel = props['aria-label'], + blurRadius = props.blurRadius, + defaultSource = props.defaultSource, +@@ -236,16 +251,10 @@ var Image = /*#__PURE__*/React.forwardRef((props, ref) => { + } + }, function error() { + updateState(ERRORED); +- if (onError) { +- onError({ +- nativeEvent: { +- error: "Failed to load resource " + uri + " (404)" +- } +- }); +- } +- if (onLoadEnd) { +- onLoadEnd(); +- } ++ raiseOnErrorEvent(uri, { ++ onError, ++ onLoadEnd ++ }); + }); + } + function abortPendingRequest() { +@@ -277,10 +286,78 @@ var Image = /*#__PURE__*/React.forwardRef((props, ref) => { + suppressHydrationWarning: true + }), hiddenImage, createTintColorSVG(tintColor, filterRef.current)); + }); +-Image.displayName = 'Image'; ++BaseImage.displayName = 'Image'; ++ ++/** ++ * This component handles specifically loading an image source with headers ++ * default source is never loaded using headers ++ */ ++var ImageWithHeaders = /*#__PURE__*/React.forwardRef((props, ref) => { ++ // $FlowIgnore: This component would only be rendered when `source` matches `ImageSource` ++ var nextSource = props.source; ++ var _React$useState3 = React.useState(''), ++ blobUri = _React$useState3[0], ++ setBlobUri = _React$useState3[1]; ++ var request = React.useRef({ ++ cancel: () => {}, ++ source: { ++ uri: '', ++ headers: {} ++ }, ++ promise: Promise.resolve('') ++ }); ++ var onError = props.onError, ++ onLoadStart = props.onLoadStart, ++ onLoadEnd = props.onLoadEnd; ++ React.useEffect(() => { ++ if (!hasSourceDiff(nextSource, request.current.source)) { ++ return; ++ } ++ ++ // When source changes we want to clean up any old/running requests ++ request.current.cancel(); ++ if (onLoadStart) { ++ onLoadStart(); ++ } ++ ++ // Store a ref for the current load request so we know what's the last loaded source, ++ // and so we can cancel it if a different source is passed through props ++ request.current = ImageLoader.loadWithHeaders(nextSource); ++ request.current.promise.then(uri => setBlobUri(uri)).catch(() => raiseOnErrorEvent(request.current.source.uri, { ++ onError, ++ onLoadEnd ++ })); ++ }, [nextSource, onLoadStart, onError, onLoadEnd]); ++ ++ // Cancel any request on unmount ++ React.useEffect(() => request.current.cancel, []); ++ var propsToPass = _objectSpread(_objectSpread({}, props), {}, { ++ // `onLoadStart` is called from the current component ++ // We skip passing it down to prevent BaseImage raising it a 2nd time ++ onLoadStart: undefined, ++ // Until the current component resolves the request (using headers) ++ // we skip forwarding the source so the base component doesn't attempt ++ // to load the original source ++ source: blobUri ? _objectSpread(_objectSpread({}, nextSource), {}, { ++ uri: blobUri ++ }) : undefined ++ }); ++ return /*#__PURE__*/React.createElement(BaseImage, _extends({ ++ ref: ref ++ }, propsToPass)); ++}); + + // $FlowIgnore: This is the correct type, but casting makes it unhappy since the variables aren't defined yet +-var ImageWithStatics = Image; ++var ImageWithStatics = /*#__PURE__*/React.forwardRef((props, ref) => { ++ if (props.source && props.source.headers) { ++ return /*#__PURE__*/React.createElement(ImageWithHeaders, _extends({ ++ ref: ref ++ }, props)); ++ } ++ return /*#__PURE__*/React.createElement(BaseImage, _extends({ ++ ref: ref ++ }, props)); ++}); + ImageWithStatics.getSize = function (uri, success, failure) { + ImageLoader.getSize(uri, success, failure); + }; +diff --git a/node_modules/react-native-web/dist/modules/ImageLoader/index.js b/node_modules/react-native-web/dist/modules/ImageLoader/index.js +index bc06a87..e309394 100644 +--- a/node_modules/react-native-web/dist/modules/ImageLoader/index.js ++++ b/node_modules/react-native-web/dist/modules/ImageLoader/index.js +@@ -76,7 +76,7 @@ var ImageLoader = { + var image = requests["" + requestId]; + if (image) { + var naturalHeight = image.naturalHeight, +- naturalWidth = image.naturalWidth; ++ naturalWidth = image.naturalWidth; + if (naturalHeight && naturalWidth) { + success(naturalWidth, naturalHeight); + complete = true; +@@ -102,11 +102,19 @@ var ImageLoader = { + id += 1; + var image = new window.Image(); + image.onerror = onError; +- image.onload = e => { ++ image.onload = nativeEvent => { + // avoid blocking the main thread +- var onDecode = () => onLoad({ +- nativeEvent: e +- }); ++ var onDecode = () => { ++ // Append `source` to match RN's ImageLoadEvent interface ++ nativeEvent.source = { ++ uri: image.src, ++ width: image.naturalWidth, ++ height: image.naturalHeight ++ }; ++ onLoad({ ++ nativeEvent ++ }); ++ }; + if (typeof image.decode === 'function') { + // Safari currently throws exceptions when decoding svgs. + // We want to catch that error and allow the load handler +@@ -120,6 +128,32 @@ var ImageLoader = { + requests["" + id] = image; + return id; + }, ++ loadWithHeaders(source) { ++ var uri; ++ var abortController = new AbortController(); ++ var request = new Request(source.uri, { ++ headers: source.headers, ++ signal: abortController.signal ++ }); ++ request.headers.append('accept', 'image/*'); ++ var promise = fetch(request).then(response => response.blob()).then(blob => { ++ uri = URL.createObjectURL(blob); ++ return uri; ++ }).catch(error => { ++ if (error.name === 'AbortError') { ++ return ''; ++ } ++ throw error; ++ }); ++ return { ++ promise, ++ source, ++ cancel: () => { ++ abortController.abort(); ++ URL.revokeObjectURL(uri); ++ } ++ }; ++ }, + prefetch(uri) { + return new Promise((resolve, reject) => { + ImageLoader.load(uri, () => { From d5982911776322d0100513709f49b56d76e8cd23 Mon Sep 17 00:00:00 2001 From: Peter Velkov Date: Fri, 24 Nov 2023 16:55:02 +0200 Subject: [PATCH 047/142] refactor(components): Exclude Auth Token for External Avatar Images This patch focuses on resolving issues encountered with avatar image loading, specifically addressing the challenges related to CORS (Cross-Origin Resource Sharing) errors. Changes: - Removed the `isAuthTokenRequired` flag from the `AttachmentModal` component in various files, including `ProfilePage.js`, `RoomHeaderAvatars.js`, and `DetailsPage.js`. This change is crucial for loading of avatar images that are hosted externally. Rationale: - The primary purpose of this modification is to streamline the loading process for avatars by removing the unnecessary inclusion of authentication tokens in requests for external images. This approach aligns with standard practices for handling externally hosted content and aims to enhance compatibility and performance. - Raised a question here as whether there are cases of avatar images that need authentication: https://github.com/Expensify/App/pull/24425/files#r1404352872 This update is expected to resolve the CORS errors associated with avatar image loading, thereby improving the overall functionality and user experience in our application. (cherry picked from commit cc4920851bed2a50c5e22cea1a2861669042e258) --- src/components/RoomHeaderAvatars.js | 2 -- src/pages/DetailsPage.js | 1 - src/pages/ProfilePage.js | 1 - 3 files changed, 4 deletions(-) diff --git a/src/components/RoomHeaderAvatars.js b/src/components/RoomHeaderAvatars.js index a3394fe71539..bf0442b69c5e 100644 --- a/src/components/RoomHeaderAvatars.js +++ b/src/components/RoomHeaderAvatars.js @@ -32,7 +32,6 @@ function RoomHeaderAvatars(props) { @@ -76,7 +75,6 @@ function RoomHeaderAvatars(props) { diff --git a/src/pages/DetailsPage.js b/src/pages/DetailsPage.js index f215b4167ab6..47ade872d25a 100755 --- a/src/pages/DetailsPage.js +++ b/src/pages/DetailsPage.js @@ -136,7 +136,6 @@ function DetailsPage(props) { {({show}) => ( diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index c0c782f176ca..5af94d48e633 100755 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -157,7 +157,6 @@ function ProfilePage(props) { From 56497e5b16c4244c3068aaea1469ff9ed78be9ff Mon Sep 17 00:00:00 2001 From: Peter Velkov Date: Tue, 5 Dec 2023 10:57:02 +0200 Subject: [PATCH 048/142] Align changes with project code styles (cherry picked from commit 1182c7d4ac0124d77bed68c2866eb0e3cfdcf7a2) --- src/components/Image/BaseImage.js | 1 + src/components/Image/index.js | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/Image/BaseImage.js b/src/components/Image/BaseImage.js index 199cbb78aab0..cd2326900c6c 100644 --- a/src/components/Image/BaseImage.js +++ b/src/components/Image/BaseImage.js @@ -24,5 +24,6 @@ function BaseImage({onLoad, ...props}) { BaseImage.propTypes = imagePropTypes; BaseImage.defaultProps = defaultProps; +BaseImage.displayName = 'BaseImage'; export default BaseImage; diff --git a/src/components/Image/index.js b/src/components/Image/index.js index 440f7ada7348..8cee1cf95e14 100644 --- a/src/components/Image/index.js +++ b/src/components/Image/index.js @@ -7,9 +7,7 @@ import BaseImage from './BaseImage'; import {defaultProps, imagePropTypes} from './imagePropTypes'; import RESIZE_MODES from './resizeModes'; -function Image(props) { - const {source: propsSource, isAuthTokenRequired, session, ...forwardedProps} = props; - +function Image({source: propsSource, isAuthTokenRequired, session, ...forwardedProps}) { // Update the source to include the auth token if required const source = useMemo(() => { if (typeof lodashGet(propsSource, 'uri') === 'number') { From 5d4609289db24ec11e12ee57e67aecfb2468ecf3 Mon Sep 17 00:00:00 2001 From: Peter Velkov Date: Mon, 15 Jan 2024 16:31:26 +0200 Subject: [PATCH 049/142] Refactor BaseImage.native.js to Align with `expo-image` Deprecation - Adapt to `expo-image` deprecation of `event.nativeEvent` usage. - Directly use the event object as recommended. - Ensure compatibility with components using the `onLoad` prop. --- src/components/Image/BaseImage.native.js | 27 +++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/components/Image/BaseImage.native.js b/src/components/Image/BaseImage.native.js index 228a455b4764..debd4b5cf606 100644 --- a/src/components/Image/BaseImage.native.js +++ b/src/components/Image/BaseImage.native.js @@ -1,4 +1,29 @@ import {Image as ExpoImage} from 'expo-image'; +import React, {useCallback} from 'react'; +import {defaultProps, imagePropTypes} from './imagePropTypes'; +function BaseImage({onLoad, ...props}) { + const imageLoadedSuccessfully = useCallback( + (event) => { + // We override `onLoad`, so both web and native have the same signature + const {width, height} = event.source; + onLoad({nativeEvent: {width, height}}); + }, + [onLoad], + ); -export default ExpoImage; + return ( + + ); +} + +BaseImage.propTypes = imagePropTypes; +BaseImage.defaultProps = defaultProps; +BaseImage.displayName = 'BaseImage'; + +export default BaseImage; From d51126fba808f92f965eec29f48b05e5f66bf05e Mon Sep 17 00:00:00 2001 From: Peter Velkov Date: Mon, 15 Jan 2024 21:09:17 +0200 Subject: [PATCH 050/142] Convert new image files to typescript --- .../{BaseImage.native.js => BaseImage.native.tsx} | 15 +++++++++------ .../Image/{BaseImage.js => BaseImage.tsx} | 15 +++++++++------ src/components/Image/types.ts | 6 ++++++ 3 files changed, 24 insertions(+), 12 deletions(-) rename src/components/Image/{BaseImage.native.js => BaseImage.native.tsx} (71%) rename src/components/Image/{BaseImage.js => BaseImage.tsx} (68%) create mode 100644 src/components/Image/types.ts diff --git a/src/components/Image/BaseImage.native.js b/src/components/Image/BaseImage.native.tsx similarity index 71% rename from src/components/Image/BaseImage.native.js rename to src/components/Image/BaseImage.native.tsx index debd4b5cf606..acde64a78330 100644 --- a/src/components/Image/BaseImage.native.js +++ b/src/components/Image/BaseImage.native.tsx @@ -1,10 +1,15 @@ import {Image as ExpoImage} from 'expo-image'; -import React, {useCallback} from 'react'; -import {defaultProps, imagePropTypes} from './imagePropTypes'; +import type {ImageLoadEventData} from 'expo-image'; +import {useCallback} from 'react'; +import type ImageProps from './types'; -function BaseImage({onLoad, ...props}) { +function BaseImage({onLoad, ...props}: ImageProps) { const imageLoadedSuccessfully = useCallback( - (event) => { + (event: ImageLoadEventData) => { + if (!onLoad) { + return; + } + // We override `onLoad`, so both web and native have the same signature const {width, height} = event.source; onLoad({nativeEvent: {width, height}}); @@ -22,8 +27,6 @@ function BaseImage({onLoad, ...props}) { ); } -BaseImage.propTypes = imagePropTypes; -BaseImage.defaultProps = defaultProps; BaseImage.displayName = 'BaseImage'; export default BaseImage; diff --git a/src/components/Image/BaseImage.js b/src/components/Image/BaseImage.tsx similarity index 68% rename from src/components/Image/BaseImage.js rename to src/components/Image/BaseImage.tsx index cd2326900c6c..7311b41ac244 100644 --- a/src/components/Image/BaseImage.js +++ b/src/components/Image/BaseImage.tsx @@ -1,12 +1,17 @@ import React, {useCallback} from 'react'; import {Image as RNImage} from 'react-native'; -import {defaultProps, imagePropTypes} from './imagePropTypes'; +import type {ImageLoadEventData} from 'react-native'; +import type ImageProps from './types'; -function BaseImage({onLoad, ...props}) { +function BaseImage({onLoad, ...props}: ImageProps) { const imageLoadedSuccessfully = useCallback( - ({nativeEvent}) => { + (event: {nativeEvent: ImageLoadEventData}) => { + if (!onLoad) { + return; + } + // We override `onLoad`, so both web and native have the same signature - const {width, height} = nativeEvent.source; + const {width, height} = event.nativeEvent.source; onLoad({nativeEvent: {width, height}}); }, [onLoad], @@ -22,8 +27,6 @@ function BaseImage({onLoad, ...props}) { ); } -BaseImage.propTypes = imagePropTypes; -BaseImage.defaultProps = defaultProps; BaseImage.displayName = 'BaseImage'; export default BaseImage; diff --git a/src/components/Image/types.ts b/src/components/Image/types.ts new file mode 100644 index 000000000000..b4cde825a004 --- /dev/null +++ b/src/components/Image/types.ts @@ -0,0 +1,6 @@ +type ImageProps = { + /** Event called with image dimensions when image is loaded */ + onLoad?: (event: {nativeEvent: {width: number; height: number}}) => void; +}; + +export default ImageProps; From 48a0d83ac054b8066825e150ae33f068da17792e Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 16 Jan 2024 12:14:28 +0500 Subject: [PATCH 051/142] fix: reply not shown --- src/pages/home/report/ReportActionsListItemRenderer.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pages/home/report/ReportActionsListItemRenderer.js b/src/pages/home/report/ReportActionsListItemRenderer.js index 3c58c4e73935..00630a72d156 100644 --- a/src/pages/home/report/ReportActionsListItemRenderer.js +++ b/src/pages/home/report/ReportActionsListItemRenderer.js @@ -75,6 +75,8 @@ function ReportActionsListItemRenderer({ error: reportAction.error, created: reportAction.created, actorAccountID: reportAction.actorAccountID, + childVisibleActionCount: reportAction.childVisibleActionCount, + childOldestFourAccountIDs: reportAction.childOldestFourAccountIDs, }), [ reportAction.actionName, @@ -91,6 +93,8 @@ function ReportActionsListItemRenderer({ reportAction.reportActionID, reportAction.whisperedToAccountIDs, reportAction.actorAccountID, + reportAction.childVisibleActionCount, + reportAction.childOldestFourAccountIDs, ], ); From baf9b3e368b4d4e288ccd70d371bd8c0c3d62723 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 17 Jan 2024 12:19:41 +0500 Subject: [PATCH 052/142] fix: add missing reportAction properties --- .../report/ReportActionsListItemRenderer.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/pages/home/report/ReportActionsListItemRenderer.js b/src/pages/home/report/ReportActionsListItemRenderer.js index 00630a72d156..b28a90f496ce 100644 --- a/src/pages/home/report/ReportActionsListItemRenderer.js +++ b/src/pages/home/report/ReportActionsListItemRenderer.js @@ -77,6 +77,17 @@ function ReportActionsListItemRenderer({ actorAccountID: reportAction.actorAccountID, childVisibleActionCount: reportAction.childVisibleActionCount, childOldestFourAccountIDs: reportAction.childOldestFourAccountIDs, + childType: reportAction.childType, + person: reportAction.person, + isOptimisticAction: reportAction.isOptimisticAction, + delegateAccountID: reportAction.delegateAccountID, + previousMessage: reportAction.previousMessage, + attachmentInfo: reportAction.attachmentInfo, + childStateNum: reportAction.childStateNum, + childStatusNum: reportAction.childStatusNum, + childReportName: reportAction.childReportName, + childManagerAccountID: reportAction.childManagerAccountID, + childMoneyRequestCount: reportAction.childMoneyRequestCount, }), [ reportAction.actionName, @@ -95,6 +106,17 @@ function ReportActionsListItemRenderer({ reportAction.actorAccountID, reportAction.childVisibleActionCount, reportAction.childOldestFourAccountIDs, + reportAction.person, + reportAction.isOptimisticAction, + reportAction.childType, + reportAction.delegateAccountID, + reportAction.previousMessage, + reportAction.attachmentInfo, + reportAction.childStateNum, + reportAction.childStatusNum, + reportAction.childReportName, + reportAction.childManagerAccountID, + reportAction.childMoneyRequestCount, ], ); From 470eb7b0956ab8f4bb20b31066b0272f9fcde74f Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 18 Jan 2024 12:34:03 +0500 Subject: [PATCH 053/142] fix: hide reply and task welcome message and add the missing fields --- src/pages/home/report/ReportActionsView.js | 53 +++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 4bed2d7ffb86..990c5e44ff77 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -256,8 +256,59 @@ function ReportActionsView(props) { reportID: props.report.reportID, policyID: props.report.policyID, lastVisibleActionCreated: props.report.lastVisibleActionCreated, + statusNum: props.report.statusNum, + stateNum: props.report.stateNum, + writeCapability: props.report.writeCapability, + type: props.report.type, + errorFields: props.report.errorFields, + isPolicyExpenseChat: props.report.isPolicyExpenseChat, + parentReportID: props.report.parentReportID, + parentReportActionID: props.report.parentReportActionID, + chatType: props.report.chatType, + pendingFields: props.report.pendingFields, + isDeletedParentAction: props.report.isDeletedParentAction, + reportName: props.report.reportName, + description: props.report.description, + managerID: props.report.managerID, + total: props.report.total, + nonReimbursableTotal: props.report.nonReimbursableTotal, + reportFields: props.report.reportFields, + ownerAccountID: props.report.ownerAccountID, + currency: props.report.currency, + participantAccountIDs: props.report.participantAccountIDs, + isWaitingOnBankAccount: props.report.isWaitingOnBankAccount, + iouReportID: props.report.iouReportID, + isOwnPolicyExpenseChat: props.report.isOwnPolicyExpenseChat, }), - [props.report.lastReadTime, props.report.reportID, props.report.policyID, props.report.lastVisibleActionCreated], + [ + props.report.lastReadTime, + props.report.reportID, + props.report.policyID, + props.report.lastVisibleActionCreated, + props.report.statusNum, + props.report.stateNum, + props.report.writeCapability, + props.report.type, + props.report.errorFields, + props.report.isPolicyExpenseChat, + props.report.parentReportID, + props.report.parentReportActionID, + props.report.chatType, + props.report.pendingFields, + props.report.isDeletedParentAction, + props.report.reportName, + props.report.description, + props.report.managerID, + props.report.total, + props.report.nonReimbursableTotal, + props.report.reportFields, + props.report.ownerAccountID, + props.report.currency, + props.report.participantAccountIDs, + props.report.isWaitingOnBankAccount, + props.report.iouReportID, + props.report.isOwnPolicyExpenseChat, + ], ); // Comments have not loaded at all yet do nothing From efd81f5906e2b60d5f60abbed5d2ecdf57860f85 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 18 Jan 2024 12:34:25 +0500 Subject: [PATCH 054/142] refactor: remove not used prop --- src/pages/home/report/ReportActionItem.js | 1 - src/pages/home/report/ReportActionItemMessageEdit.tsx | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 9573d4a4ff1a..a59b9d268e76 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -502,7 +502,6 @@ function ReportActionItem(props) { reportID={props.report.reportID} index={props.index} ref={textInputRef} - report={props.report} // Avoid defining within component due to an existing Onyx bug preferredSkinTone={props.preferredSkinTone} shouldDisableEmojiPicker={ diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 5934c4c333cb..e97aa0338f90 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -52,10 +52,6 @@ type ReportActionItemMessageEditProps = { /** Position index of the report action in the overall report FlatList view */ index: number; - /** The report currently being looked at */ - // eslint-disable-next-line react/no-unused-prop-types - report?: OnyxTypes.Report; - /** Whether or not the emoji picker is disabled */ shouldDisableEmojiPicker?: boolean; From 722910dad6685d350bd7ce11d3e641ce0bc0faf1 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 19 Jan 2024 16:07:06 +0500 Subject: [PATCH 055/142] fix: local time not displayed --- src/pages/home/report/ReportFooter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/home/report/ReportFooter.js b/src/pages/home/report/ReportFooter.js index 8f4906a32aa9..6e6a849658ae 100644 --- a/src/pages/home/report/ReportFooter.js +++ b/src/pages/home/report/ReportFooter.js @@ -134,6 +134,7 @@ function ReportFooter(props) { Date: Fri, 19 Jan 2024 16:10:09 +0500 Subject: [PATCH 056/142] refactor: move memoized report object to report screen --- src/pages/home/ReportScreen.js | 44 +++++++++++++- src/pages/home/report/ReportActionsView.js | 67 +--------------------- 2 files changed, 44 insertions(+), 67 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index b238bcf74d8a..00136cea43b1 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -140,7 +140,7 @@ function getReportID(route) { function ReportScreen({ betas, route, - report, + report: reportProp, reportMetadata, reportActions, parentReportAction, @@ -162,6 +162,48 @@ function ReportScreen({ const firstRenderRef = useRef(true); const flatListRef = useRef(); const reactionListRef = useRef(); + /** + * Create a lightweight Report so as to keep the re-rendering as light as possible by + * passing in only the required props. + * + * Also, this plays nicely in contrast with Onyx, + * which creates a new object every time collection changes. Because of this we can't + * put this into onyx selector as it will be the same. + */ + const report = useMemo( + () => ({ + lastReadTime: reportProp.lastReadTime, + reportID: reportProp.reportID, + policyID: reportProp.policyID, + lastVisibleActionCreated: reportProp.lastVisibleActionCreated, + statusNum: reportProp.statusNum, + stateNum: reportProp.stateNum, + writeCapability: reportProp.writeCapability, + type: reportProp.type, + errorFields: reportProp.errorFields, + isPolicyExpenseChat: reportProp.isPolicyExpenseChat, + parentReportID: reportProp.parentReportID, + parentReportActionID: reportProp.parentReportActionID, + chatType: reportProp.chatType, + pendingFields: reportProp.pendingFields, + isDeletedParentAction: reportProp.isDeletedParentAction, + reportName: reportProp.reportName, + description: reportProp.description, + managerID: reportProp.managerID, + total: reportProp.total, + nonReimbursableTotal: reportProp.nonReimbursableTotal, + reportFields: reportProp.reportFields, + ownerAccountID: reportProp.ownerAccountID, + currency: reportProp.currency, + participantAccountIDs: reportProp.participantAccountIDs, + isWaitingOnBankAccount: reportProp.isWaitingOnBankAccount, + iouReportID: reportProp.iouReportID, + isOwnPolicyExpenseChat: reportProp.isOwnPolicyExpenseChat, + notificationPreference: reportProp.notificationPreference, + }), + [reportProp.lastReadTime, reportProp.reportID, reportProp.policyID, reportProp.lastVisibleActionCreated, reportProp.statusNum, reportProp.stateNum, reportProp.writeCapability, reportProp.type, reportProp.errorFields, reportProp.isPolicyExpenseChat, reportProp.parentReportID, reportProp.parentReportActionID, reportProp.chatType, reportProp.pendingFields, reportProp.isDeletedParentAction, reportProp.reportName, reportProp.description, reportProp.managerID, reportProp.total, reportProp.nonReimbursableTotal, reportProp.reportFields, reportProp.ownerAccountID, reportProp.currency, reportProp.participantAccountIDs, reportProp.isWaitingOnBankAccount, reportProp.iouReportID, reportProp.isOwnPolicyExpenseChat, reportProp.notificationPreference], + ); + const prevReport = usePrevious(report); const prevUserLeavingStatus = usePrevious(userLeavingStatus); const [isBannerVisible, setIsBannerVisible] = useState(true); diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 990c5e44ff77..9bd8551ba7b3 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -246,71 +246,6 @@ function ReportActionsView(props) { } }; - /** - * Create a lightweight Report so as to keep the re-rendering as light as possible by - * passing in only the required props. - */ - const report = useMemo( - () => ({ - lastReadTime: props.report.lastReadTime, - reportID: props.report.reportID, - policyID: props.report.policyID, - lastVisibleActionCreated: props.report.lastVisibleActionCreated, - statusNum: props.report.statusNum, - stateNum: props.report.stateNum, - writeCapability: props.report.writeCapability, - type: props.report.type, - errorFields: props.report.errorFields, - isPolicyExpenseChat: props.report.isPolicyExpenseChat, - parentReportID: props.report.parentReportID, - parentReportActionID: props.report.parentReportActionID, - chatType: props.report.chatType, - pendingFields: props.report.pendingFields, - isDeletedParentAction: props.report.isDeletedParentAction, - reportName: props.report.reportName, - description: props.report.description, - managerID: props.report.managerID, - total: props.report.total, - nonReimbursableTotal: props.report.nonReimbursableTotal, - reportFields: props.report.reportFields, - ownerAccountID: props.report.ownerAccountID, - currency: props.report.currency, - participantAccountIDs: props.report.participantAccountIDs, - isWaitingOnBankAccount: props.report.isWaitingOnBankAccount, - iouReportID: props.report.iouReportID, - isOwnPolicyExpenseChat: props.report.isOwnPolicyExpenseChat, - }), - [ - props.report.lastReadTime, - props.report.reportID, - props.report.policyID, - props.report.lastVisibleActionCreated, - props.report.statusNum, - props.report.stateNum, - props.report.writeCapability, - props.report.type, - props.report.errorFields, - props.report.isPolicyExpenseChat, - props.report.parentReportID, - props.report.parentReportActionID, - props.report.chatType, - props.report.pendingFields, - props.report.isDeletedParentAction, - props.report.reportName, - props.report.description, - props.report.managerID, - props.report.total, - props.report.nonReimbursableTotal, - props.report.reportFields, - props.report.ownerAccountID, - props.report.currency, - props.report.participantAccountIDs, - props.report.isWaitingOnBankAccount, - props.report.iouReportID, - props.report.isOwnPolicyExpenseChat, - ], - ); - // Comments have not loaded at all yet do nothing if (!_.size(props.reportActions)) { return null; @@ -319,7 +254,7 @@ function ReportActionsView(props) { return ( <> Date: Sat, 20 Jan 2024 12:21:43 +0530 Subject: [PATCH 057/142] fix: Selecting last possible member to group hides selected members. Signed-off-by: Krishna Gupta --- src/libs/OptionsListUtils.js | 14 +++++++++-- src/pages/NewChatPage.js | 24 +++++++++++++++---- ...yForRefactorRequestParticipantsSelector.js | 16 ++++++++----- .../MoneyRequestParticipantsSelector.js | 14 +++++++---- 4 files changed, 51 insertions(+), 17 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index e86c9daacb42..bc4894387786 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -1941,13 +1941,23 @@ function shouldOptionShowTooltip(option) { * @param {Object} personalDetails * @param {Boolean} shouldGetOptionDetails * @param {Number} indexOffset + * @param {Boolean} maxOptionsSelected * @returns {Object} */ -function formatSectionsFromSearchTerm(searchTerm, selectedOptions, filteredRecentReports, filteredPersonalDetails, personalDetails = {}, shouldGetOptionDetails = false, indexOffset) { +function formatSectionsFromSearchTerm( + searchTerm, + selectedOptions, + filteredRecentReports, + filteredPersonalDetails, + personalDetails = {}, + shouldGetOptionDetails = false, + indexOffset, + maxOptionsSelected, +) { // We show the selected participants at the top of the list when there is no search term // However, if there is a search term we remove the selected participants from the top of the list unless they are part of the search results // This clears up space on mobile views, where if you create a group with 4+ people you can't see the selected participants and the search results at the same time - if (searchTerm === '') { + if (searchTerm === '' || maxOptionsSelected) { return { section: { title: undefined, diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index b90ce6bbc247..98d2ecbd4b9f 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -77,7 +77,16 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i const sectionsList = []; let indexOffset = 0; - const formatResults = OptionsListUtils.formatSectionsFromSearchTerm(searchTerm, selectedOptions, filteredRecentReports, filteredPersonalDetails, {}, false, indexOffset); + const formatResults = OptionsListUtils.formatSectionsFromSearchTerm( + searchTerm, + selectedOptions, + filteredRecentReports, + filteredPersonalDetails, + {}, + false, + indexOffset, + maxParticipantsReached, + ); sectionsList.push(formatResults.section); indexOffset = formatResults.newIndexOffset; @@ -230,10 +239,15 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i }, [didScreenTransitionEnd, updateOptions]); // When search term updates we will fetch any reports - const setSearchTermAndSearchInServer = useCallback((text = '') => { - Report.searchInServer(text); - setSearchTerm(text); - }, []); + const setSearchTermAndSearchInServer = useCallback( + (text = '') => { + if (text && !maxParticipantsReached) { + Report.searchInServer(text); + } + setSearchTerm(text); + }, + [maxParticipantsReached], + ); const {inputCallbackRef} = useAutoFocusInput(); diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 0949081435c4..df863469f1db 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -131,6 +131,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ personalDetails, true, indexOffset, + maxParticipantsReached, ); newSections.push(formatResults.section); indexOffset = formatResults.newIndexOffset; @@ -239,12 +240,15 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ ); // When search term updates we will fetch any reports - const setSearchTermAndSearchInServer = useCallback((text = '') => { - if (text.length) { - Report.searchInServer(text); - } - setSearchTerm(text); - }, []); + const setSearchTermAndSearchInServer = useCallback( + (text = '') => { + if (text.length && !maxParticipantsReached) { + Report.searchInServer(text); + } + setSearchTerm(text); + }, + [maxParticipantsReached], + ); // Right now you can't split a request with a workspace and other additional participants // This is getting properly fixed in https://github.com/Expensify/App/issues/27508, but as a stop-gap to prevent diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 9567b17ecdf5..7700387996c8 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -142,6 +142,7 @@ function MoneyRequestParticipantsSelector({ personalDetails, true, indexOffset, + maxParticipantsReached, ); newSections.push(formatResults.section); indexOffset = formatResults.newIndexOffset; @@ -259,10 +260,15 @@ function MoneyRequestParticipantsSelector({ ); // When search term updates we will fetch any reports - const setSearchTermAndSearchInServer = useCallback((text = '') => { - Report.searchInServer(text); - setSearchTerm(text); - }, []); + const setSearchTermAndSearchInServer = useCallback( + (text = '') => { + if (text && !maxParticipantsReached) { + Report.searchInServer(text); + } + setSearchTerm(text); + }, + [maxParticipantsReached], + ); // Right now you can't split a request with a workspace and other additional participants // This is getting properly fixed in https://github.com/Expensify/App/issues/27508, but as a stop-gap to prevent From 1176d22ec08825d50ef1cdbe3050fcff8cb0e255 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sat, 20 Jan 2024 12:26:25 +0530 Subject: [PATCH 058/142] comment added. Signed-off-by: Krishna Gupta --- src/libs/OptionsListUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index bc4894387786..c7db5867036e 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -1954,7 +1954,7 @@ function formatSectionsFromSearchTerm( indexOffset, maxOptionsSelected, ) { - // We show the selected participants at the top of the list when there is no search term + // We show the selected participants at the top of the list when there is no search term or maximum number of participants has already been selected // However, if there is a search term we remove the selected participants from the top of the list unless they are part of the search results // This clears up space on mobile views, where if you create a group with 4+ people you can't see the selected participants and the search results at the same time if (searchTerm === '' || maxOptionsSelected) { From c6b3b2355f7b1abde5028139c5133a367fb6ef14 Mon Sep 17 00:00:00 2001 From: Peter Velkov Date: Mon, 22 Jan 2024 21:38:21 +0200 Subject: [PATCH 059/142] Add `BaseImage` specific props --- src/components/Image/BaseImage.native.tsx | 4 ++-- src/components/Image/BaseImage.tsx | 4 ++-- src/components/Image/types.ts | 6 ++++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/Image/BaseImage.native.tsx b/src/components/Image/BaseImage.native.tsx index acde64a78330..3be1933af50c 100644 --- a/src/components/Image/BaseImage.native.tsx +++ b/src/components/Image/BaseImage.native.tsx @@ -1,9 +1,9 @@ import {Image as ExpoImage} from 'expo-image'; import type {ImageLoadEventData} from 'expo-image'; import {useCallback} from 'react'; -import type ImageProps from './types'; +import type {BaseImageProps} from './types'; -function BaseImage({onLoad, ...props}: ImageProps) { +function BaseImage({onLoad, ...props}: BaseImageProps) { const imageLoadedSuccessfully = useCallback( (event: ImageLoadEventData) => { if (!onLoad) { diff --git a/src/components/Image/BaseImage.tsx b/src/components/Image/BaseImage.tsx index 7311b41ac244..9585b3c18e3f 100644 --- a/src/components/Image/BaseImage.tsx +++ b/src/components/Image/BaseImage.tsx @@ -1,9 +1,9 @@ import React, {useCallback} from 'react'; import {Image as RNImage} from 'react-native'; import type {ImageLoadEventData} from 'react-native'; -import type ImageProps from './types'; +import type {BaseImageProps} from './types'; -function BaseImage({onLoad, ...props}: ImageProps) { +function BaseImage({onLoad, ...props}: BaseImageProps) { const imageLoadedSuccessfully = useCallback( (event: {nativeEvent: ImageLoadEventData}) => { if (!onLoad) { diff --git a/src/components/Image/types.ts b/src/components/Image/types.ts index b4cde825a004..5a4c94364a46 100644 --- a/src/components/Image/types.ts +++ b/src/components/Image/types.ts @@ -1,6 +1,8 @@ -type ImageProps = { +type BaseImageProps = { /** Event called with image dimensions when image is loaded */ onLoad?: (event: {nativeEvent: {width: number; height: number}}) => void; }; -export default ImageProps; +export type {BaseImageProps}; + +export default BaseImageProps; From 89c96b1a3308782bc2489ba8bc9c9c683b855308 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 23 Jan 2024 16:28:18 +0530 Subject: [PATCH 060/142] minor fix. Signed-off-by: Krishna Gupta --- .../MoneyTemporaryForRefactorRequestParticipantsSelector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index df863469f1db..552d791ac856 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -242,7 +242,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ // When search term updates we will fetch any reports const setSearchTermAndSearchInServer = useCallback( (text = '') => { - if (text.length && !maxParticipantsReached) { + if (text && !maxParticipantsReached) { Report.searchInServer(text); } setSearchTerm(text); From 4487cccc9dfd6d17df9da53feb39957d19fe3618 Mon Sep 17 00:00:00 2001 From: Peter Velkov Date: Tue, 23 Jan 2024 16:16:32 +0200 Subject: [PATCH 061/142] Extend BaseImage props to include native image properties --- src/components/Image/BaseImage.native.tsx | 4 ++-- src/components/Image/BaseImage.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Image/BaseImage.native.tsx b/src/components/Image/BaseImage.native.tsx index 3be1933af50c..c517efd04515 100644 --- a/src/components/Image/BaseImage.native.tsx +++ b/src/components/Image/BaseImage.native.tsx @@ -1,9 +1,9 @@ import {Image as ExpoImage} from 'expo-image'; -import type {ImageLoadEventData} from 'expo-image'; +import type {ImageProps as ExpoImageProps, ImageLoadEventData} from 'expo-image'; import {useCallback} from 'react'; import type {BaseImageProps} from './types'; -function BaseImage({onLoad, ...props}: BaseImageProps) { +function BaseImage({onLoad, ...props}: ExpoImageProps & BaseImageProps) { const imageLoadedSuccessfully = useCallback( (event: ImageLoadEventData) => { if (!onLoad) { diff --git a/src/components/Image/BaseImage.tsx b/src/components/Image/BaseImage.tsx index 9585b3c18e3f..ebdd76840267 100644 --- a/src/components/Image/BaseImage.tsx +++ b/src/components/Image/BaseImage.tsx @@ -1,9 +1,9 @@ import React, {useCallback} from 'react'; import {Image as RNImage} from 'react-native'; -import type {ImageLoadEventData} from 'react-native'; +import type {ImageLoadEventData, ImageProps as WebImageProps} from 'react-native'; import type {BaseImageProps} from './types'; -function BaseImage({onLoad, ...props}: BaseImageProps) { +function BaseImage({onLoad, ...props}: WebImageProps & BaseImageProps) { const imageLoadedSuccessfully = useCallback( (event: {nativeEvent: ImageLoadEventData}) => { if (!onLoad) { From 4f87b179df5eb79621c333a0607cea84b9610ead Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 23 Jan 2024 15:26:10 +0100 Subject: [PATCH 062/142] [TS migration] Migrate 'WorkspaceBills' page --- ...tion.js => WorkspaceBillsFirstSection.tsx} | 76 ++++++++----------- ...VBAView.js => WorkspaceBillsNoVBAView.tsx} | 30 ++++---- .../workspace/bills/WorkspaceBillsPage.js | 42 ---------- .../workspace/bills/WorkspaceBillsPage.tsx | 35 +++++++++ ...lsVBAView.js => WorkspaceBillsVBAView.tsx} | 28 ++++--- 5 files changed, 93 insertions(+), 118 deletions(-) rename src/pages/workspace/bills/{WorkspaceBillsFirstSection.js => WorkspaceBillsFirstSection.tsx} (54%) rename src/pages/workspace/bills/{WorkspaceBillsNoVBAView.js => WorkspaceBillsNoVBAView.tsx} (50%) delete mode 100644 src/pages/workspace/bills/WorkspaceBillsPage.js create mode 100644 src/pages/workspace/bills/WorkspaceBillsPage.tsx rename src/pages/workspace/bills/{WorkspaceBillsVBAView.js => WorkspaceBillsVBAView.tsx} (59%) diff --git a/src/pages/workspace/bills/WorkspaceBillsFirstSection.js b/src/pages/workspace/bills/WorkspaceBillsFirstSection.tsx similarity index 54% rename from src/pages/workspace/bills/WorkspaceBillsFirstSection.js rename to src/pages/workspace/bills/WorkspaceBillsFirstSection.tsx index 3f7f59180c8b..5931e8b14843 100644 --- a/src/pages/workspace/bills/WorkspaceBillsFirstSection.js +++ b/src/pages/workspace/bills/WorkspaceBillsFirstSection.tsx @@ -1,56 +1,47 @@ import Str from 'expensify-common/lib/str'; -import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import CopyTextToClipboard from '@components/CopyTextToClipboard'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import Section from '@components/Section'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; -import userPropTypes from '@pages/settings/userPropTypes'; import * as Link from '@userActions/Link'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Session, User} from '@src/types/onyx'; -const propTypes = { - /** The policy ID currently being configured */ - policyID: PropTypes.string.isRequired, - - ...withLocalizePropTypes, - - /* From Onyx */ +type WorkspaceBillsFirstSectionOnyxProps = { /** Session of currently logged in user */ - session: PropTypes.shape({ - /** Email address */ - email: PropTypes.string.isRequired, - }), + session: OnyxEntry; /** Information about the logged in user's account */ - user: userPropTypes, + user: OnyxEntry; }; -const defaultProps = { - session: { - email: null, - }, - user: {}, +type WorkspaceBillsFirstSectionProps = WorkspaceBillsFirstSectionOnyxProps & { + /** The policy ID currently being configured */ + policyID: string; }; -function WorkspaceBillsFirstSection(props) { +function WorkspaceBillsFirstSection({session, policyID, user}: WorkspaceBillsFirstSectionProps) { const styles = useThemeStyles(); - const emailDomain = Str.extractEmailDomain(props.session.email); - const manageYourBillsUrl = `reports?policyID=${props.policyID}&from=all&type=bill&showStates=Open,Processing,Approved,Reimbursed,Archived&isAdvancedFilterMode=true`; + const {translate} = useLocalize(); + + const emailDomain = Str.extractEmailDomain(session?.email ?? ''); + const manageYourBillsUrl = `reports?policyID=${policyID}&from=all&type=bill&showStates=Open,Processing,Approved,Reimbursed,Archived&isAdvancedFilterMode=true`; + return (
Link.openOldDotLink(manageYourBillsUrl), icon: Expensicons.Bill, shouldShowRightIcon: true, @@ -59,40 +50,35 @@ function WorkspaceBillsFirstSection(props) { link: () => Link.buildOldDotURL(manageYourBillsUrl), }, ]} - containerStyles={[styles.cardSection]} + containerStyles={styles.cardSection} > - + - {props.translate('workspace.bills.askYourVendorsBeforeEmail')} - {props.user.isFromPublicDomain ? ( + {translate('workspace.bills.askYourVendorsBeforeEmail')} + {user?.isFromPublicDomain ? ( Link.openExternalLink('https://community.expensify.com/discussion/7500/how-to-pay-your-company-bills-in-expensify/')}> example.com@expensify.cash ) : ( )} - {props.translate('workspace.bills.askYourVendorsAfterEmail')} + {translate('workspace.bills.askYourVendorsAfterEmail')}
); } -WorkspaceBillsFirstSection.propTypes = propTypes; -WorkspaceBillsFirstSection.defaultProps = defaultProps; WorkspaceBillsFirstSection.displayName = 'WorkspaceBillsFirstSection'; -export default compose( - withLocalize, - withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - user: { - key: ONYXKEYS.USER, - }, - }), -)(WorkspaceBillsFirstSection); +export default withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, + user: { + key: ONYXKEYS.USER, + }, +})(WorkspaceBillsFirstSection); diff --git a/src/pages/workspace/bills/WorkspaceBillsNoVBAView.js b/src/pages/workspace/bills/WorkspaceBillsNoVBAView.tsx similarity index 50% rename from src/pages/workspace/bills/WorkspaceBillsNoVBAView.js rename to src/pages/workspace/bills/WorkspaceBillsNoVBAView.tsx index 8211f70163d5..72f4d0ad2b8e 100644 --- a/src/pages/workspace/bills/WorkspaceBillsNoVBAView.js +++ b/src/pages/workspace/bills/WorkspaceBillsNoVBAView.tsx @@ -1,45 +1,43 @@ -import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; import ConnectBankAccountButton from '@components/ConnectBankAccountButton'; import * as Illustrations from '@components/Icon/Illustrations'; import Section from '@components/Section'; import Text from '@components/Text'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import WorkspaceBillsFirstSection from './WorkspaceBillsFirstSection'; -const propTypes = { +type WorkspaceBillsNoVBAViewProps = { /** The policy ID currently being configured */ - policyID: PropTypes.string.isRequired, - - ...withLocalizePropTypes, + policyID: string; }; -function WorkspaceBillsNoVBAView(props) { +function WorkspaceBillsNoVBAView({policyID}: WorkspaceBillsNoVBAViewProps) { const styles = useThemeStyles(); + const {translate} = useLocalize(); + return ( <> - +
- - {props.translate('workspace.bills.unlockNoVBACopy')} + + {translate('workspace.bills.unlockNoVBACopy')}
); } -WorkspaceBillsNoVBAView.propTypes = propTypes; WorkspaceBillsNoVBAView.displayName = 'WorkspaceBillsNoVBAView'; -export default withLocalize(WorkspaceBillsNoVBAView); +export default WorkspaceBillsNoVBAView; diff --git a/src/pages/workspace/bills/WorkspaceBillsPage.js b/src/pages/workspace/bills/WorkspaceBillsPage.js deleted file mode 100644 index c607071a4365..000000000000 --- a/src/pages/workspace/bills/WorkspaceBillsPage.js +++ /dev/null @@ -1,42 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; -import CONST from '@src/CONST'; -import WorkspaceBillsNoVBAView from './WorkspaceBillsNoVBAView'; -import WorkspaceBillsVBAView from './WorkspaceBillsVBAView'; - -const propTypes = { - /** The route object passed to this page from the navigator */ - route: PropTypes.shape({ - /** Each parameter passed via the URL */ - params: PropTypes.shape({ - /** The policyID that is being configured */ - policyID: PropTypes.string.isRequired, - }).isRequired, - }).isRequired, - - ...withLocalizePropTypes, -}; - -function WorkspaceBillsPage(props) { - return ( - - {(hasVBA, policyID) => ( - <> - {!hasVBA && } - {hasVBA && } - - )} - - ); -} - -WorkspaceBillsPage.propTypes = propTypes; -WorkspaceBillsPage.displayName = 'WorkspaceBillsPage'; -export default withLocalize(WorkspaceBillsPage); diff --git a/src/pages/workspace/bills/WorkspaceBillsPage.tsx b/src/pages/workspace/bills/WorkspaceBillsPage.tsx new file mode 100644 index 000000000000..da86839f39c1 --- /dev/null +++ b/src/pages/workspace/bills/WorkspaceBillsPage.tsx @@ -0,0 +1,35 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import useLocalize from '@hooks/useLocalize'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; +import CONST from '@src/CONST'; +import type SCREENS from '@src/SCREENS'; +import WorkspaceBillsNoVBAView from './WorkspaceBillsNoVBAView'; +import WorkspaceBillsVBAView from './WorkspaceBillsVBAView'; + +type WorkspaceBillsPageProps = StackScreenProps; + +function WorkspaceBillsPage({route}: WorkspaceBillsPageProps) { + const {translate} = useLocalize(); + + return ( + + {(hasVBA: boolean, policyID: string) => ( + <> + {!hasVBA && } + {hasVBA && } + + )} + + ); +} + +WorkspaceBillsPage.displayName = 'WorkspaceBillsPage'; + +export default WorkspaceBillsPage; diff --git a/src/pages/workspace/bills/WorkspaceBillsVBAView.js b/src/pages/workspace/bills/WorkspaceBillsVBAView.tsx similarity index 59% rename from src/pages/workspace/bills/WorkspaceBillsVBAView.js rename to src/pages/workspace/bills/WorkspaceBillsVBAView.tsx index dd9c1c7fbaf6..ae06fac378dd 100644 --- a/src/pages/workspace/bills/WorkspaceBillsVBAView.js +++ b/src/pages/workspace/bills/WorkspaceBillsVBAView.tsx @@ -1,36 +1,35 @@ -import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import Section from '@components/Section'; import Text from '@components/Text'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Link from '@userActions/Link'; import WorkspaceBillsFirstSection from './WorkspaceBillsFirstSection'; -const propTypes = { +type WorkspaceBillsVBAViewProps = { /** The policy ID currently being configured */ - policyID: PropTypes.string.isRequired, - - ...withLocalizePropTypes, + policyID: string; }; -function WorkspaceBillsVBAView(props) { +function WorkspaceBillsVBAView({policyID}: WorkspaceBillsVBAViewProps) { const styles = useThemeStyles(); - const reportsUrl = `reports?policyID=${props.policyID}&from=all&type=bill&showStates=Processing,Approved&isAdvancedFilterMode=true`; + const {translate} = useLocalize(); + + const reportsUrl = `reports?policyID=${policyID}&from=all&type=bill&showStates=Processing,Approved&isAdvancedFilterMode=true`; return ( <> - +
Link.openOldDotLink(reportsUrl), icon: Expensicons.Bill, shouldShowRightIcon: true, @@ -40,15 +39,14 @@ function WorkspaceBillsVBAView(props) { }, ]} > - - {props.translate('workspace.bills.VBACopy')} + + {translate('workspace.bills.VBACopy')}
); } -WorkspaceBillsVBAView.propTypes = propTypes; WorkspaceBillsVBAView.displayName = 'WorkspaceBillsVBAView'; -export default withLocalize(WorkspaceBillsVBAView); +export default WorkspaceBillsVBAView; From d3a6b8cacbf2d83b1b0a67e0f332be6ebfd4f725 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 23 Jan 2024 16:36:51 +0100 Subject: [PATCH 063/142] Update PolicyRoute type --- src/pages/workspace/WorkspacePageWithSections.tsx | 6 +++--- src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx | 9 ++++----- src/pages/workspace/withPolicy.tsx | 8 ++++++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/pages/workspace/WorkspacePageWithSections.tsx b/src/pages/workspace/WorkspacePageWithSections.tsx index 8817f813a990..a469e5bd4dff 100644 --- a/src/pages/workspace/WorkspacePageWithSections.tsx +++ b/src/pages/workspace/WorkspacePageWithSections.tsx @@ -1,4 +1,3 @@ -import type {RouteProp} from '@react-navigation/native'; import React, {useEffect, useMemo, useRef} from 'react'; import type {ReactNode} from 'react'; import {View} from 'react-native'; @@ -14,6 +13,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import BankAccount from '@libs/models/BankAccount'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; +import type {PolicyRoute} from '@pages/workspace/withPolicy'; import * as BankAccounts from '@userActions/BankAccounts'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -40,10 +40,10 @@ type WorkspacePageWithSectionsProps = WithPolicyAndFullscreenLoadingProps & headerText: string; /** The route object passed to this page from the navigator */ - route: RouteProp<{params: {policyID: string}}>; + route: PolicyRoute; /** Main content of the page */ - children: (hasVBA?: boolean, policyID?: string, isUsingECard?: boolean) => ReactNode; + children: (hasVBA: boolean, policyID: string, isUsingECard: boolean) => ReactNode; /** Content to be added as fixed footer */ footer?: ReactNode; diff --git a/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx b/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx index 79ff76204c69..ffd9a700ae7e 100644 --- a/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx +++ b/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx @@ -1,15 +1,14 @@ -import type {RouteProp} from '@react-navigation/native'; +import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; import useLocalize from '@hooks/useLocalize'; +import type {SettingsNavigatorParamList} from '@navigation/types'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; import CONST from '@src/CONST'; +import type SCREENS from '@src/SCREENS'; import WorkspaceInvoicesNoVBAView from './WorkspaceInvoicesNoVBAView'; import WorkspaceInvoicesVBAView from './WorkspaceInvoicesVBAView'; -/** Defined route object that contains the policyID param, WorkspacePageWithSections is a common component for Workspaces and expect the route prop that includes the policyID */ -type WorkspaceInvoicesPageProps = { - route: RouteProp<{params: {policyID: string}}>; -}; +type WorkspaceInvoicesPageProps = StackScreenProps; function WorkspaceInvoicesPage({route}: WorkspaceInvoicesPageProps) { const {translate} = useLocalize(); diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index ec38b61fb0dc..184a847a483e 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -5,13 +5,17 @@ import type {ComponentType, ForwardedRef, RefAttributes} from 'react'; import React, {forwardRef} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; +import type {SettingsNavigatorParamList} from '@navigation/types'; import policyMemberPropType from '@pages/policyMemberPropType'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; -type PolicyRoute = RouteProp<{params: {policyID: string}}>; +type PolicyRouteName = typeof SCREENS.WORKSPACE.BILLS | typeof SCREENS.WORKSPACE.INVOICES; + +type PolicyRoute = RouteProp; function getPolicyIDFromRoute(route: PolicyRoute): string { return route?.params?.policyID ?? ''; @@ -131,4 +135,4 @@ export default function (WrappedComponent: } export {policyPropTypes, policyDefaultProps}; -export type {WithPolicyOnyxProps, WithPolicyProps}; +export type {WithPolicyOnyxProps, WithPolicyProps, PolicyRoute}; From b32efdb4e7d53002629a67add427b7c0b2731358 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 23 Jan 2024 16:41:11 +0100 Subject: [PATCH 064/142] Fix lint error --- src/pages/workspace/WorkspacePageWithSections.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspacePageWithSections.tsx b/src/pages/workspace/WorkspacePageWithSections.tsx index a469e5bd4dff..8b45bfad771c 100644 --- a/src/pages/workspace/WorkspacePageWithSections.tsx +++ b/src/pages/workspace/WorkspacePageWithSections.tsx @@ -13,13 +13,13 @@ import useThemeStyles from '@hooks/useThemeStyles'; import BankAccount from '@libs/models/BankAccount'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; -import type {PolicyRoute} from '@pages/workspace/withPolicy'; import * as BankAccounts from '@userActions/BankAccounts'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Route} from '@src/ROUTES'; import type {Policy, ReimbursementAccount, User} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import type {PolicyRoute} from './withPolicy'; import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; From d233cbb19de40d9e448abccb4e9b348ab37d6fbe Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 24 Jan 2024 12:52:48 +0500 Subject: [PATCH 065/142] fix: composer not being auto focused --- src/components/AttachmentModal.js | 15 +--- .../ReportActionCompose.js | 68 +++++++++---------- 2 files changed, 34 insertions(+), 49 deletions(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index fe0793bd7c5e..6e2e13a46dfa 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -2,7 +2,7 @@ import Str from 'expensify-common/lib/str'; import lodashExtend from 'lodash/extend'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useState} from 'react'; +import React, {memo, useCallback, useEffect, useMemo, useState} from 'react'; import {Animated, Keyboard, View} from 'react-native'; import {GestureHandlerRootView} from 'react-native-gesture-handler'; import {withOnyx} from 'react-native-onyx'; @@ -118,7 +118,7 @@ const defaultProps = { canEditReceipt: false, }; -const AttachmentModal = forwardRef((props, ref) => { +function AttachmentModal(props) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -415,14 +415,6 @@ const AttachmentModal = forwardRef((props, ref) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.isReceiptAttachment, props.parentReport, props.parentReportActions, props.policy, props.transaction, file, source]); - useImperativeHandle( - ref, - () => ({ - displayFileInModal: validateAndDisplayFileToUpload, - }), - [validateAndDisplayFileToUpload], - ); - // There are a few things that shouldn't be set until we absolutely know if the file is a receipt or an attachment. // props.isReceiptAttachment will be null until its certain what the file is, in which case it will then be true|false. let headerTitle = props.headerTitle; @@ -546,12 +538,11 @@ const AttachmentModal = forwardRef((props, ref) => { })} ); -}); +}; AttachmentModal.propTypes = propTypes; AttachmentModal.defaultProps = defaultProps; AttachmentModal.displayName = 'AttachmentModal'; - export default compose( withWindowDimensions, withLocalize, diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js index 341cabe28cb9..2aa41e4f9d3d 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js @@ -158,7 +158,6 @@ function ReportActionCompose({ const suggestionsRef = useRef(null); const composerRef = useRef(null); - const attachementModalRef = useRef(null); const reportParticipantIDs = useMemo( () => _.without(lodashGet(report, 'participantAccountIDs', []), currentUserPersonalDetails.accountID), @@ -341,10 +340,6 @@ function ReportActionCompose({ runOnJS(submitForm)(); }, [isSendDisabled, resetFullComposerSize, submitForm, isReportReadyForDisplay]); - const onDisplayFileInModal = (file) => { - attachementModalRef.current.displayFileInModal(file); - }; - return ( @@ -368,7 +363,6 @@ function ReportActionCompose({ ]} > setIsAttachmentPreviewActive(true)} @@ -398,6 +392,37 @@ function ReportActionCompose({ onItemSelected={onItemSelected} actionButtonRef={actionButtonRef} /> + { if (isAttachmentPreviewActive) { @@ -410,37 +435,6 @@ function ReportActionCompose({ )} - Date: Wed, 24 Jan 2024 12:53:17 +0500 Subject: [PATCH 066/142] refactor: remove not needed code change --- src/pages/home/report/ReportFooter.js | 1 - src/styles/index.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportFooter.js b/src/pages/home/report/ReportFooter.js index 6e6a849658ae..1761e135481a 100644 --- a/src/pages/home/report/ReportFooter.js +++ b/src/pages/home/report/ReportFooter.js @@ -152,7 +152,6 @@ function ReportFooter(props) { ReportFooter.displayName = 'ReportFooter'; ReportFooter.propTypes = propTypes; ReportFooter.defaultProps = defaultProps; - export default compose( withWindowDimensions, withOnyx({ diff --git a/src/styles/index.ts b/src/styles/index.ts index 01d8b87edd15..aace13c34594 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1958,9 +1958,9 @@ const styles = (theme: ThemeColors) => alignSelf: 'flex-end', borderRadius: variables.buttonBorderRadius, height: 40, + marginVertical: 3, paddingHorizontal: 10, justifyContent: 'center', - marginVertical: 3, }, editChatItemEmojiWrapper: { From ddb5ef6961ae905785f1a1a98b789dacabd31524 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 24 Jan 2024 13:29:19 +0500 Subject: [PATCH 067/142] fix: linting --- src/components/AttachmentModal.js | 2 +- src/pages/home/ReportScreen.js | 33 +++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 6e2e13a46dfa..66b95e33eb82 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -538,7 +538,7 @@ function AttachmentModal(props) { })} ); -}; +} AttachmentModal.propTypes = propTypes; AttachmentModal.defaultProps = defaultProps; diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 00136cea43b1..5c333278de78 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -165,7 +165,7 @@ function ReportScreen({ /** * Create a lightweight Report so as to keep the re-rendering as light as possible by * passing in only the required props. - * + * * Also, this plays nicely in contrast with Onyx, * which creates a new object every time collection changes. Because of this we can't * put this into onyx selector as it will be the same. @@ -201,7 +201,36 @@ function ReportScreen({ isOwnPolicyExpenseChat: reportProp.isOwnPolicyExpenseChat, notificationPreference: reportProp.notificationPreference, }), - [reportProp.lastReadTime, reportProp.reportID, reportProp.policyID, reportProp.lastVisibleActionCreated, reportProp.statusNum, reportProp.stateNum, reportProp.writeCapability, reportProp.type, reportProp.errorFields, reportProp.isPolicyExpenseChat, reportProp.parentReportID, reportProp.parentReportActionID, reportProp.chatType, reportProp.pendingFields, reportProp.isDeletedParentAction, reportProp.reportName, reportProp.description, reportProp.managerID, reportProp.total, reportProp.nonReimbursableTotal, reportProp.reportFields, reportProp.ownerAccountID, reportProp.currency, reportProp.participantAccountIDs, reportProp.isWaitingOnBankAccount, reportProp.iouReportID, reportProp.isOwnPolicyExpenseChat, reportProp.notificationPreference], + [ + reportProp.lastReadTime, + reportProp.reportID, + reportProp.policyID, + reportProp.lastVisibleActionCreated, + reportProp.statusNum, + reportProp.stateNum, + reportProp.writeCapability, + reportProp.type, + reportProp.errorFields, + reportProp.isPolicyExpenseChat, + reportProp.parentReportID, + reportProp.parentReportActionID, + reportProp.chatType, + reportProp.pendingFields, + reportProp.isDeletedParentAction, + reportProp.reportName, + reportProp.description, + reportProp.managerID, + reportProp.total, + reportProp.nonReimbursableTotal, + reportProp.reportFields, + reportProp.ownerAccountID, + reportProp.currency, + reportProp.participantAccountIDs, + reportProp.isWaitingOnBankAccount, + reportProp.iouReportID, + reportProp.isOwnPolicyExpenseChat, + reportProp.notificationPreference, + ], ); const prevReport = usePrevious(report); From 62d407ab050d8b98ae227732e987615d1b48c369 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 24 Jan 2024 11:49:23 +0100 Subject: [PATCH 068/142] Minor code improvement --- src/pages/workspace/withPolicy.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index 184a847a483e..d2b087e00443 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -13,9 +13,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; -type PolicyRouteName = typeof SCREENS.WORKSPACE.BILLS | typeof SCREENS.WORKSPACE.INVOICES; - -type PolicyRoute = RouteProp; +type PolicyRoute = RouteProp; function getPolicyIDFromRoute(route: PolicyRoute): string { return route?.params?.policyID ?? ''; From ccdd6479fd41205522c2e07a97ff46f1bfbe0fb4 Mon Sep 17 00:00:00 2001 From: Conor Pendergrast Date: Thu, 25 Jan 2024 00:57:50 +1300 Subject: [PATCH 069/142] Create Preferences.md --- .../account-settings/Preferences.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 docs/articles/new-expensify/account-settings/Preferences.md diff --git a/docs/articles/new-expensify/account-settings/Preferences.md b/docs/articles/new-expensify/account-settings/Preferences.md new file mode 100644 index 000000000000..a31be00a02bb --- /dev/null +++ b/docs/articles/new-expensify/account-settings/Preferences.md @@ -0,0 +1,20 @@ +--- +title: Preferences +description: How to manage your Expensify Preferences +--- +# Overview +Your Preferences in Expensify allow you to customise how you interact with New Expensify. +- Set your theme preference + +# How to set your theme preference in New Expensify + +To set or update your theme preference in New Expensify: +1. Go to **Settings > Preferences** +2. Tap on **Theme** +3. You can choose between the _Dark_ theme, the _Light_ theme, or _Use Device Settings_ (which is the default setting) + +_Use Device Settings_ is the default setting. + +Selecting _Use Device Settings_ will use your device's theme settings. For example, if you have your device set to switch to light theme during daylight hours, and dark theme during after sunset, we'll match that. + +Your theme preference will sync across all of the ways you use New Expensify (mobile app, web app or OSX desktop app). From 17a5cdab574002d9613756c8fe4d4b3ad596da5c Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 24 Jan 2024 17:53:06 +0500 Subject: [PATCH 070/142] fix: reassure tests --- .../ComposerWithSuggestions.js | 41 +++---------------- .../ReportActionCompose.js | 35 +++++++++++++--- 2 files changed, 35 insertions(+), 41 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index f6a97316479f..883255c273f9 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -3,10 +3,8 @@ import lodashGet from 'lodash/get'; import React, {memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import {findNodeHandle, InteractionManager, NativeModules, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import {runOnJS, setNativeProps, useAnimatedRef} from 'react-native-reanimated'; import _ from 'underscore'; import Composer from '@components/Composer'; -import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton'; import withKeyboardState from '@components/withKeyboardState'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; @@ -20,7 +18,6 @@ import compose from '@libs/compose'; import * as ComposerUtils from '@libs/ComposerUtils'; import getDraftComment from '@libs/ComposerUtils/getDraftComment'; import convertToLTRForComposer from '@libs/convertToLTRForComposer'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as EmojiUtils from '@libs/EmojiUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; import getPlatform from '@libs/getPlatform'; @@ -31,7 +28,6 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as SuggestionUtils from '@libs/SuggestionUtils'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import willBlurTextInputOnTapOutsideFunc from '@libs/willBlurTextInputOnTapOutside'; -import SendButton from '@pages/home/report/ReportActionCompose/SendButton'; import SilentCommentUpdater from '@pages/home/report/ReportActionCompose/SilentCommentUpdater'; import Suggestions from '@pages/home/report/ReportActionCompose/Suggestions'; import * as EmojiPickerActions from '@userActions/EmojiPickerAction'; @@ -97,7 +93,7 @@ function ComposerWithSuggestions({ disabled, isFullComposerAvailable, setIsFullComposerAvailable, - isSendDisabled, + setIsCommentEmpty, handleSendMessage, shouldShowComposeInput, measureParentContainer, @@ -106,6 +102,7 @@ function ComposerWithSuggestions({ raiseIsScrollLikelyLayoutTriggered, // Refs suggestionsRef, + animatedRef, forwardedRef, isNextModalWillOpenRef, editFocused, @@ -121,8 +118,6 @@ function ComposerWithSuggestions({ const emojisPresentBefore = useRef([]); const draftComment = getDraftComment(reportID) || ''; - const [isCommentEmpty, setIsCommentEmpty] = useState(() => !draftComment || !!draftComment.match(/^(\s)*$/)); - const animatedRef = useAnimatedRef(); const [value, setValue] = useState(() => { if (draftComment) { emojisPresentBefore.current = EmojiUtils.extractEmojis(draftComment); @@ -131,7 +126,7 @@ function ComposerWithSuggestions({ }); const commentRef = useRef(value); - const {isSmallScreenWidth, isMediumScreenWidth} = useWindowDimensions(); + const {isSmallScreenWidth} = useWindowDimensions(); const maxComposerLines = isSmallScreenWidth ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES; const parentReportAction = lodashGet(parentReportActions, [parentReportActionID]); @@ -196,19 +191,6 @@ function ComposerWithSuggestions({ [], ); - const sendMessage = useCallback(() => { - 'worklet'; - - if (isCommentEmpty) { - return; - } - - runOnJS(handleSendMessage)(); - // We are setting the isCommentEmpty flag to true so the status of it will be in sync of the native text input state - runOnJS(setIsCommentEmpty)(true); - setNativeProps(animatedRef, {text: ''}); // clears native text input on the UI thread - }, [animatedRef, handleSendMessage, isCommentEmpty]); - /** * Update the value of the comment in Onyx * @@ -357,7 +339,7 @@ function ComposerWithSuggestions({ // Submit the form when Enter is pressed if (e.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey && !e.shiftKey) { e.preventDefault(); - sendMessage(); + handleSendMessage(); } // Trigger the edit box for last sent message if ArrowUp is pressed and the comment is empty and Chronos is not in the participants @@ -369,7 +351,7 @@ function ComposerWithSuggestions({ } } }, - [isSmallScreenWidth, isKeyboardShown, suggestionsRef, includeChronos, sendMessage, lastReportAction, reportID], + [isSmallScreenWidth, isKeyboardShown, suggestionsRef, includeChronos, handleSendMessage, lastReportAction, reportID], ); const onChangeText = useCallback( @@ -600,19 +582,6 @@ function ComposerWithSuggestions({ /> - {DeviceCapabilities.canUseTouchScreen() && isMediumScreenWidth ? null : ( - replaceSelectionWithText(...args)} - emojiPickerID={reportID} - /> - )} - - { + const draftComment = getDraftComment(reportID); + return !draftComment || !!draftComment.match(/^(\s)*$/); + }); /** * Updates the visibility state of the menu @@ -329,16 +338,19 @@ function ReportActionCompose({ const hasReportRecipient = _.isObject(reportRecipient) && !_.isEmpty(reportRecipient); - const isSendDisabled = isBlockedFromConcierge || disabled || hasExceededMaxCommentLength; + const isSendDisabled = isCommentEmpty || isBlockedFromConcierge || disabled || hasExceededMaxCommentLength; const handleSendMessage = useCallback(() => { if (isSendDisabled || !isReportReadyForDisplay) { return; } + // We are setting the isCommentEmpty flag to true so the status of it will be in sync of the native text input state + runOnJS(setIsCommentEmpty)(true); runOnJS(resetFullComposerSize)(); + setNativeProps(animatedRef, {text: ''}); // clears native text input on the UI thread runOnJS(submitForm)(); - }, [isSendDisabled, resetFullComposerSize, submitForm, isReportReadyForDisplay]); + }, [isSendDisabled, isReportReadyForDisplay, resetFullComposerSize, animatedRef, submitForm]); return ( @@ -394,6 +406,7 @@ function ReportActionCompose({ /> + {DeviceCapabilities.canUseTouchScreen() && isMediumScreenWidth ? null : ( + composerRef.current.replaceSelectionWithText(...args)} + emojiPickerID={report.reportID} + /> + )} + Date: Wed, 24 Jan 2024 18:24:11 +0500 Subject: [PATCH 071/142] refactor: add prop types --- .../ComposerWithSuggestions/composerWithSuggestionsProps.js | 6 ++++++ .../home/report/ReportActionCompose/ReportActionCompose.js | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/composerWithSuggestionsProps.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/composerWithSuggestionsProps.js index 2543149abd50..9d05db572949 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/composerWithSuggestionsProps.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/composerWithSuggestionsProps.js @@ -56,6 +56,9 @@ const propTypes = { /** Function to set whether the full composer is available or not */ setIsFullComposerAvailable: PropTypes.func.isRequired, + /** Function to set whether the comment is empty or not */ + setIsCommentEmpty: PropTypes.func.isRequired, + /** A method to call when the form is submitted */ handleSendMessage: PropTypes.func.isRequired, @@ -82,6 +85,9 @@ const propTypes = { }), }).isRequired, + /** Ref for the animated view (text input) */ + animatedRef: PropTypes.func.isRequired, + /** Ref for the composer */ forwardedRef: PropTypes.shape({current: PropTypes.shape({})}), diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js index 072b41b484c0..712bdf1aa0d3 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js @@ -350,7 +350,7 @@ function ReportActionCompose({ runOnJS(resetFullComposerSize)(); setNativeProps(animatedRef, {text: ''}); // clears native text input on the UI thread runOnJS(submitForm)(); - }, [isSendDisabled, isReportReadyForDisplay, resetFullComposerSize, animatedRef, submitForm]); + }, [isSendDisabled, resetFullComposerSize, submitForm, animatedRef, isReportReadyForDisplay]); return ( From d9faf512022fba05983044259864ee6cbf28b0c0 Mon Sep 17 00:00:00 2001 From: Pavlo Tsimura Date: Wed, 24 Jan 2024 23:17:20 +0100 Subject: [PATCH 072/142] Show merchant error only for Policy Expense splits --- src/components/MoneyRequestConfirmationList.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 590154b48bca..9d9277c0ac2e 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -731,8 +731,8 @@ function MoneyRequestConfirmationList(props) { }} disabled={didConfirm} interactive={!props.isReadOnly} - brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} - error={shouldDisplayMerchantError || (shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction)) ? translate('common.error.enterMerchant') : ''} + brickRoadIndicator={props.isPolicyExpenseChat && shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + error={shouldDisplayMerchantError || (props.isPolicyExpenseChat && shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction)) ? translate('common.error.enterMerchant') : ''} /> )} {shouldShowCategories && ( From 30f5da8ce91fd7d686db35aa3f15e817ba69771f Mon Sep 17 00:00:00 2001 From: Pavlo Tsimura Date: Wed, 24 Jan 2024 23:26:17 +0100 Subject: [PATCH 073/142] Show merchant error only for Policy Expense splits --- src/components/MoneyRequestConfirmationList.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 9d9277c0ac2e..f0ad024bc8ed 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -731,8 +731,14 @@ function MoneyRequestConfirmationList(props) { }} disabled={didConfirm} interactive={!props.isReadOnly} - brickRoadIndicator={props.isPolicyExpenseChat && shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} - error={shouldDisplayMerchantError || (props.isPolicyExpenseChat && shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction)) ? translate('common.error.enterMerchant') : ''} + brickRoadIndicator={ + props.isPolicyExpenseChat && shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : '' + } + error={ + shouldDisplayMerchantError || (props.isPolicyExpenseChat && shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction)) + ? translate('common.error.enterMerchant') + : '' + } /> )} {shouldShowCategories && ( From 7f4992f6bec2d92efa21995b6eeb796916d40825 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 25 Jan 2024 10:48:17 +0700 Subject: [PATCH 074/142] implement billable for split bill --- .../MoneyRequestConfirmationList.js | 2 +- src/libs/actions/IOU.js | 24 +++++++++++++++---- src/pages/iou/SplitBillDetailsPage.js | 2 ++ .../step/IOURequestStepConfirmation.js | 3 +++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 590154b48bca..005279092771 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -252,7 +252,7 @@ function MoneyRequestConfirmationList(props) { const shouldShowTax = props.isPolicyExpenseChat && props.policy.isTaxTrackingEnabled; // A flag for showing the billable field - const shouldShowBillable = !lodashGet(props.policy, 'disabledFields.defaultBillable', true); + const shouldShowBillable = !lodashGet(props.policy, 'disabledFields.defaultBillable', true) || true; const hasRoute = TransactionUtils.hasRoute(transaction); const isDistanceRequestWithoutRoute = props.isDistanceRequest && !hasRoute; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index eb9541edcad2..90c5353fa065 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1365,10 +1365,11 @@ function requestMoney( * @param {String} category * @param {String} tag * @param {String} existingSplitChatReportID - the report ID where the split bill happens, could be a group chat or a workspace chat + * @param {Boolean} billable * * @return {Object} */ -function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag, existingSplitChatReportID = '') { +function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag, existingSplitChatReportID = '', billable = false) { const currentUserEmailForIOUSplit = OptionsListUtils.addSMSDomainIfPhoneNumber(currentUserLogin); const participantAccountIDs = _.map(participants, (participant) => Number(participant.accountID)); const existingSplitChatReport = @@ -1392,6 +1393,7 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco undefined, category, tag, + billable, ); // Note: The created action must be optimistically generated before the IOU action so there's no chance that the created action appears after the IOU action in the chat @@ -1593,6 +1595,7 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco undefined, category, tag, + billable, ); // STEP 4: Build optimistic reportActions. We need: @@ -1710,8 +1713,9 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco * @param {String} category * @param {String} tag * @param {String} existingSplitChatReportID - Either a group DM or a workspace chat + * @param {Boolean} billable */ -function splitBill(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag, existingSplitChatReportID = '') { +function splitBill(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag, existingSplitChatReportID = '', billable = false) { const {splitData, splits, onyxData} = createSplitsAndOnyxData( participants, currentUserLogin, @@ -1723,6 +1727,7 @@ function splitBill(participants, currentUserLogin, currentUserAccountID, amount, category, tag, existingSplitChatReportID, + billable, ); API.write( 'SplitBill', @@ -1735,6 +1740,7 @@ function splitBill(participants, currentUserLogin, currentUserAccountID, amount, category, merchant, tag, + billable, transactionID: splitData.transactionID, reportActionID: splitData.reportActionID, createdReportActionID: splitData.createdReportActionID, @@ -1758,9 +1764,10 @@ function splitBill(participants, currentUserLogin, currentUserAccountID, amount, * @param {String} merchant * @param {String} category * @param {String} tag + * @param {Boolean} billable */ -function splitBillAndOpenReport(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag) { - const {splitData, splits, onyxData} = createSplitsAndOnyxData(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag); +function splitBillAndOpenReport(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag, billable) { + const {splitData, splits, onyxData} = createSplitsAndOnyxData(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, merchant, category, tag, billable); API.write( 'SplitBillAndOpenReport', @@ -1773,6 +1780,7 @@ function splitBillAndOpenReport(participants, currentUserLogin, currentUserAccou comment, category, tag, + billable, transactionID: splitData.transactionID, reportActionID: splitData.reportActionID, createdReportActionID: splitData.createdReportActionID, @@ -1797,8 +1805,9 @@ function splitBillAndOpenReport(participants, currentUserLogin, currentUserAccou * @param {String} tag * @param {Object} receipt * @param {String} existingSplitChatReportID - Either a group DM or a workspace chat + * @param {Boolean} billable */ -function startSplitBill(participants, currentUserLogin, currentUserAccountID, comment, category, tag, receipt, existingSplitChatReportID = '') { +function startSplitBill(participants, currentUserLogin, currentUserAccountID, comment, category, tag, receipt, existingSplitChatReportID = '', billable = false) { const currentUserEmailForIOUSplit = OptionsListUtils.addSMSDomainIfPhoneNumber(currentUserLogin); const participantAccountIDs = _.map(participants, (participant) => Number(participant.accountID)); const existingSplitChatReport = @@ -1823,6 +1832,10 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT, receiptObject, filename, + null, + '', + '', + billable, ); // Note: The created action must be optimistically generated before the IOU action so there's no chance that the created action appears after the IOU action in the chat @@ -2036,6 +2049,7 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co category, tag, isFromGroupDM: !existingSplitChatReport, + billable, ...(existingSplitChatReport ? {} : {createdReportActionID: splitChatCreatedReportAction.reportActionID}), }, {optimisticData, successData, failureData}, diff --git a/src/pages/iou/SplitBillDetailsPage.js b/src/pages/iou/SplitBillDetailsPage.js index 94c2f1c31242..e0dd9bd1bb44 100644 --- a/src/pages/iou/SplitBillDetailsPage.js +++ b/src/pages/iou/SplitBillDetailsPage.js @@ -102,6 +102,7 @@ function SplitBillDetailsPage(props) { created: splitCreated, category: splitCategory, tag: splitTag, + billable: splitBillable, } = isEditingSplitBill && props.draftTransaction ? ReportUtils.getTransactionDetails(props.draftTransaction) : ReportUtils.getTransactionDetails(props.transaction); const onConfirm = useCallback( @@ -133,6 +134,7 @@ function SplitBillDetailsPage(props) { iouMerchant={splitMerchant} iouCategory={splitCategory} iouTag={splitTag} + iouIsBillable={splitBillable} iouType={CONST.IOU.TYPE.SPLIT} isReadOnly={!isEditingSplitBill} shouldShowSmartScanFields diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js index 9df2564ae38d..551196520b2e 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.js +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js @@ -226,6 +226,7 @@ function IOURequestStepConfirmation({ transaction.tag, receiptFile, report.reportID, + transaction.billable, ); return; } @@ -244,6 +245,7 @@ function IOURequestStepConfirmation({ transaction.category, transaction.tag, report.reportID, + transaction.billable, ); return; } @@ -260,6 +262,7 @@ function IOURequestStepConfirmation({ transaction.merchant, transaction.category, transaction.tag, + transaction.billable, ); return; } From 2bfbca6121e5f6e51210d7da48291036f9e7e0ac Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 25 Jan 2024 11:01:32 +0700 Subject: [PATCH 075/142] revert hard code --- src/components/MoneyRequestConfirmationList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 005279092771..590154b48bca 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -252,7 +252,7 @@ function MoneyRequestConfirmationList(props) { const shouldShowTax = props.isPolicyExpenseChat && props.policy.isTaxTrackingEnabled; // A flag for showing the billable field - const shouldShowBillable = !lodashGet(props.policy, 'disabledFields.defaultBillable', true) || true; + const shouldShowBillable = !lodashGet(props.policy, 'disabledFields.defaultBillable', true); const hasRoute = TransactionUtils.hasRoute(transaction); const isDistanceRequestWithoutRoute = props.isDistanceRequest && !hasRoute; From b8f112557bc615036490abb20c4e800036e4b588 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Fri, 26 Jan 2024 06:00:03 +0530 Subject: [PATCH 076/142] fix: Expense - Title tooltip shows user email below workspace name. Signed-off-by: Krishna Gupta --- .../UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx index 21e19ac7c2e8..8aecaced4f32 100644 --- a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx +++ b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx @@ -37,9 +37,10 @@ function BaseUserDetailsTooltip({accountID, fallbackUserDetails, icon, delegateA } let title = String(userDisplayName).trim() ? userDisplayName : ''; - const subtitle = userLogin.trim() && LocalePhoneNumber.formatPhoneNumber(userLogin) !== userDisplayName ? Str.removeSMSDomain(userLogin) : ''; + let subtitle = userLogin.trim() && LocalePhoneNumber.formatPhoneNumber(userLogin) !== userDisplayName ? Str.removeSMSDomain(userLogin) : ''; if (icon && (icon.type === CONST.ICON_TYPE_WORKSPACE || !title)) { title = icon.name ?? ''; + subtitle = ''; } const renderTooltipContent = useCallback( () => ( From b05197674c96012fbb1fc943a8fa6216096df830 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 26 Jan 2024 13:19:40 +0100 Subject: [PATCH 077/142] fix: use correct property --- src/libs/actions/Task.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index b2f6b57f390a..893a11e5abba 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -789,8 +789,7 @@ function deleteTask(taskReportID: string, taskTitle: string, originalStateNum: n key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport?.reportID}`, value: { lastMessageText: ReportActionsUtils.getLastVisibleMessage(parentReport?.reportID ?? '', optimisticReportActions as OnyxTypes.ReportActions).lastMessageText ?? '', - lastVisibleActionCreated: - ReportActionsUtils.getLastVisibleAction(parentReport?.reportID ?? '', optimisticReportActions as OnyxTypes.ReportActions)?.childLastVisibleActionCreated ?? 'created', + lastVisibleActionCreated: ReportActionsUtils.getLastVisibleAction(parentReport?.reportID ?? '', optimisticReportActions as OnyxTypes.ReportActions)?.created, }, }, { From 47e6c4f9f4c1cb2469dfbbe4223dde70f48dc626 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Fri, 26 Jan 2024 18:49:07 +0530 Subject: [PATCH 078/142] fix: ordering of formatSectionsFromSearchTerm defauls parameters. Signed-off-by: Krishna Gupta --- src/libs/OptionsListUtils.js | 8 ++++---- src/pages/NewChatPage.js | 11 +---------- ...TemporaryForRefactorRequestParticipantsSelector.js | 4 ++-- .../MoneyRequestParticipantsSelector.js | 4 ++-- 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index c7db5867036e..50b1be420b62 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -1938,10 +1938,10 @@ function shouldOptionShowTooltip(option) { * @param {Array} selectedOptions * @param {Array} filteredRecentReports * @param {Array} filteredPersonalDetails - * @param {Object} personalDetails - * @param {Boolean} shouldGetOptionDetails * @param {Number} indexOffset * @param {Boolean} maxOptionsSelected + * @param {Object} personalDetails + * @param {Boolean} shouldGetOptionDetails * @returns {Object} */ function formatSectionsFromSearchTerm( @@ -1949,10 +1949,10 @@ function formatSectionsFromSearchTerm( selectedOptions, filteredRecentReports, filteredPersonalDetails, - personalDetails = {}, - shouldGetOptionDetails = false, indexOffset, maxOptionsSelected, + personalDetails = {}, + shouldGetOptionDetails = false, ) { // We show the selected participants at the top of the list when there is no search term or maximum number of participants has already been selected // However, if there is a search term we remove the selected participants from the top of the list unless they are part of the search results diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 98d2ecbd4b9f..8766e0b21fee 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -77,16 +77,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i const sectionsList = []; let indexOffset = 0; - const formatResults = OptionsListUtils.formatSectionsFromSearchTerm( - searchTerm, - selectedOptions, - filteredRecentReports, - filteredPersonalDetails, - {}, - false, - indexOffset, - maxParticipantsReached, - ); + const formatResults = OptionsListUtils.formatSectionsFromSearchTerm(searchTerm, selectedOptions, filteredRecentReports, filteredPersonalDetails, indexOffset, maxParticipantsReached); sectionsList.push(formatResults.section); indexOffset = formatResults.newIndexOffset; diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 552d791ac856..c3c75242103d 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -128,10 +128,10 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ participants, chatOptions.recentReports, chatOptions.personalDetails, - personalDetails, - true, indexOffset, maxParticipantsReached, + personalDetails, + true, ); newSections.push(formatResults.section); indexOffset = formatResults.newIndexOffset; diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 7700387996c8..649a42a5e559 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -139,10 +139,10 @@ function MoneyRequestParticipantsSelector({ participants, newChatOptions.recentReports, newChatOptions.personalDetails, - personalDetails, - true, indexOffset, maxParticipantsReached, + personalDetails, + true, ); newSections.push(formatResults.section); indexOffset = formatResults.newIndexOffset; From 9a629cc3d34d6bac496b4420b724a8dfc4f3b205 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Fri, 26 Jan 2024 13:35:22 +0000 Subject: [PATCH 079/142] Rename report param of the getMoneyRequestInformation to clarify its chat report --- src/libs/actions/IOU.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index eb9541edcad2..852e8a5fa4bf 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -608,7 +608,7 @@ function buildOnyxDataForMoneyRequest( * Gathers all the data needed to make a money request. It attempts to find existing reports, iouReports, and receipts. If it doesn't find them, then * it creates optimistic versions of them and uses those instead * - * @param {Object} report + * @param {Object} parentChatReport * @param {Object} participant * @param {String} comment * @param {Number} amount @@ -625,6 +625,7 @@ function buildOnyxDataForMoneyRequest( * @param {Object} [policy] * @param {Object} [policyTags] * @param {Object} [policyCategories] + * @param {Number} [moneyRequestReportID] * @returns {Object} data * @returns {String} data.payerEmail * @returns {Object} data.iouReport @@ -640,7 +641,7 @@ function buildOnyxDataForMoneyRequest( * @returns {Object} data.onyxData.failureData */ function getMoneyRequestInformation( - report, + parentChatReport, participant, comment, amount, @@ -657,6 +658,7 @@ function getMoneyRequestInformation( policy = undefined, policyTags = undefined, policyCategories = undefined, + moneyRequestReportID = 0, ) { const payerEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login); const payerAccountID = Number(participant.accountID); @@ -664,7 +666,7 @@ function getMoneyRequestInformation( // STEP 1: Get existing chat report OR build a new optimistic one let isNewChatReport = false; - let chatReport = lodashGet(report, 'reportID', null) ? report : null; + let chatReport = lodashGet(parentChatReport, 'reportID', null) ? parentChatReport : null; // If this is a policyExpenseChat, the chatReport must exist and we can get it from Onyx. // report is null if the flow is initiated from the global create menu. However, participant always stores the reportID if it exists, which is the case for policyExpenseChats From 4c75f8b28c936390c230955bcc1689bc5fa18718 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Fri, 26 Jan 2024 13:42:18 +0000 Subject: [PATCH 080/142] Add the new reportID param to createDistanceRequest --- src/libs/actions/IOU.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 852e8a5fa4bf..a47b1cb3b544 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -684,9 +684,10 @@ function getMoneyRequestInformation( chatReport = ReportUtils.buildOptimisticChatReport([payerAccountID]); } - // STEP 2: Get existing IOU report and update its total OR build a new optimistic one - const isNewIOUReport = !chatReport.iouReportID || ReportUtils.hasIOUWaitingOnCurrentUserBankAccount(chatReport); - let iouReport = isNewIOUReport ? null : allReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`]; + // STEP 2: Get existing IOU report, either from the chat report, or by the provided specific moneyRequestReportID, + // then update its total OR build a new optimistic one. + const isNewIOUReport = !moneyRequestReportID && (!chatReport.iouReportID || ReportUtils.hasIOUWaitingOnCurrentUserBankAccount(chatReport)); + let iouReport = isNewIOUReport ? null : allReports[`${ONYXKEYS.COLLECTION.REPORT}${moneyRequestReportID > 0 ? moneyRequestReportID : chatReport.iouReportID}`]; // Check if the Scheduled Submit is enabled in case of expense report let needsToBeManuallySubmitted = false; @@ -872,6 +873,7 @@ function createDistanceRequest(report, participant, comment, created, category, // If the report is an iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); const currentChatReport = isMoneyRequestReport ? ReportUtils.getReport(report.chatReportID) : report; + const moneyRequestReportID = isMoneyRequestReport ? report.reportID : 0 const optimisticReceipt = { source: ReceiptGeneric, @@ -895,6 +897,7 @@ function createDistanceRequest(report, participant, comment, created, category, policy, policyTags, policyCategories, + moneyRequestReportID, ); API.write( 'CreateDistanceRequest', From 4f6bd800984f386e96ff3b0bf6542c75fa0538ed Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Fri, 26 Jan 2024 13:42:50 +0000 Subject: [PATCH 081/142] Add the reportID to the requestMoney action --- src/libs/actions/IOU.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index a47b1cb3b544..10a2702545ca 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -873,7 +873,7 @@ function createDistanceRequest(report, participant, comment, created, category, // If the report is an iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); const currentChatReport = isMoneyRequestReport ? ReportUtils.getReport(report.chatReportID) : report; - const moneyRequestReportID = isMoneyRequestReport ? report.reportID : 0 + const moneyRequestReportID = isMoneyRequestReport ? report.reportID : 0; const optimisticReceipt = { source: ReceiptGeneric, @@ -1295,6 +1295,7 @@ function requestMoney( // If the report is iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); const currentChatReport = isMoneyRequestReport ? ReportUtils.getReport(report.chatReportID) : report; + const moneyRequestReportID = isMoneyRequestReport ? report.reportID : 0; const {payerAccountID, payerEmail, iouReport, chatReport, transaction, iouAction, createdChatReportActionID, createdIOUReportActionID, reportPreviewAction, onyxData} = getMoneyRequestInformation( currentChatReport, @@ -1314,6 +1315,7 @@ function requestMoney( policy, policyTags, policyCategories, + moneyRequestReportID, ); const activeReportID = isMoneyRequestReport ? report.reportID : chatReport.reportID; From dbcb0b42741bf498b7735a8631437f47e6e27340 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 26 Jan 2024 15:11:22 +0100 Subject: [PATCH 082/142] fix: after deleting task report action shouldnt be highlighted in report --- src/libs/actions/Task.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 893a11e5abba..af18d8f903cc 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -767,7 +767,6 @@ function deleteTask(taskReportID: string, taskTitle: string, originalStateNum: n ], errors: undefined, linkMetadata: [], - reportActionID: '', }; const optimisticReportActions = { [parentReportAction.reportActionID]: optimisticReportAction, From 44b5f596d923d4e72315b39dfbb2902ae5512280 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 26 Jan 2024 16:32:23 +0100 Subject: [PATCH 083/142] fix: added an improvment --- src/pages/home/report/ReportActionItem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 80fb341a2cf8..24d53041b20b 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -150,7 +150,7 @@ function ReportActionItem(props) { const prevDraftMessage = usePrevious(props.draftMessage); const originalReportID = ReportUtils.getOriginalReportID(props.report.reportID, props.action); const originalReport = props.report.reportID === originalReportID ? props.report : ReportUtils.getReport(originalReportID); - const isReportActionLinked = props.linkedReportActionID === props.action.reportActionID; + const isReportActionLinked = props.linkedReportActionID && props.action.reportActionID && props.linkedReportActionID === props.action.reportActionID; const highlightedBackgroundColorIfNeeded = useMemo( () => (isReportActionLinked ? StyleUtils.getBackgroundColorStyle(theme.hoverComponentBG) : {}), From 3e23f402f6a6c54a6548bf3e1f149d1b1d279a0c Mon Sep 17 00:00:00 2001 From: caitlinwhite1 Date: Fri, 26 Jan 2024 13:53:48 -0600 Subject: [PATCH 084/142] Update redirects.csv redirecing old links to Help. --- docs/redirects.csv | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/redirects.csv b/docs/redirects.csv index 74667e967f7f..4881e3f50a31 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -30,3 +30,19 @@ https://community.expensify.com/discussion/5124/how-to-add-your-name-and-photo-t https://community.expensify.com/discussion/5149/how-to-manage-your-devices-in-expensify,https://help.expensify.com/articles/expensify-classic/account-settings/Account-Details https://community.expensify.com/discussion/4432/how-to-add-a-secondary-login,https://help.expensify.com/articles/expensify-classic/account-settings/Account-Details https://community.expensify.com/discussion/6794/how-to-change-your-email-in-expensify,https://help.expensify.com/articles/expensify-classic/account-settings/Account-Details +Reimbursements,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/Global-Reimbursements +https://community.expensify.com/discussion/4452/how-to-merge-accounts,https://help.expensify.com/articles/expensify-classic/account-settings/Merge-Accounts#gsc.tab=0 +https://community.expensify.com/discussion/4783/how-to-add-or-remove-a-copilot#latest,https://help.expensify.com/articles/expensify-classic/account-settings/Copilot#gsc.tab=0 +https://community.expensify.com/discussion/4343/expensify-anz-partnership-announcement,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-ANZ +https://community.expensify.com/discussion/7318/deep-dive-company-credit-card-import-options,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards +https://community.expensify.com/discussion/2673/personalize-your-commercial-card-feed-name,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Commercial-Card-Feeds +https://community.expensify.com/discussion/6569/how-to-import-and-assign-company-cards-from-csv-file,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import +https://community.expensify.com/discussion/4714/how-to-set-up-a-direct-bank-connection-for-company-cards,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Direct-Bank-Connections +https://community.expensify.com/discussion/4828/how-to-reconcile-your-company-cards-statement-in-expensify,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Reconciliation +https://community.expensify.com/discussion/5366/deep-dive-troubleshooting-credit-card-issues-in-expensify,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Troubleshooting +https://community.expensify.com/discussion/9554/how-to-set-up-global-reimbursemen,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/Global-Reimbursements +https://community.expensify.com/discussion/4463/how-to-remove-or-manage-settings-for-imported-personal-cards,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Credit-Cards +https://community.expensify.com/discussion/5793/how-to-connect-your-personal-card-to-import-expenses,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Credit-Cards +https://community.expensify.com/discussion/4826/how-to-set-your-annual-subscription-size,https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription +https://community.expensify.com/discussion/5667/deep-dive-how-does-the-annual-subscription-billing-work,https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription +https://help.expensify.com/expensify-classic/hubs/getting-started/plan-types,https://www.expensify.com/pricing From 71913238d8d09ede04e01e35d27f29a938fe1dae Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sun, 28 Jan 2024 06:25:10 +0530 Subject: [PATCH 085/142] fix: comments. Signed-off-by: Krishna Gupta --- src/pages/NewChatPage.js | 2 +- .../MoneyTemporaryForRefactorRequestParticipantsSelector.js | 2 +- .../MoneyRequestParticipantsSelector.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 8766e0b21fee..90ebda4ac242 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -229,7 +229,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i updateOptions(); }, [didScreenTransitionEnd, updateOptions]); - // When search term updates we will fetch any reports + // When search term updates & user hasn't selected max number of participants we will fetch any reports const setSearchTermAndSearchInServer = useCallback( (text = '') => { if (text && !maxParticipantsReached) { diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index c3c75242103d..1f8d1c470fad 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -239,7 +239,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ [maxParticipantsReached, newChatOptions, participants, searchTerm], ); - // When search term updates we will fetch any reports + // When search term updates & user hasn't selected max number of participants we will fetch any reports const setSearchTermAndSearchInServer = useCallback( (text = '') => { if (text && !maxParticipantsReached) { diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 649a42a5e559..58fc81dcd805 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -259,7 +259,7 @@ function MoneyRequestParticipantsSelector({ [maxParticipantsReached, newChatOptions.personalDetails.length, newChatOptions.recentReports.length, newChatOptions.userToInvite, participants, searchTerm], ); - // When search term updates we will fetch any reports + // When search term updates & user hasn't selected max number of participants we will fetch any reports const setSearchTermAndSearchInServer = useCallback( (text = '') => { if (text && !maxParticipantsReached) { From efa6bfa02a88d9030c384fa97923ac80d5612268 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sun, 28 Jan 2024 06:45:25 +0530 Subject: [PATCH 086/142] fix: code updated due to merge conflicts. Signed-off-by: Krishna Gupta --- src/libs/OptionsListUtils.ts | 5 +++-- src/pages/NewChatPage.js | 2 +- .../MoneyTemporaryForRefactorRequestParticipantsSelector.js | 2 +- .../MoneyRequestParticipantsSelector.js | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 6332a57deec0..478bed7c9afd 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1914,14 +1914,15 @@ function formatSectionsFromSearchTerm( selectedOptions: ReportUtils.OptionData[], filteredRecentReports: ReportUtils.OptionData[], filteredPersonalDetails: PersonalDetails[], + maxOptionsSelected: boolean, + indexOffset = 0, personalDetails: OnyxEntry = {}, shouldGetOptionDetails = false, - indexOffset = 0, ): SectionForSearchTerm { // We show the selected participants at the top of the list when there is no search term // However, if there is a search term we remove the selected participants from the top of the list unless they are part of the search results // This clears up space on mobile views, where if you create a group with 4+ people you can't see the selected participants and the search results at the same time - if (searchTerm === '') { + if (searchTerm === '' || maxOptionsSelected) { return { section: { title: undefined, diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 90ebda4ac242..9624967a89a3 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -77,7 +77,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i const sectionsList = []; let indexOffset = 0; - const formatResults = OptionsListUtils.formatSectionsFromSearchTerm(searchTerm, selectedOptions, filteredRecentReports, filteredPersonalDetails, indexOffset, maxParticipantsReached); + const formatResults = OptionsListUtils.formatSectionsFromSearchTerm(searchTerm, selectedOptions, filteredRecentReports, filteredPersonalDetails, maxParticipantsReached, indexOffset); sectionsList.push(formatResults.section); indexOffset = formatResults.newIndexOffset; diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 51cc6fe5e9a6..b25b545a5382 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -133,8 +133,8 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ participants, chatOptions.recentReports, chatOptions.personalDetails, - indexOffset, maxParticipantsReached, + indexOffset, personalDetails, true, ); diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index cc0f854f59af..e62d77f1a52a 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -140,8 +140,8 @@ function MoneyRequestParticipantsSelector({ participants, newChatOptions.recentReports, newChatOptions.personalDetails, - indexOffset, maxParticipantsReached, + indexOffset, personalDetails, true, ); From 40b30f065e743586e10345afaa0be71b53749c0e Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sun, 28 Jan 2024 06:47:36 +0530 Subject: [PATCH 087/142] comment updated for formatSectionsFromSearchTerm. Signed-off-by: Krishna Gupta --- src/libs/OptionsListUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 478bed7c9afd..2c7695ade9e4 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1919,7 +1919,7 @@ function formatSectionsFromSearchTerm( personalDetails: OnyxEntry = {}, shouldGetOptionDetails = false, ): SectionForSearchTerm { - // We show the selected participants at the top of the list when there is no search term + // We show the selected participants at the top of the list when there is no search term or maximum number of participants has already been selected // However, if there is a search term we remove the selected participants from the top of the list unless they are part of the search results // This clears up space on mobile views, where if you create a group with 4+ people you can't see the selected participants and the search results at the same time if (searchTerm === '' || maxOptionsSelected) { From b0f6f9bb0965ec37a482e9ca51f185efcf6aba3a Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Mon, 29 Jan 2024 04:28:17 +0500 Subject: [PATCH 088/142] include the whole reportfield object in the report --- .../ReportActionItem/MoneyReportView.tsx | 29 +++-- src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/Permissions.ts | 2 +- src/libs/ReportUtils.ts | 88 +++++++++---- src/libs/actions/Report.ts | 105 +++++++++++++-- src/pages/EditReportFieldDatePage.tsx | 9 +- src/pages/EditReportFieldDropdownPage.tsx | 40 +++++- src/pages/EditReportFieldPage.tsx | 122 ++++++++++-------- src/pages/EditReportFieldTextPage.tsx | 9 +- src/pages/home/report/ReportActionItem.js | 9 +- src/types/onyx/PolicyReportField.ts | 3 + src/types/onyx/Report.ts | 3 +- 13 files changed, 303 insertions(+), 118 deletions(-) diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx index d81072d67b90..5651d0b943ab 100644 --- a/src/components/ReportActionItem/MoneyReportView.tsx +++ b/src/components/ReportActionItem/MoneyReportView.tsx @@ -19,12 +19,15 @@ import * as ReportUtils from '@libs/ReportUtils'; import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground'; import variables from '@styles/variables'; import ROUTES from '@src/ROUTES'; -import type {PolicyReportField, Report} from '@src/types/onyx'; +import type {Policy, PolicyReportField, Report} from '@src/types/onyx'; type MoneyReportViewProps = { /** The report currently being looked at */ report: Report; + /** Policy that the report belongs to */ + policy: Policy; + /** Policy report fields */ policyReportFields: PolicyReportField[]; @@ -32,7 +35,7 @@ type MoneyReportViewProps = { shouldShowHorizontalRule: boolean; }; -function MoneyReportView({report, policyReportFields, shouldShowHorizontalRule}: MoneyReportViewProps) { +function MoneyReportView({report, policy, policyReportFields, shouldShowHorizontalRule}: MoneyReportViewProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -55,10 +58,13 @@ function MoneyReportView({report, policyReportFields, shouldShowHorizontalRule}: StyleUtils.getColorStyle(theme.textSupporting), ]; - const sortedPolicyReportFields = useMemo( - (): PolicyReportField[] => policyReportFields.sort(({orderWeight: firstOrderWeight}, {orderWeight: secondOrderWeight}) => firstOrderWeight - secondOrderWeight), - [policyReportFields], - ); + const sortedPolicyReportFields = useMemo((): PolicyReportField[] => { + const reportFields = Object.values(report.reportFields ?? {}); + const mergedFieldIds = Array.from(new Set([...policyReportFields.map(({fieldID}) => fieldID), ...reportFields.map(({fieldID}) => fieldID)])); + const mergedFields = mergedFieldIds.map((id) => report?.reportFields?.[id] ?? policyReportFields.find(({fieldID}) => fieldID === id)) as PolicyReportField[]; + const allReportFields = isSettled ? reportFields : mergedFields; + return allReportFields.sort(({orderWeight: firstOrderWeight}, {orderWeight: secondOrderWeight}) => firstOrderWeight - secondOrderWeight); + }, [policyReportFields, report.reportFields, isSettled]); return ( @@ -66,7 +72,10 @@ function MoneyReportView({report, policyReportFields, shouldShowHorizontalRule}: {canUseReportFields && sortedPolicyReportFields.map((reportField) => { - const title = ReportUtils.getReportFieldTitle(report, reportField); + const isTitleField = ReportUtils.isReportFieldOfTypeTitle(reportField); + const fieldValue = isTitleField ? report.reportName : reportField.value ?? reportField.defaultValue; + const isFieldDisabled = ReportUtils.isReportFieldDisabled(report, reportField, policy); + return ( Navigation.navigate(ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute(report.reportID, report.policyID ?? '', reportField.fieldID))} shouldShowRightIcon - disabled={ReportUtils.isReportFieldOfTypeTitle(reportField)} + disabled={isFieldDisabled} wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} shouldGreyOutWhenDisabled={false} numberOfLinesTitle={0} diff --git a/src/languages/en.ts b/src/languages/en.ts index c1ccacaadf53..f4fc98a88a02 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1882,6 +1882,7 @@ export default { genericCreateReportFailureMessage: 'Unexpected error creating this chat, please try again later', genericAddCommentFailureMessage: 'Unexpected error while posting the comment, please try again later', genericUpdateReportFieldFailureMessage: 'Unexpected error while updating the field, please try again later', + genericUpdateReporNameEditFailureMessage: 'Unexpected error while renaming the report, please try again later', noActivityYet: 'No activity yet', }, chronos: { diff --git a/src/languages/es.ts b/src/languages/es.ts index f5278d093319..31389ba9c17a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1908,6 +1908,7 @@ export default { genericCreateReportFailureMessage: 'Error inesperado al crear el chat. Por favor, inténtalo más tarde', genericAddCommentFailureMessage: 'Error inesperado al añadir el comentario. Por favor, inténtalo más tarde', genericUpdateReportFieldFailureMessage: 'Error inesperado al actualizar el campo. Por favor, inténtalo más tarde', + genericUpdateReporNameEditFailureMessage: 'Error inesperado al cambiar el nombre del informe. Vuelva a intentarlo más tarde.', noActivityYet: 'Sin actividad todavía', }, chronos: { diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index ce5e0e674c59..af85ce5c7576 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -3,7 +3,7 @@ import CONST from '@src/CONST'; import type Beta from '@src/types/onyx/Beta'; function canUseAllBetas(betas: OnyxEntry): boolean { - return !!betas?.includes(CONST.BETAS.ALL); + return true; // !!betas?.includes(CONST.BETAS.ALL); } function canUseChronos(betas: OnyxEntry): boolean { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 86f939edc1b5..ef744dde5488 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -21,6 +21,7 @@ import type { PersonalDetailsList, Policy, PolicyReportField, + PolicyReportFields, Report, ReportAction, ReportMetadata, @@ -465,8 +466,21 @@ Onyx.connect({ callback: (value) => (loginList = value), }); -let allTransactions: OnyxCollection = {}; +let allPolicyReportFields: OnyxCollection; + +Onyx.connect({ + key: ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS, + waitForCollectionCallback: true, + callback: (value) => (allPolicyReportFields = value), +}); +let allBetas: OnyxEntry; +Onyx.connect({ + key: ONYXKEYS.BETAS, + callback: (value) => (allBetas = value), +}); + +let allTransactions: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, @@ -1898,10 +1912,54 @@ function getPolicyExpenseChatName(report: OnyxEntry, policy: OnyxEntry

): boolean { + return reportField?.type === 'formula' && reportField?.fieldID === CONST.REPORT_FIELD_TITLE_FIELD_ID; +} + +/** + * Given a report field, check if the field can be edited or not. + * For title fields, its considered disabled if `deletable` prop is `true` (https://github.com/Expensify/App/issues/35043#issuecomment-1911275433) + * For non title fields, its considered disabled if: + * 1. The user is not admin of the report + * 2. Report is settled or it is closed + */ +function isReportFieldDisabled(report: OnyxEntry, reportField: OnyxEntry, policy: OnyxEntry): boolean { + const isReportSettled = isSettled(report?.reportID); + const isReportClosed = report?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED; + const isTitleField = isReportFieldOfTypeTitle(reportField); + const isAdmin = isPolicyAdmin(report?.policyID ?? '', {[`${ONYXKEYS.COLLECTION.POLICY}${policy?.id ?? ''}`]: policy}); + return isTitleField ? !reportField?.deletable : !isAdmin && (isReportSettled || isReportClosed); +} + +/** + * Given a set of report fields, return the field of type formula + */ +function getFormulaTypeReportField(reportFields: PolicyReportFields) { + return Object.values(reportFields).find((field) => field.type === 'formula'); +} + +/** + * Get the report fields attached to the policy given policyID + */ +function getReportFieldsByPolicyID(policyID: string) { + return Object.entries(allPolicyReportFields ?? {}).find(([key]) => key.replace(ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS, '') === policyID)?.[1]; +} + /** * Get the title for an IOU or expense chat which will be showing the payer and the amount */ function getMoneyRequestReportName(report: OnyxEntry, policy: OnyxEntry | undefined = undefined): string { + const isReportSettled = isSettled(report?.reportID ?? ''); + const reportFields = isReportSettled ? report?.reportFields : getReportFieldsByPolicyID(report?.policyID ?? ''); + const titleReportField = getFormulaTypeReportField(reportFields ?? {}); + + if (titleReportField && report?.reportName && Permissions.canUseReportFields(allBetas ?? [])) { + return report.reportName; + } + const moneyRequestTotal = getMoneyRequestReimbursableTotal(report); const formattedAmount = CurrencyUtils.convertToDisplayString(moneyRequestTotal, report?.currency, hasOnlyDistanceRequestTransactions(report?.reportID)); const payerOrApproverName = isExpenseReport(report) ? getPolicyName(report, false, policy) : getDisplayNameForParticipant(report?.managerID) ?? ''; @@ -4551,32 +4609,6 @@ function navigateToPrivateNotes(report: Report, session: Session) { Navigation.navigate(ROUTES.PRIVATE_NOTES_LIST.getRoute(report.reportID)); } -/** - * Given a report field and a report, get the title of the field. - * This is specially useful when we have a report field of type formula. - */ -function getReportFieldTitle(report: OnyxEntry, reportField: PolicyReportField): string { - const value = report?.reportFields?.[reportField.fieldID] ?? reportField.defaultValue; - - if (reportField.type !== 'formula') { - return value; - } - - return value.replaceAll(CONST.REGEX.REPORT_FIELD_TITLE, (match, property) => { - if (report && property in report) { - return report[property as keyof Report]?.toString() ?? match; - } - return match; - }); -} - -/** - * Given a report field, check if the field is for the report title. - */ -function isReportFieldOfTypeTitle(reportField: PolicyReportField): boolean { - return reportField.type === 'formula' && reportField.fieldID === CONST.REPORT_FIELD_TITLE_FIELD_ID; -} - /** * Checks if thread replies should be displayed */ @@ -4787,12 +4819,12 @@ export { canEditWriteCapability, hasSmartscanError, shouldAutoFocusOnKeyPress, - getReportFieldTitle, shouldDisplayThreadReplies, shouldDisableThread, doesReportBelongToWorkspace, getChildReportNotificationPreference, isReportFieldOfTypeTitle, + isReportFieldDisabled, }; export type { diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 3030a160ddb3..d466c0a9b1a9 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -32,7 +32,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetails, PersonalDetailsList, PolicyReportField, ReportActionReactions, ReportUserIsTyping} from '@src/types/onyx'; +import type {PersonalDetails, PersonalDetailsList, PolicyReportField, RecentlyUsedReportFields, ReportActionReactions, ReportUserIsTyping} from '@src/types/onyx'; import type {Decision, OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import type {NotificationPreference, WriteCapability} from '@src/types/onyx/Report'; import type Report from '@src/types/onyx/Report'; @@ -137,6 +137,13 @@ Linking.getInitialURL().then((url) => { reportIDDeeplinkedFromOldDot = reportID; }); +let allRecentlyUsedReportFields: OnyxCollection = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_REPORT_FIELDS, + waitForCollectionCallback: true, + callback: (val) => (allRecentlyUsedReportFields = val), +}); + /** Get the private pusher channel name for a Report. */ function getReportChannelName(reportID: string): string { return `${CONST.PUSHER.PRIVATE_REPORT_CHANNEL_PREFIX}${reportID}${CONFIG.PUSHER.SUFFIX}`; @@ -1499,46 +1506,125 @@ function toggleSubscribeToChildReport(childReportID = '0', parentReportAction: P } } -function updatePolicyReportField(reportID: string, policyField: PolicyReportField, fieldValue: string) { +function updatePolicyReportName(reportID: string, value: string) { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: { + reportName: value, + pendingFields: { + reportName: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }, + }, + ]; + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: { + pendingFields: { + reportName: null, + }, + errorFields: { + reportName: ErrorUtils.getMicroSecondOnyxError('report.genericUpdateReporNameEditFailureMessage'), + }, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: { + pendingFields: { + reportName: null, + }, + errorFields: { + reportName: null, + }, + }, + }, + ]; + + type UpdateReportNameParameters = { + reportID: string; + reportName: string; + }; + + const parameters: UpdateReportNameParameters = { + reportID, + reportName: value, + }; + + API.write('RenameReport', parameters, {optimisticData, failureData, successData}); +} + +function updatePolicyReportField(policyID: string, reportID: string, reportField: PolicyReportField) { + const recentlyUsedValues = allRecentlyUsedReportFields?.[policyID]?.[reportField.fieldID] ?? []; + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { reportFields: { - [policyField.fieldID]: fieldValue, + [reportField.fieldID]: reportField, }, pendingFields: { - [policyField.fieldID]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + [reportField.fieldID]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, }, }, }, ]; + + if (reportField.type === 'dropdown') { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_REPORT_FIELDS}${policyID}`, + value: { + [reportField.fieldID]: [...new Set([...recentlyUsedValues, reportField.value])], + }, + }); + } + const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { pendingFields: { - [policyField.fieldID]: null, + [reportField.fieldID]: null, }, errorFields: { - [policyField.fieldID]: ErrorUtils.getMicroSecondOnyxError('report.genericUpdateReportFieldFailureMessage'), + [reportField.fieldID]: ErrorUtils.getMicroSecondOnyxError('report.genericUpdateReportFieldFailureMessage'), }, }, }, ]; + if (reportField.type === 'dropdown') { + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_REPORT_FIELDS}${policyID}`, + value: { + [reportField.fieldID]: recentlyUsedValues, + }, + }); + } + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { pendingFields: { - [policyField.fieldID]: null, + [reportField.fieldID]: null, }, errorFields: { - [policyField.fieldID]: null, + [reportField.fieldID]: null, }, }, }, @@ -1551,7 +1637,7 @@ function updatePolicyReportField(reportID: string, policyField: PolicyReportFiel const parameters: UpdateReportFieldParameters = { reportID, - reportFields: JSON.stringify({[policyField.fieldID]: {fieldID: policyField.fieldID, value: fieldValue, type: policyField.type, name: policyField.name}}), + reportFields: JSON.stringify({[reportField.fieldID]: {fieldID: reportField.fieldID, value: reportField.value, type: reportField.type, name: reportField.name}}), }; API.write('Report_SetFields', parameters, {optimisticData, failureData, successData}); @@ -2885,5 +2971,6 @@ export { updateLastVisitTime, clearNewRoomFormError, updatePolicyReportField, + updatePolicyReportName, resolveActionableMentionWhisper, }; diff --git a/src/pages/EditReportFieldDatePage.tsx b/src/pages/EditReportFieldDatePage.tsx index 8b3a8b74ba0d..8740d1ddecf6 100644 --- a/src/pages/EditReportFieldDatePage.tsx +++ b/src/pages/EditReportFieldDatePage.tsx @@ -20,11 +20,14 @@ type EditReportFieldDatePageProps = { /** ID of the policy report field */ fieldID: string; + /** Flag to indicate if the field can be left blank */ + isRequired: boolean; + /** Callback to fire when the Save button is pressed */ onSubmit: (form: Record) => void; }; -function EditReportFieldDatePage({fieldName, onSubmit, fieldValue, fieldID}: EditReportFieldDatePageProps) { +function EditReportFieldDatePage({fieldName, isRequired, onSubmit, fieldValue, fieldID}: EditReportFieldDatePageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const inputRef = useRef(null); @@ -32,12 +35,12 @@ function EditReportFieldDatePage({fieldName, onSubmit, fieldValue, fieldID}: Edi const validate = useCallback( (value: Record) => { const errors: Record = {}; - if (value[fieldID].trim() === '') { + if (isRequired && value[fieldID].trim() === '') { errors[fieldID] = 'common.error.fieldRequired'; } return errors; }, - [fieldID], + [fieldID, isRequired], ); return ( diff --git a/src/pages/EditReportFieldDropdownPage.tsx b/src/pages/EditReportFieldDropdownPage.tsx index ccecc9a5f289..448ec5d68773 100644 --- a/src/pages/EditReportFieldDropdownPage.tsx +++ b/src/pages/EditReportFieldDropdownPage.tsx @@ -1,12 +1,16 @@ import React, {useMemo, useState} from 'react'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import OptionsSelector from '@components/OptionsSelector'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {RecentlyUsedReportFields} from '@src/types/onyx'; -type EditReportFieldDropdownPageProps = { +type EditReportFieldDropdownPageComponentProps = { /** Value of the policy report field */ fieldValue: string; @@ -16,6 +20,10 @@ type EditReportFieldDropdownPageProps = { /** ID of the policy report field */ fieldID: string; + /** ID of the policy this report field belongs to */ + // eslint-disable-next-line react/no-unused-prop-types + policyID: string; + /** Options of the policy report field */ fieldOptions: string[]; @@ -23,24 +31,38 @@ type EditReportFieldDropdownPageProps = { onSubmit: (form: Record) => void; }; -function EditReportFieldDropdownPage({fieldName, onSubmit, fieldID, fieldValue, fieldOptions}: EditReportFieldDropdownPageProps) { +type EditReportFieldDropdownPageOnyxProps = { + policyRecentlyUsedReportFields: OnyxEntry; +}; + +type EditReportFieldDropdownPageProps = EditReportFieldDropdownPageComponentProps & EditReportFieldDropdownPageOnyxProps; + +function EditReportFieldDropdownPage({fieldName, onSubmit, fieldID, fieldValue, fieldOptions, policyRecentlyUsedReportFields}: EditReportFieldDropdownPageProps) { const [searchValue, setSearchValue] = useState(''); const styles = useThemeStyles(); const {getSafeAreaMargins} = useStyleUtils(); const {translate} = useLocalize(); + const recentlyUsedOptions = useMemo(() => policyRecentlyUsedReportFields?.[fieldID] ?? [], [policyRecentlyUsedReportFields, fieldID]); const sections = useMemo(() => { - const filteredOptions = fieldOptions.filter((option) => option.toLowerCase().includes(searchValue.toLowerCase())); + const filteredRecentOptions = recentlyUsedOptions.filter((option) => option.toLowerCase().includes(searchValue.toLowerCase())); + const filteredRestOfOptions = fieldOptions.filter((option) => !filteredRecentOptions.includes(option) && option.toLowerCase().includes(searchValue.toLowerCase())); + return [ { title: translate('common.recents'), shouldShow: true, - data: [], + data: filteredRecentOptions.map((option) => ({ + text: option, + keyForList: option, + searchText: option, + tooltipText: option, + })), }, { title: translate('common.all'), shouldShow: true, - data: filteredOptions.map((option) => ({ + data: filteredRestOfOptions.map((option) => ({ text: option, keyForList: option, searchText: option, @@ -48,7 +70,7 @@ function EditReportFieldDropdownPage({fieldName, onSubmit, fieldID, fieldValue, })), }, ]; - }, [fieldOptions, searchValue, translate]); + }, [fieldOptions, recentlyUsedOptions, searchValue, translate]); return ( ({ + policyRecentlyUsedReportFields: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_REPORT_FIELDS}${policyID}`, + }, +})(EditReportFieldDropdownPage); diff --git a/src/pages/EditReportFieldPage.tsx b/src/pages/EditReportFieldPage.tsx index 6ed17636d043..f411583fc681 100644 --- a/src/pages/EditReportFieldPage.tsx +++ b/src/pages/EditReportFieldPage.tsx @@ -1,12 +1,13 @@ -import React, {useEffect} from 'react'; +import React from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ScreenWrapper from '@components/ScreenWrapper'; import Navigation from '@libs/Navigation/Navigation'; +import * as ReportUtils from '@libs/ReportUtils'; import * as ReportActions from '@src/libs/actions/Report'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PolicyReportFields, Report} from '@src/types/onyx'; +import type {Policy, PolicyReportFields, Report} from '@src/types/onyx'; import EditReportFieldDatePage from './EditReportFieldDatePage'; import EditReportFieldDropdownPage from './EditReportFieldDropdownPage'; import EditReportFieldTextPage from './EditReportFieldTextPage'; @@ -17,6 +18,9 @@ type EditReportFieldPageOnyxProps = { /** Policy report fields */ policyReportFields: OnyxEntry; + + /** Policy to which the report belongs to */ + policy: OnyxEntry; }; type EditReportFieldPageProps = EditReportFieldPageOnyxProps & { @@ -36,70 +40,77 @@ type EditReportFieldPageProps = EditReportFieldPageOnyxProps & { }; }; -function EditReportFieldPage({route, report, policyReportFields}: EditReportFieldPageProps) { - const policyReportField = policyReportFields?.[route.params.fieldID]; - const reportFieldValue = report?.reportFields?.[policyReportField?.fieldID ?? '']; +function EditReportFieldPage({route, policy, report, policyReportFields}: EditReportFieldPageProps) { + const reportField = report?.reportFields?.[route.params.fieldID] ?? policyReportFields?.[route.params.fieldID]; + const isDisabled = ReportUtils.isReportFieldDisabled(report, reportField ?? null, policy); + + if (!reportField || !report || isDisabled) { + return ( + + {}} + onLinkPress={() => {}} + /> + + ); + } - // Decides whether to allow or disallow editing a money request - useEffect(() => {}, []); + const isReportFieldTitle = ReportUtils.isReportFieldOfTypeTitle(reportField); const handleReportFieldChange = (form: Record) => { - if (report && policyReportField) { - const value = form[policyReportField.fieldID] || ''; - ReportActions.updatePolicyReportField(report.reportID, policyReportField, value); + const value = form[reportField.fieldID] || ''; + if (isReportFieldTitle) { + ReportActions.updatePolicyReportName(report.reportID, value); + } else { + ReportActions.updatePolicyReportField(report.policyID ?? '', report.reportID, {...reportField, value}); } + Navigation.dismissModal(report?.reportID); }; - if (policyReportField) { - if (policyReportField.type === 'text' || policyReportField.type === 'formula') { - return ( - - ); - } + const fieldValue = isReportFieldTitle ? report.reportName ?? '' : reportField.value ?? reportField.defaultValue; - if (policyReportField.type === 'date') { - return ( - - ); - } + if (reportField.type === 'text' || isReportFieldTitle) { + return ( + + ); + } - if (policyReportField.type === 'dropdown') { - return ( - - ); - } + if (reportField.type === 'date') { + return ( + + ); } - return ( - - {}} - onLinkPress={() => {}} + if (reportField.type === 'dropdown') { + return ( + - - ); + ); + } } EditReportFieldPage.displayName = 'EditReportFieldPage'; @@ -111,4 +122,7 @@ export default withOnyx( policyReportFields: { key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS}${route.params.policyID}`, }, + policy: { + key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`, + }, })(EditReportFieldPage); diff --git a/src/pages/EditReportFieldTextPage.tsx b/src/pages/EditReportFieldTextPage.tsx index eac85053448f..b2e76a9596bc 100644 --- a/src/pages/EditReportFieldTextPage.tsx +++ b/src/pages/EditReportFieldTextPage.tsx @@ -20,11 +20,14 @@ type EditReportFieldTextPageProps = { /** ID of the policy report field */ fieldID: string; + /** Flag to indicate if the field can be left blank */ + isRequired: boolean; + /** Callback to fire when the Save button is pressed */ onSubmit: (form: Record) => void; }; -function EditReportFieldTextPage({fieldName, onSubmit, fieldValue, fieldID}: EditReportFieldTextPageProps) { +function EditReportFieldTextPage({fieldName, onSubmit, fieldValue, isRequired, fieldID}: EditReportFieldTextPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const inputRef = useRef(null); @@ -32,12 +35,12 @@ function EditReportFieldTextPage({fieldName, onSubmit, fieldValue, fieldID}: Edi const validate = useCallback( (value: Record) => { const errors: Record = {}; - if (value[fieldID].trim() === '') { + if (isRequired && value[fieldID].trim() === '') { errors[fieldID] = 'common.error.fieldRequired'; } return errors; }, - [fieldID], + [fieldID, isRequired], ); return ( diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 00ce20c19a85..a550397d0fc5 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -107,7 +107,6 @@ const propTypes = { /** Stores user's preferred skin tone */ preferredSkinTone: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - ...windowDimensionsPropTypes, emojiReactions: EmojiReactionsPropTypes, /** IOU report for this action, if any */ @@ -650,6 +649,7 @@ function ReportActionItem(props) { @@ -805,6 +805,10 @@ export default compose( key: ({report}) => (report && 'policyID' in report ? `${ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS}${report.policyID}` : undefined), initialValue: [], }, + policy: { + key: ({report}) => (report && 'policyID' in report ? `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}` : undefined), + initialValue: {}, + }, emojiReactions: { key: ({action}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`, initialValue: {}, @@ -848,6 +852,7 @@ export default compose( lodashGet(prevProps.report, 'nonReimbursableTotal', 0) === lodashGet(nextProps.report, 'nonReimbursableTotal', 0) && prevProps.linkedReportActionID === nextProps.linkedReportActionID && _.isEqual(prevProps.policyReportFields, nextProps.policyReportFields) && - _.isEqual(prevProps.report.reportFields, nextProps.report.reportFields), + _.isEqual(prevProps.report.reportFields, nextProps.report.reportFields) && + _.isEqual(prevProps.policy, nextProps.policy), ), ); diff --git a/src/types/onyx/PolicyReportField.ts b/src/types/onyx/PolicyReportField.ts index a1724a9ff52f..de385070aa25 100644 --- a/src/types/onyx/PolicyReportField.ts +++ b/src/types/onyx/PolicyReportField.ts @@ -19,6 +19,9 @@ type PolicyReportField = { /** Tells if the field is required or not */ deletable: boolean; + /** Value of the field */ + value: string; + /** Options to select from if field is of type dropdown */ values: string[]; }; diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index b1571e7514e4..95ea6152a725 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -2,6 +2,7 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type * as OnyxCommon from './OnyxCommon'; import type PersonalDetails from './PersonalDetails'; +import type {PolicyReportField} from './PolicyReportField'; type NotificationPreference = ValueOf; @@ -160,7 +161,7 @@ type Report = { isLoadingPrivateNotes?: boolean; /** If the report contains reportFields, save the field id and its value */ - reportFields?: Record; + reportFields?: Record; }; export default Report; From 06b2b16747f87c88d5e72897ed6384182a744ae5 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Mon, 29 Jan 2024 05:06:56 +0500 Subject: [PATCH 089/142] create new types for the write commands --- src/libs/API/parameters/SetPolicyReportFieldParams.ts | 6 ++++++ src/libs/API/parameters/SetPolicyReportNameParams.ts | 6 ++++++ src/libs/API/parameters/index.ts | 2 ++ src/libs/API/types.ts | 4 ++++ 4 files changed, 18 insertions(+) create mode 100644 src/libs/API/parameters/SetPolicyReportFieldParams.ts create mode 100644 src/libs/API/parameters/SetPolicyReportNameParams.ts diff --git a/src/libs/API/parameters/SetPolicyReportFieldParams.ts b/src/libs/API/parameters/SetPolicyReportFieldParams.ts new file mode 100644 index 000000000000..94f059057b58 --- /dev/null +++ b/src/libs/API/parameters/SetPolicyReportFieldParams.ts @@ -0,0 +1,6 @@ +type SetPolicyReportFieldParams = { + reportID: string; + reportFields: string; +}; + +export default SetPolicyReportFieldParams; diff --git a/src/libs/API/parameters/SetPolicyReportNameParams.ts b/src/libs/API/parameters/SetPolicyReportNameParams.ts new file mode 100644 index 000000000000..d6310ee18115 --- /dev/null +++ b/src/libs/API/parameters/SetPolicyReportNameParams.ts @@ -0,0 +1,6 @@ +type SetPolicyReportNameParams = { + reportID: string; + reportName: string; +}; + +export default SetPolicyReportNameParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 039398c0fbf6..635887b33e4b 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -122,3 +122,5 @@ export type {default as ReopenTaskParams} from './ReopenTaskParams'; export type {default as CompleteTaskParams} from './CompleteTaskParams'; export type {default as CompleteEngagementModalParams} from './CompleteEngagementModalParams'; export type {default as SetNameValuePairParams} from './SetNameValuePairParams'; +export type {default as SetPolicyReportFieldParams} from './SetPolicyReportFieldParams'; +export type {default as SetPolicyReportNameParams} from './SetPolicyReportNameParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index f58ebc30b4a2..ca13e828f1bd 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -113,6 +113,8 @@ const WRITE_COMMANDS = { COMPLETE_TASK: 'CompleteTask', COMPLETE_ENGAGEMENT_MODAL: 'CompleteEngagementModal', SET_NAME_VALUE_PAIR: 'SetNameValuePair', + SET_POLICY_REPORT_FIELD: 'Report_SetFields', + SET_POLICY_REPORT_NAME: 'RenameReport', } as const; type WriteCommand = ValueOf; @@ -223,6 +225,8 @@ type WriteCommandParameters = { [WRITE_COMMANDS.COMPLETE_TASK]: Parameters.CompleteTaskParams; [WRITE_COMMANDS.COMPLETE_ENGAGEMENT_MODAL]: Parameters.CompleteEngagementModalParams; [WRITE_COMMANDS.SET_NAME_VALUE_PAIR]: Parameters.SetNameValuePairParams; + [WRITE_COMMANDS.SET_POLICY_REPORT_FIELD]: Parameters.SetPolicyReportFieldParams; + [WRITE_COMMANDS.SET_POLICY_REPORT_NAME]: Parameters.SetPolicyReportNameParams; }; const READ_COMMANDS = { From b58d85b873517ab82a701ad54ffb60c952c02640 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Mon, 29 Jan 2024 05:09:19 +0500 Subject: [PATCH 090/142] fix lint issues --- src/pages/EditReportFieldDatePage.tsx | 7 +++++-- src/pages/EditReportFieldTextPage.tsx | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/pages/EditReportFieldDatePage.tsx b/src/pages/EditReportFieldDatePage.tsx index 473317663998..2496e5b23bf2 100644 --- a/src/pages/EditReportFieldDatePage.tsx +++ b/src/pages/EditReportFieldDatePage.tsx @@ -11,6 +11,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Errors} from '@src/types/onyx/OnyxCommon'; type EditReportFieldDatePageProps = { /** Value of the policy report field */ @@ -36,7 +37,7 @@ function EditReportFieldDatePage({fieldName, isRequired, onSubmit, fieldValue, f const validate = useCallback( (value: OnyxFormValuesFields) => { - const errors: Record = {}; + const errors: Errors = {}; if (isRequired && value[fieldID].toString().trim() === '') { errors[fieldID] = 'common.error.fieldRequired'; } @@ -49,7 +50,9 @@ function EditReportFieldDatePage({fieldName, isRequired, onSubmit, fieldValue, f {inputRef.current?.focus()}} + onEntryTransitionEnd={() => { + inputRef.current?.focus(); + }} testID={EditReportFieldDatePage.displayName} > diff --git a/src/pages/EditReportFieldTextPage.tsx b/src/pages/EditReportFieldTextPage.tsx index 18cf84fcfbf3..096967cf4003 100644 --- a/src/pages/EditReportFieldTextPage.tsx +++ b/src/pages/EditReportFieldTextPage.tsx @@ -37,7 +37,7 @@ function EditReportFieldTextPage({fieldName, onSubmit, fieldValue, isRequired, f const validate = useCallback( (values: OnyxFormValuesFields) => { - const errors: Errors = {}; + const errors: Errors = {}; if (isRequired && values[fieldID].toString().trim() === '') { errors[fieldID] = 'common.error.fieldRequired'; } @@ -50,7 +50,9 @@ function EditReportFieldTextPage({fieldName, onSubmit, fieldValue, isRequired, f {inputRef.current?.focus()}} + onEntryTransitionEnd={() => { + inputRef.current?.focus(); + }} testID={EditReportFieldTextPage.displayName} > From 19cafde76db980e66fd977952d241d9aaa44aa6f Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Mon, 29 Jan 2024 05:21:45 +0500 Subject: [PATCH 091/142] fix: typechecks --- src/pages/EditReportFieldPage.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/EditReportFieldPage.tsx b/src/pages/EditReportFieldPage.tsx index f411583fc681..2cb47918ce34 100644 --- a/src/pages/EditReportFieldPage.tsx +++ b/src/pages/EditReportFieldPage.tsx @@ -2,6 +2,7 @@ import React from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import type {OnyxFormValuesFields} from '@components/Form/types'; import ScreenWrapper from '@components/ScreenWrapper'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; @@ -62,8 +63,8 @@ function EditReportFieldPage({route, policy, report, policyReportFields}: EditRe const isReportFieldTitle = ReportUtils.isReportFieldOfTypeTitle(reportField); - const handleReportFieldChange = (form: Record) => { - const value = form[reportField.fieldID] || ''; + const handleReportFieldChange = (form: OnyxFormValuesFields) => { + const value = form[reportField.fieldID].toString() || ''; if (isReportFieldTitle) { ReportActions.updatePolicyReportName(report.reportID, value); } else { From 72b68c91a687fd588454de0e34d3298b4eda7844 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Mon, 29 Jan 2024 05:25:40 +0500 Subject: [PATCH 092/142] fix: remove leftover code --- src/libs/Permissions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index af85ce5c7576..ce5e0e674c59 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -3,7 +3,7 @@ import CONST from '@src/CONST'; import type Beta from '@src/types/onyx/Beta'; function canUseAllBetas(betas: OnyxEntry): boolean { - return true; // !!betas?.includes(CONST.BETAS.ALL); + return !!betas?.includes(CONST.BETAS.ALL); } function canUseChronos(betas: OnyxEntry): boolean { From 9263a0f14f247173bdd211b5c8b230963affc18d Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 29 Jan 2024 14:15:30 +0700 Subject: [PATCH 093/142] fix lint --- src/libs/actions/IOU.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 3d6fdad302b1..cb73afad5012 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1857,7 +1857,7 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co undefined, category, tag, - billable + billable, ); // Note: The created action must be optimistically generated before the IOU action so there's no chance that the created action appears after the IOU action in the chat From 824d49eb33bcb9bea04af2932d58b0ec4446cdb3 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 29 Jan 2024 09:27:53 +0100 Subject: [PATCH 094/142] Simplify PolicyRoute typing --- src/pages/workspace/WorkspacePageWithSections.tsx | 2 +- src/pages/workspace/withPolicy.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/WorkspacePageWithSections.tsx b/src/pages/workspace/WorkspacePageWithSections.tsx index 5047ea270dd7..65189a3c6468 100644 --- a/src/pages/workspace/WorkspacePageWithSections.tsx +++ b/src/pages/workspace/WorkspacePageWithSections.tsx @@ -93,7 +93,7 @@ function WorkspacePageWithSections({ const isLoading = reimbursementAccount?.isLoading ?? true; const achState = reimbursementAccount?.achData?.state ?? ''; const isUsingECard = user?.isUsingExpensifyCard ?? false; - const policyID = route.params.policyID; + const policyID = route.params?.policyID ?? ''; const policyName = policy?.name; const hasVBA = achState === BankAccount.STATE.OPEN; const content = children(hasVBA, policyID, isUsingECard); diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index d2b087e00443..aee03f1f74e9 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -5,6 +5,7 @@ import type {ComponentType, ForwardedRef, RefAttributes} from 'react'; import React, {forwardRef} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import type {SettingsNavigatorParamList} from '@navigation/types'; import policyMemberPropType from '@pages/policyMemberPropType'; import * as Policy from '@userActions/Policy'; @@ -13,7 +14,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; -type PolicyRoute = RouteProp; +type PolicyRoute = RouteProp>; function getPolicyIDFromRoute(route: PolicyRoute): string { return route?.params?.policyID ?? ''; From c8069ee77694bb0c24a85541b8bc24368b44450a Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 29 Jan 2024 21:04:31 +0700 Subject: [PATCH 095/142] fix selected contact not move to top --- src/pages/NewChatPage.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index b90ce6bbc247..ce5cd445350e 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -148,7 +148,6 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i {}, [], true, - true, ); setSelectedOptions(newSelectedOptions); @@ -199,8 +198,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i false, {}, [], - true, - true, + true ); setFilteredRecentReports(recentReports); setFilteredPersonalDetails(newChatPersonalDetails); From 44edf1d66372bc231073499ce6e7fdfe8bc4f59c Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 29 Jan 2024 19:53:50 +0530 Subject: [PATCH 096/142] added extra check. Signed-off-by: Krishna Gupta --- .../UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx index 8aecaced4f32..974ce30cbf67 100644 --- a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx +++ b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx @@ -40,7 +40,9 @@ function BaseUserDetailsTooltip({accountID, fallbackUserDetails, icon, delegateA let subtitle = userLogin.trim() && LocalePhoneNumber.formatPhoneNumber(userLogin) !== userDisplayName ? Str.removeSMSDomain(userLogin) : ''; if (icon && (icon.type === CONST.ICON_TYPE_WORKSPACE || !title)) { title = icon.name ?? ''; - subtitle = ''; + if (icon.type === CONST.ICON_TYPE_WORKSPACE) { + subtitle = ''; + } } const renderTooltipContent = useCallback( () => ( From a455a4c6057e28c69d66ee39f70a92de9d16e2a4 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 29 Jan 2024 21:37:38 +0700 Subject: [PATCH 097/142] fix lint issue --- src/pages/NewChatPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index ce5cd445350e..f488b776b49e 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -198,7 +198,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i false, {}, [], - true + true, ); setFilteredRecentReports(recentReports); setFilteredPersonalDetails(newChatPersonalDetails); From 7611dae3120c76ea899f8aab131a0f390e7e18f0 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 29 Jan 2024 20:30:05 +0530 Subject: [PATCH 098/142] added comment. Signed-off-by: Krishna Gupta --- .../UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx index 974ce30cbf67..09bdb1b70279 100644 --- a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx +++ b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx @@ -40,6 +40,8 @@ function BaseUserDetailsTooltip({accountID, fallbackUserDetails, icon, delegateA let subtitle = userLogin.trim() && LocalePhoneNumber.formatPhoneNumber(userLogin) !== userDisplayName ? Str.removeSMSDomain(userLogin) : ''; if (icon && (icon.type === CONST.ICON_TYPE_WORKSPACE || !title)) { title = icon.name ?? ''; + + // We need to ensure we only clear the subtitle only when icon is of type workspace if (icon.type === CONST.ICON_TYPE_WORKSPACE) { subtitle = ''; } From e118db57ee9087f44f916c3d396eccb1f9c1c654 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 29 Jan 2024 20:44:19 +0530 Subject: [PATCH 099/142] comment udpated. Signed-off-by: Krishna Gupta --- .../UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx index 09bdb1b70279..2555836b90a1 100644 --- a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx +++ b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx @@ -41,7 +41,8 @@ function BaseUserDetailsTooltip({accountID, fallbackUserDetails, icon, delegateA if (icon && (icon.type === CONST.ICON_TYPE_WORKSPACE || !title)) { title = icon.name ?? ''; - // We need to ensure we only clear the subtitle only when icon is of type workspace + // We need to clear the subtitle for the workspaces otherwise user's email will be shown as subtitle + // Also we need to clear only when icon is of type `Workspace` if (icon.type === CONST.ICON_TYPE_WORKSPACE) { subtitle = ''; } From c237445ce2a77b159738ff673767ee90a76ee2ed Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Mon, 29 Jan 2024 17:53:02 +0000 Subject: [PATCH 100/142] Address PR feedback --- src/libs/actions/IOU.js | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 7342700257cd..06c541f40710 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -349,7 +349,7 @@ function getOutstandingChildRequest(policy, needsToBeManuallySubmitted) { * @param {Array} optimisticPolicyRecentlyUsedCategories * @param {Array} optimisticPolicyRecentlyUsedTags * @param {boolean} isNewChatReport - * @param {boolean} isNewIOUReport + * @param {boolean} shouldCreateNewMoneyRequestReport * @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts) * @param {Array} policyTags * @param {Array} policyCategories @@ -368,7 +368,7 @@ function buildOnyxDataForMoneyRequest( optimisticPolicyRecentlyUsedCategories, optimisticPolicyRecentlyUsedTags, isNewChatReport, - isNewIOUReport, + shouldCreateNewMoneyRequestReport, policy, policyTags, policyCategories, @@ -391,14 +391,14 @@ function buildOnyxDataForMoneyRequest( }, }, { - onyxMethod: isNewIOUReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE, + onyxMethod: shouldCreateNewMoneyRequestReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, value: { ...iouReport, lastMessageText: iouAction.message[0].text, lastMessageHtml: iouAction.message[0].html, pendingFields: { - ...(isNewIOUReport ? {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD} : {preview: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), + ...(shouldCreateNewMoneyRequestReport ? {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD} : {preview: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), }, }, }, @@ -416,10 +416,10 @@ function buildOnyxDataForMoneyRequest( }, }, { - onyxMethod: isNewIOUReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE, + onyxMethod: shouldCreateNewMoneyRequestReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, value: { - ...(isNewIOUReport ? {[iouCreatedAction.reportActionID]: iouCreatedAction} : {}), + ...(shouldCreateNewMoneyRequestReport ? {[iouCreatedAction.reportActionID]: iouCreatedAction} : {}), [iouAction.reportActionID]: iouAction, }, }, @@ -507,7 +507,7 @@ function buildOnyxDataForMoneyRequest( onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, value: { - ...(isNewIOUReport + ...(shouldCreateNewMoneyRequestReport ? { [iouCreatedAction.reportActionID]: { pendingAction: null, @@ -547,7 +547,7 @@ function buildOnyxDataForMoneyRequest( value: { pendingFields: null, errorFields: { - ...(isNewIOUReport ? {createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage')} : {}), + ...(shouldCreateNewMoneyRequestReport ? {createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage')} : {}), }, }, }, @@ -593,7 +593,7 @@ function buildOnyxDataForMoneyRequest( onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, value: { - ...(isNewIOUReport + ...(shouldCreateNewMoneyRequestReport ? { [iouCreatedAction.reportActionID]: { errors: getReceiptError(transaction.receipt, transaction.filename || transaction.receipt.filename, isScanRequest), @@ -651,7 +651,7 @@ function buildOnyxDataForMoneyRequest( * @param {Object} [policy] * @param {Object} [policyTags] * @param {Object} [policyCategories] - * @param {Number} [moneyRequestReportID] + * @param {Number} [moneyRequestReportID] - If user requests money from a composer on some money request report, we always add a request to that specific report. * @returns {Object} data * @returns {String} data.payerEmail * @returns {Object} data.iouReport @@ -710,10 +710,15 @@ function getMoneyRequestInformation( chatReport = ReportUtils.buildOptimisticChatReport([payerAccountID]); } - // STEP 2: Get existing IOU report, either from the chat report, or by the provided specific moneyRequestReportID, - // then update its total OR build a new optimistic one. - const isNewIOUReport = !moneyRequestReportID && (!chatReport.iouReportID || ReportUtils.hasIOUWaitingOnCurrentUserBankAccount(chatReport)); - let iouReport = isNewIOUReport ? null : allReports[`${ONYXKEYS.COLLECTION.REPORT}${moneyRequestReportID > 0 ? moneyRequestReportID : chatReport.iouReportID}`]; + // STEP 2: Get the money request report. If the moneyRequestReportID has been provided, we want to add the transaction to this specific report. + // If no such reportID has been provided, let's use the chatReport.iouReportID property. In case that is not present, build a new optimistic money request report. + let iouReport = null; + const shouldCreateNewMoneyRequestReport = !moneyRequestReportID && (!chatReport.iouReportID || ReportUtils.hasIOUWaitingOnCurrentUserBankAccount(chatReport)); + if (moneyRequestReportID > 0) { + iouReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${moneyRequestReportID}`]; + } else if (!shouldCreateNewMoneyRequestReport) { + iouReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`]; + } // Check if the Scheduled Submit is enabled in case of expense report let needsToBeManuallySubmitted = true; @@ -810,7 +815,7 @@ function getMoneyRequestInformation( currentTime, ); - let reportPreviewAction = isNewIOUReport ? null : ReportActionsUtils.getReportPreviewAction(chatReport.reportID, iouReport.reportID); + let reportPreviewAction = shouldCreateNewMoneyRequestReport ? null : ReportActionsUtils.getReportPreviewAction(chatReport.reportID, iouReport.reportID); if (reportPreviewAction) { reportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction, false, comment, optimisticTransaction); } else { @@ -848,7 +853,7 @@ function getMoneyRequestInformation( optimisticPolicyRecentlyUsedCategories, optimisticPolicyRecentlyUsedTags, isNewChatReport, - isNewIOUReport, + shouldCreateNewMoneyRequestReport, policy, policyTags, policyCategories, @@ -863,7 +868,7 @@ function getMoneyRequestInformation( transaction: optimisticTransaction, iouAction, createdChatReportActionID: isNewChatReport ? optimisticCreatedActionForChat.reportActionID : 0, - createdIOUReportActionID: isNewIOUReport ? optimisticCreatedActionForIOU.reportActionID : 0, + createdIOUReportActionID: shouldCreateNewMoneyRequestReport ? optimisticCreatedActionForIOU.reportActionID : 0, reportPreviewAction, onyxData: { optimisticData, From cb0f172efcff65f31898f60dee6b13dda5d72fd7 Mon Sep 17 00:00:00 2001 From: Pavlo Tsimura Date: Mon, 29 Jan 2024 18:58:27 +0100 Subject: [PATCH 101/142] Persist the latest selected payment option --- src/components/ButtonWithDropdownMenu.tsx | 5 +++++ src/components/SettlementButton.js | 12 ++++++++---- src/libs/actions/IOU.js | 5 +++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/components/ButtonWithDropdownMenu.tsx b/src/components/ButtonWithDropdownMenu.tsx index 466c68229a32..676912de6b60 100644 --- a/src/components/ButtonWithDropdownMenu.tsx +++ b/src/components/ButtonWithDropdownMenu.tsx @@ -32,6 +32,9 @@ type ButtonWithDropdownMenuProps = { /** Callback to execute when the main button is pressed */ onPress: (event: GestureResponderEvent | KeyboardEvent | undefined, value: string) => void; + /** Callback to execute when a dropdown option is selected */ + onOptionSelected?: (option: DropdownOption) => void; + /** Call the onPress function on main button when Enter key is pressed */ pressOnEnter?: boolean; @@ -72,6 +75,7 @@ function ButtonWithDropdownMenu({ buttonRef, onPress, options, + onOptionSelected, }: ButtonWithDropdownMenuProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -174,6 +178,7 @@ function ButtonWithDropdownMenu({ menuItems={options.map((item, index) => ({ ...item, onSelected: () => { + onOptionSelected?.(item); setSelectedItemIndex(index); }, }))} diff --git a/src/components/SettlementButton.js b/src/components/SettlementButton.js index 0c8e193af4cc..493b8767ee9b 100644 --- a/src/components/SettlementButton.js +++ b/src/components/SettlementButton.js @@ -177,8 +177,9 @@ function SettlementButton({ return [approveButtonOption]; } - // To achieve the one tap pay experience we need to choose the correct payment type as default, - // if user already paid for some request or expense, let's use the last payment method or use default. + // To achieve the one tap pay experience we need to choose the correct payment type as default. + // If the user has previously chosen a specific payment option or paid for some request or expense, + // let's use the last payment method or use default. const paymentMethod = nvp_lastPaymentMethod[policyID] || ''; if (canUseWallet) { buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.EXPENSIFY]); @@ -192,12 +193,14 @@ function SettlementButton({ buttonOptions.push(approveButtonOption); } - // Put the preferred payment method to the front of the array so its shown as default + // Put the preferred payment method to the front of the array, so it's shown as default if (paymentMethod) { return _.sortBy(buttonOptions, (method) => (method.value === paymentMethod ? 0 : 1)); } return buttonOptions; - }, [currency, formattedAmount, iouReport, nvp_lastPaymentMethod, policyID, translate, shouldHidePaymentOptions, shouldShowApproveButton]); + // We don't want to reorder the options when the preferred payment method changes while the button is still visible + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currency, formattedAmount, iouReport, policyID, translate, shouldHidePaymentOptions, shouldShowApproveButton]); const selectPaymentType = (event, iouPaymentType, triggerKYCFlow) => { if (iouPaymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY || iouPaymentType === CONST.IOU.PAYMENT_TYPE.VBBA) { @@ -235,6 +238,7 @@ function SettlementButton({ onPress={(event, iouPaymentType) => selectPaymentType(event, iouPaymentType, triggerKYCFlow)} pressOnEnter={pressOnEnter} options={paymentButtonOptions} + onOptionSelected={(option) => IOU.savePreferredPaymentMethod(policyID, option.value)} style={style} buttonSize={buttonSize} anchorAlignment={paymentMethodDropdownAnchorAlignment} diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 5297d9aa4463..580053998c3c 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -3749,6 +3749,10 @@ function navigateToStartStepIfScanFileCannotBeRead(receiptFilename, receiptPath, FileUtils.readFileAsync(receiptPath, receiptFilename, onSuccess, onFailure); } +function savePreferredPaymentMethod(policyID, paymentMethod) { + Onyx.merge(`${ONYXKEYS.NVP_LAST_PAYMENT_METHOD}`, {[policyID]: paymentMethod}); +} + export { setMoneyRequestParticipants, createDistanceRequest, @@ -3809,4 +3813,5 @@ export { getIOUReportID, editMoneyRequest, navigateToStartStepIfScanFileCannotBeRead, + savePreferredPaymentMethod, }; From 0b0070a636864f131d7228a5e0b6f086677f9020 Mon Sep 17 00:00:00 2001 From: Pavlo Tsimura Date: Mon, 29 Jan 2024 19:15:12 +0100 Subject: [PATCH 102/142] Add JSDoc --- src/libs/actions/IOU.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 580053998c3c..8db42d62dab9 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -3749,6 +3749,11 @@ function navigateToStartStepIfScanFileCannotBeRead(receiptFilename, receiptPath, FileUtils.readFileAsync(receiptPath, receiptFilename, onSuccess, onFailure); } +/** + * Save the preferred payment method for a policy + * @param {String} policyID + * @param {String} paymentMethod + */ function savePreferredPaymentMethod(policyID, paymentMethod) { Onyx.merge(`${ONYXKEYS.NVP_LAST_PAYMENT_METHOD}`, {[policyID]: paymentMethod}); } From cd576d8291384e4f05ed435c7836f20dca3adb71 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Tue, 30 Jan 2024 02:34:59 +0500 Subject: [PATCH 103/142] fix: update recently used field implementation --- src/ONYXKEYS.ts | 6 ++++-- src/libs/actions/Report.ts | 13 ++++++------- src/pages/EditReportFieldDropdownPage.tsx | 10 +++++----- src/pages/EditReportFieldPage.tsx | 2 +- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 7abf6db1769d..cfcae988d6b5 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -243,6 +243,9 @@ const ONYXKEYS = { // Max width supported for HTML element MAX_CANVAS_WIDTH: 'maxCanvasWidth', + // Stores the recently used report fields + RECENTLY_USED_REPORT_FIELDS: 'recentlyUsedReportFields', + /** Collection Keys */ COLLECTION: { DOWNLOAD: 'download_', @@ -256,7 +259,6 @@ const ONYXKEYS = { POLICY_TAX_RATE: 'policyTaxRates_', POLICY_RECENTLY_USED_TAGS: 'policyRecentlyUsedTags_', POLICY_REPORT_FIELDS: 'policyReportFields_', - POLICY_RECENTLY_USED_REPORT_FIELDS: 'policyRecentlyUsedReportFields_', WORKSPACE_INVITE_MEMBERS_DRAFT: 'workspaceInviteMembersDraft_', WORKSPACE_INVITE_MESSAGE_DRAFT: 'workspaceInviteMessageDraft_', REPORT: 'report_', @@ -440,6 +442,7 @@ type OnyxValues = { [ONYXKEYS.MAX_CANVAS_AREA]: number; [ONYXKEYS.MAX_CANVAS_HEIGHT]: number; [ONYXKEYS.MAX_CANVAS_WIDTH]: number; + [ONYXKEYS.RECENTLY_USED_REPORT_FIELDS]: OnyxTypes.RecentlyUsedReportFields; // Collections [ONYXKEYS.COLLECTION.DOWNLOAD]: OnyxTypes.Download; @@ -451,7 +454,6 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories; [ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS]: OnyxTypes.PolicyReportFields; - [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_REPORT_FIELDS]: OnyxTypes.RecentlyUsedReportFields; [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMembers; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: Record; [ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index afb3f3711746..fe273aefd000 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -176,10 +176,9 @@ Linking.getInitialURL().then((url) => { reportIDDeeplinkedFromOldDot = reportID; }); -let allRecentlyUsedReportFields: OnyxCollection = {}; +let allRecentlyUsedReportFields: OnyxEntry = {}; Onyx.connect({ - key: ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_REPORT_FIELDS, - waitForCollectionCallback: true, + key: ONYXKEYS.RECENTLY_USED_REPORT_FIELDS, callback: (val) => (allRecentlyUsedReportFields = val), }); @@ -1526,8 +1525,8 @@ function updatePolicyReportName(reportID: string, value: string) { API.write('RenameReport', parameters, {optimisticData, failureData, successData}); } -function updatePolicyReportField(policyID: string, reportID: string, reportField: PolicyReportField) { - const recentlyUsedValues = allRecentlyUsedReportFields?.[policyID]?.[reportField.fieldID] ?? []; +function updatePolicyReportField(reportID: string, reportField: PolicyReportField) { + const recentlyUsedValues = allRecentlyUsedReportFields?.[reportField.fieldID] ?? []; const optimisticData: OnyxUpdate[] = [ { @@ -1547,7 +1546,7 @@ function updatePolicyReportField(policyID: string, reportID: string, reportField if (reportField.type === 'dropdown') { optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_REPORT_FIELDS}${policyID}`, + key: ONYXKEYS.RECENTLY_USED_REPORT_FIELDS, value: { [reportField.fieldID]: [...new Set([...recentlyUsedValues, reportField.value])], }, @@ -1572,7 +1571,7 @@ function updatePolicyReportField(policyID: string, reportID: string, reportField if (reportField.type === 'dropdown') { failureData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_REPORT_FIELDS}${policyID}`, + key: ONYXKEYS.RECENTLY_USED_REPORT_FIELDS, value: { [reportField.fieldID]: recentlyUsedValues, }, diff --git a/src/pages/EditReportFieldDropdownPage.tsx b/src/pages/EditReportFieldDropdownPage.tsx index 448ec5d68773..2815867778eb 100644 --- a/src/pages/EditReportFieldDropdownPage.tsx +++ b/src/pages/EditReportFieldDropdownPage.tsx @@ -32,17 +32,17 @@ type EditReportFieldDropdownPageComponentProps = { }; type EditReportFieldDropdownPageOnyxProps = { - policyRecentlyUsedReportFields: OnyxEntry; + recentlyUsedReportFields: OnyxEntry; }; type EditReportFieldDropdownPageProps = EditReportFieldDropdownPageComponentProps & EditReportFieldDropdownPageOnyxProps; -function EditReportFieldDropdownPage({fieldName, onSubmit, fieldID, fieldValue, fieldOptions, policyRecentlyUsedReportFields}: EditReportFieldDropdownPageProps) { +function EditReportFieldDropdownPage({fieldName, onSubmit, fieldID, fieldValue, fieldOptions, recentlyUsedReportFields}: EditReportFieldDropdownPageProps) { const [searchValue, setSearchValue] = useState(''); const styles = useThemeStyles(); const {getSafeAreaMargins} = useStyleUtils(); const {translate} = useLocalize(); - const recentlyUsedOptions = useMemo(() => policyRecentlyUsedReportFields?.[fieldID] ?? [], [policyRecentlyUsedReportFields, fieldID]); + const recentlyUsedOptions = useMemo(() => recentlyUsedReportFields?.[fieldID] ?? [], [recentlyUsedReportFields, fieldID]); const sections = useMemo(() => { const filteredRecentOptions = recentlyUsedOptions.filter((option) => option.toLowerCase().includes(searchValue.toLowerCase())); @@ -105,7 +105,7 @@ function EditReportFieldDropdownPage({fieldName, onSubmit, fieldID, fieldValue, EditReportFieldDropdownPage.displayName = 'EditReportFieldDropdownPage'; export default withOnyx({ - policyRecentlyUsedReportFields: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_REPORT_FIELDS}${policyID}`, + recentlyUsedReportFields: { + key: () => ONYXKEYS.RECENTLY_USED_REPORT_FIELDS, }, })(EditReportFieldDropdownPage); diff --git a/src/pages/EditReportFieldPage.tsx b/src/pages/EditReportFieldPage.tsx index 2cb47918ce34..24ade45993f6 100644 --- a/src/pages/EditReportFieldPage.tsx +++ b/src/pages/EditReportFieldPage.tsx @@ -68,7 +68,7 @@ function EditReportFieldPage({route, policy, report, policyReportFields}: EditRe if (isReportFieldTitle) { ReportActions.updatePolicyReportName(report.reportID, value); } else { - ReportActions.updatePolicyReportField(report.policyID ?? '', report.reportID, {...reportField, value}); + ReportActions.updatePolicyReportField(report.reportID, {...reportField, value}); } Navigation.dismissModal(report?.reportID); From 4ea2337c2399c30b800181be31bb2ce148e611f1 Mon Sep 17 00:00:00 2001 From: Github Date: Tue, 30 Jan 2024 09:01:06 +0100 Subject: [PATCH 104/142] Refactor ReportScreen perf tests --- tests/perf-test/ReportScreen.perf-test.js | 60 +++++++++++------------ tests/utils/ReportTestUtils.js | 9 +++- tests/utils/collections/reportActions.ts | 21 ++++++-- 3 files changed, 53 insertions(+), 37 deletions(-) diff --git a/tests/perf-test/ReportScreen.perf-test.js b/tests/perf-test/ReportScreen.perf-test.js index 5a144e715f5b..f5bc3f985227 100644 --- a/tests/perf-test/ReportScreen.perf-test.js +++ b/tests/perf-test/ReportScreen.perf-test.js @@ -15,7 +15,10 @@ import * as Localize from '../../src/libs/Localize'; import ONYXKEYS from '../../src/ONYXKEYS'; import {ReportAttachmentsProvider} from '../../src/pages/home/report/ReportAttachmentsContext'; import ReportScreen from '../../src/pages/home/ReportScreen'; -import * as LHNTestUtils from '../utils/LHNTestUtils'; +import createCollection from '../utils/collections/createCollection'; +import createPersonalDetails from '../utils/collections/personalDetails'; +import createRandomPolicy from '../utils/collections/policies'; +import createRandomReport from '../utils/collections/reports'; import PusherHelper from '../utils/PusherHelper'; import * as ReportTestUtils from '../utils/ReportTestUtils'; import * as TestHelper from '../utils/TestHelper'; @@ -56,6 +59,7 @@ jest.mock('../../src/hooks/useEnvironment', () => jest.mock('../../src/libs/Permissions', () => ({ canUseLinkPreviews: jest.fn(() => true), + canUseDefaultRooms: jest.fn(() => true), })); jest.mock('../../src/hooks/usePermissions.ts'); @@ -103,6 +107,18 @@ afterEach(() => { PusherHelper.teardown(); }); +const policies = createCollection( + (item) => `${ONYXKEYS.COLLECTION.POLICY}${item.id}`, + (index) => createRandomPolicy(index), + 10, +); + +const personalDetails = createCollection( + (item) => item.accountID, + (index) => createPersonalDetails(index), + 20, +); + /** * This is a helper function to create a mock for the addListener function of the react-navigation library. * The reason we need this is because we need to trigger the transitionEnd event in our tests to simulate @@ -152,7 +168,11 @@ function ReportScreenWrapper(args) { ); } -test.skip('[ReportScreen] should render ReportScreen with composer interactions', () => { +const report = {...createRandomReport(1), policyID: '1'}; +const reportActions = ReportTestUtils.getMockedReportActionsMap(500); +const mockRoute = {params: {reportID: '1'}}; + +test('[ReportScreen] should render ReportScreen with composer interactions', () => { const {triggerTransitionEnd, addListener} = createAddListenerMock(); const scenario = async () => { /** @@ -166,9 +186,6 @@ test.skip('[ReportScreen] should render ReportScreen with composer interactions' await act(triggerTransitionEnd); - // Query for the report list - await screen.findByTestId('report-actions-list'); - // Query for the composer const composer = await screen.findByTestId('composer'); @@ -189,15 +206,6 @@ test.skip('[ReportScreen] should render ReportScreen with composer interactions' await screen.findByLabelText(hintHeaderText); }; - const policy = { - policyID: 1, - name: 'Testing Policy', - }; - - const report = LHNTestUtils.getFakeReport(); - const reportActions = ReportTestUtils.getMockedReportActionsMap(1000); - const mockRoute = {params: {reportID: '1'}}; - const navigation = {addListener}; return waitForBatchedUpdates() @@ -206,9 +214,9 @@ test.skip('[ReportScreen] should render ReportScreen with composer interactions' [ONYXKEYS.IS_SIDEBAR_LOADED]: true, [`${ONYXKEYS.COLLECTION.REPORT}${mockRoute.params.reportID}`]: report, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${mockRoute.params.reportID}`]: reportActions, - [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: personalDetails, [ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS], - [`${ONYXKEYS.COLLECTION.POLICY}${policy.policyID}`]: policy, + [`${ONYXKEYS.COLLECTION.POLICY}`]: policies, [`${ONYXKEYS.COLLECTION.REPORT_METADATA}${mockRoute.params.reportID}`]: { isLoadingReportActions: false, }, @@ -225,7 +233,7 @@ test.skip('[ReportScreen] should render ReportScreen with composer interactions' ); }); -test.skip('[ReportScreen] should press of the report item', () => { +test('[ReportScreen] should press of the report item', () => { const {triggerTransitionEnd, addListener} = createAddListenerMock(); const scenario = async () => { /** @@ -239,12 +247,9 @@ test.skip('[ReportScreen] should press of the report item', () => { await act(triggerTransitionEnd); - // Query for the report list + // // Query for the report list await screen.findByTestId('report-actions-list'); - // Query for the composer - await screen.findByTestId('composer'); - const hintReportPreviewText = Localize.translateLocal('iou.viewDetails'); // Query for report preview buttons @@ -254,15 +259,6 @@ test.skip('[ReportScreen] should press of the report item', () => { fireEvent.press(reportPreviewButtons[0]); }; - const policy = { - policyID: 123, - name: 'Testing Policy', - }; - - const report = LHNTestUtils.getFakeReport(); - const reportActions = ReportTestUtils.getMockedReportActionsMap(1000); - const mockRoute = {params: {reportID: '2'}}; - const navigation = {addListener}; return waitForBatchedUpdates() @@ -271,9 +267,9 @@ test.skip('[ReportScreen] should press of the report item', () => { [ONYXKEYS.IS_SIDEBAR_LOADED]: true, [`${ONYXKEYS.COLLECTION.REPORT}${mockRoute.params.reportID}`]: report, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${mockRoute.params.reportID}`]: reportActions, - [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: personalDetails, [ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS], - [`${ONYXKEYS.COLLECTION.POLICY}${policy.policyID}`]: policy, + [`${ONYXKEYS.COLLECTION.POLICY}`]: policies, [`${ONYXKEYS.COLLECTION.REPORT_METADATA}${mockRoute.params.reportID}`]: { isLoadingReportActions: false, }, diff --git a/tests/utils/ReportTestUtils.js b/tests/utils/ReportTestUtils.js index 910f2200876b..86899e4045f6 100644 --- a/tests/utils/ReportTestUtils.js +++ b/tests/utils/ReportTestUtils.js @@ -1,4 +1,5 @@ import _ from 'underscore'; +import createRandomReportAction from './collections/reportActions'; const actionNames = ['ADDCOMMENT', 'IOU', 'REPORTPREVIEW', 'CLOSED']; @@ -51,7 +52,13 @@ const getMockedReportActionsMap = (length = 100) => { const mockReports = Array.from({length}, (__, i) => { const reportID = i + 1; const actionName = i === 0 ? 'CREATED' : actionNames[i % actionNames.length]; - const reportAction = getFakeReportAction(reportID, actionName); + const reportAction = { + ...createRandomReportAction(reportID), + actionName, + originalMessage: { + linkedReportID: reportID.toString(), + }, + }; return {[reportID]: reportAction}; }); diff --git a/tests/utils/collections/reportActions.ts b/tests/utils/collections/reportActions.ts index cc258e89c041..4efff8b3748a 100644 --- a/tests/utils/collections/reportActions.ts +++ b/tests/utils/collections/reportActions.ts @@ -1,4 +1,4 @@ -import {rand, randAggregation, randBoolean, randPastDate, randWord} from '@ngneat/falso'; +import {rand, randAggregation, randBoolean, randWord} from '@ngneat/falso'; import CONST from '@src/CONST'; import type {ReportAction} from '@src/types/onyx'; @@ -17,6 +17,19 @@ const flattenActionNamesValues = (actionNames: any) => { return result; }; +const addZero = (value: number): string | number => (value < 10 ? `0${value}` : value); + +const getRandomDate = (): string => { + const randomTimestamp = Math.random() * new Date().getTime(); + const randomDate = new Date(randomTimestamp); + + const formattedDate = `${randomDate.getFullYear()}-${addZero(randomDate.getMonth() + 1)}-${addZero(randomDate.getDate())} ${addZero(randomDate.getHours())}:${addZero( + randomDate.getMinutes(), + )}:${addZero(randomDate.getSeconds())}.${randomDate.getMilliseconds()}`; + + return formattedDate; +}; + export default function createRandomReportAction(index: number): ReportAction { return { // we need to add any here because of the way we are generating random values @@ -32,7 +45,7 @@ export default function createRandomReportAction(index: number): ReportAction { text: randWord(), }, ], - created: randPastDate().toISOString(), + created: getRandomDate(), message: [ { type: randWord(), @@ -57,13 +70,13 @@ export default function createRandomReportAction(index: number): ReportAction { ], originalMessage: { html: randWord(), - type: rand(Object.values(CONST.IOU.REPORT_ACTION_TYPE)), + lastModified: getRandomDate(), }, whisperedToAccountIDs: randAggregation(), avatar: randWord(), automatic: randBoolean(), shouldShow: randBoolean(), - lastModified: randPastDate().toISOString(), + lastModified: getRandomDate(), pendingAction: rand(Object.values(CONST.RED_BRICK_ROAD_PENDING_ACTION)), delegateAccountID: index, errors: {}, From 76cbb772fe5e4370374d57cd19a27ee3695d3d75 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Tue, 30 Jan 2024 13:23:07 +0000 Subject: [PATCH 105/142] Revert testing change --- src/components/MoneyReportHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 470d03e639ad..4b4e3915f969 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -78,7 +78,7 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton; const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0; const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; - const shouldShowNextStep = !!nextStep?.message?.length; + const shouldShowNextStep = isFromPaidPolicy && !!nextStep?.message?.length; const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep; const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency); From 30c2a90b3f039a048f08688a4dc8af77ffb570f9 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 30 Jan 2024 20:22:42 +0530 Subject: [PATCH 106/142] update comment. Signed-off-by: Krishna Gupta --- .../UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx index 2555836b90a1..4535874c3fac 100644 --- a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx +++ b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx @@ -41,8 +41,7 @@ function BaseUserDetailsTooltip({accountID, fallbackUserDetails, icon, delegateA if (icon && (icon.type === CONST.ICON_TYPE_WORKSPACE || !title)) { title = icon.name ?? ''; - // We need to clear the subtitle for the workspaces otherwise user's email will be shown as subtitle - // Also we need to clear only when icon is of type `Workspace` + // We need to clear the subtitle for workspaces so that we don't display any user details under the workspace name if (icon.type === CONST.ICON_TYPE_WORKSPACE) { subtitle = ''; } From b20246681b7d2ea44a4deb1bc3dba693208f9f7c Mon Sep 17 00:00:00 2001 From: Github Date: Tue, 30 Jan 2024 17:11:52 +0100 Subject: [PATCH 107/142] test --- src/pages/home/ReportScreen.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 4d043f12351e..7219b730dac3 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -468,10 +468,10 @@ function ReportScreen({ [report, reportMetadata, isLoading, shouldHideReport, isOptimisticDelete, userLeavingStatus], ); - const actionListValue = useMemo(() => ({flatListRef, scrollPosition, setScrollPosition}), [flatListRef, scrollPosition, setScrollPosition]); + const actionListValues = useMemo(() => ({flatListRef, scrollPosition, setScrollPosition}), [flatListRef, scrollPosition, setScrollPosition]); return ( - + Date: Tue, 30 Jan 2024 17:21:41 +0100 Subject: [PATCH 108/142] Revert "test" This reverts commit b20246681b7d2ea44a4deb1bc3dba693208f9f7c. --- src/pages/home/ReportScreen.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 7219b730dac3..4d043f12351e 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -468,10 +468,10 @@ function ReportScreen({ [report, reportMetadata, isLoading, shouldHideReport, isOptimisticDelete, userLeavingStatus], ); - const actionListValues = useMemo(() => ({flatListRef, scrollPosition, setScrollPosition}), [flatListRef, scrollPosition, setScrollPosition]); + const actionListValue = useMemo(() => ({flatListRef, scrollPosition, setScrollPosition}), [flatListRef, scrollPosition, setScrollPosition]); return ( - + Date: Tue, 30 Jan 2024 19:09:38 +0000 Subject: [PATCH 109/142] Add new harvesting policy key --- src/types/onyx/Policy.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index eca7e9d1ee06..63319a1c67bc 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -88,10 +88,15 @@ type Policy = { /** The scheduled submit frequency set up on the this policy */ autoReportingFrequency?: ValueOf; - /** Whether the scheduled submit is enabled */ + /** @deprecated Whether the scheduled submit is enabled */ isHarvestingEnabled?: boolean; /** Whether the scheduled submit is enabled */ + harvesting?: { + enabled: boolean; + } + + /** Whether the self approval or submitting is enabled */ isPreventSelfApprovalEnabled?: boolean; /** When the monthly scheduled submit should happen */ From 65f9daddbd1f6b83fdc2facb254b6a49202641a6 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Tue, 30 Jan 2024 19:22:38 +0000 Subject: [PATCH 110/142] Clean up other usages of isHarvestingEnabled --- src/components/MoneyReportHeader.tsx | 9 ++++++--- src/components/ReportActionItem/ReportPreview.tsx | 9 ++++++--- src/libs/actions/IOU.js | 2 +- tests/utils/LHNTestUtils.js | 4 +++- tests/utils/collections/policies.ts | 4 +++- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 4b4e3915f969..2f578c625cae 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -76,7 +76,10 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt return isManager && !isDraft && !isApproved && !isSettled; }, [isPaidGroupPolicy, isManager, isDraft, isApproved, isSettled]); const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton; - const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0; + const shouldShowSubmitButton = useMemo( + () => isDraft && reimbursableSpend !== 0, + [isDraft, reimbursableSpend], + ); const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; const shouldShowNextStep = isFromPaidPolicy && !!nextStep?.message?.length; const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep; @@ -86,8 +89,8 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt // The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on const isWaitingForSubmissionFromCurrentUser = useMemo( - () => chatReport?.isOwnPolicyExpenseChat && !policy.isHarvestingEnabled, - [chatReport?.isOwnPolicyExpenseChat, policy.isHarvestingEnabled], + () => chatReport?.isOwnPolicyExpenseChat && !(policy.harvesting?.enabled ?? policy.isHarvestingEnabled), + [chatReport?.isOwnPolicyExpenseChat, policy.harvesting?.enabled, policy.isHarvestingEnabled], ); const threeDotsMenuItems = [HeaderUtils.getPinMenuItem(moneyRequestReport)]; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index b2fece085f57..b2714cc58a4a 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -155,12 +155,15 @@ function ReportPreview({ scanningReceipts: numberOfScanningReceipts, }); - const shouldShowSubmitButton = isDraftExpenseReport && reimbursableSpend !== 0; + const shouldShowSubmitButton = useMemo( + () => isDraftExpenseReport && reimbursableSpend !== 0, + [isDraftExpenseReport, reimbursableSpend], + ); // The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on const isWaitingForSubmissionFromCurrentUser = useMemo( - () => chatReport?.isOwnPolicyExpenseChat && !policy?.isHarvestingEnabled, - [chatReport?.isOwnPolicyExpenseChat, policy?.isHarvestingEnabled], + () => chatReport?.isOwnPolicyExpenseChat && !(policy?.harvesting?.enabled ?? policy?.isHarvestingEnabled), + [chatReport?.isOwnPolicyExpenseChat, policy?.harvesting?.enabled, policy?.isHarvestingEnabled], ); const getDisplayAmount = (): string => { diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index a9e1b09ed984..0e9fd4bcb94a 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -719,7 +719,7 @@ function getMoneyRequestInformation( isFromPaidPolicy = PolicyUtils.isPaidGroupPolicy(policy); // If the scheduled submit is turned off on the policy, user needs to manually submit the report which is indicated by GBR in LHN - needsToBeManuallySubmitted = isFromPaidPolicy && !(policy.isHarvestingEnabled || false); + needsToBeManuallySubmitted = isFromPaidPolicy && !(lodashGet(policy, 'harvesting.enabled', policy.isHarvestingEnabled) || false); // If the linked expense report on paid policy is not draft, we need to create a new draft expense report if (iouReport && isFromPaidPolicy && !ReportUtils.isDraftExpenseReport(iouReport)) { diff --git a/tests/utils/LHNTestUtils.js b/tests/utils/LHNTestUtils.js index 6c72558e5df3..04246c1c438a 100644 --- a/tests/utils/LHNTestUtils.js +++ b/tests/utils/LHNTestUtils.js @@ -256,7 +256,9 @@ function getFakePolicy(id = 1, name = 'Workspace-Test-001') { lastModified: 1697323926777105, autoReporting: true, autoReportingFrequency: 'immediate', - isHarvestingEnabled: true, + harvesting: { + enabled: true, + }, autoReportingOffset: 1, isPreventSelfApprovalEnabled: true, submitsTo: 123456, diff --git a/tests/utils/collections/policies.ts b/tests/utils/collections/policies.ts index 8547c171c7a7..4223c7e41941 100644 --- a/tests/utils/collections/policies.ts +++ b/tests/utils/collections/policies.ts @@ -11,7 +11,9 @@ export default function createRandomPolicy(index: number): Policy { autoReporting: randBoolean(), isPolicyExpenseChatEnabled: randBoolean(), autoReportingFrequency: rand(Object.values(CONST.POLICY.AUTO_REPORTING_FREQUENCIES)), - isHarvestingEnabled: randBoolean(), + harvesting: { + enabled: randBoolean(), + }, autoReportingOffset: 1, isPreventSelfApprovalEnabled: randBoolean(), submitsTo: index, From d0ab3e5bb6d3c068b8af6ad029211545c2c7e208 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Tue, 30 Jan 2024 19:25:27 +0000 Subject: [PATCH 111/142] Remove submit memo change --- src/components/MoneyReportHeader.tsx | 5 +---- src/components/ReportActionItem/ReportPreview.tsx | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 2f578c625cae..c2e6ff341416 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -76,10 +76,7 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt return isManager && !isDraft && !isApproved && !isSettled; }, [isPaidGroupPolicy, isManager, isDraft, isApproved, isSettled]); const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton; - const shouldShowSubmitButton = useMemo( - () => isDraft && reimbursableSpend !== 0, - [isDraft, reimbursableSpend], - ); + const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0; const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; const shouldShowNextStep = isFromPaidPolicy && !!nextStep?.message?.length; const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index b2714cc58a4a..52e9d94eaefd 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -155,10 +155,7 @@ function ReportPreview({ scanningReceipts: numberOfScanningReceipts, }); - const shouldShowSubmitButton = useMemo( - () => isDraftExpenseReport && reimbursableSpend !== 0, - [isDraftExpenseReport, reimbursableSpend], - ); + const shouldShowSubmitButton = isDraftExpenseReport && reimbursableSpend !== 0; // The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on const isWaitingForSubmissionFromCurrentUser = useMemo( From 90583bccd62032aa13b8b0c41e517eb1ebd74468 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Tue, 30 Jan 2024 20:34:33 +0000 Subject: [PATCH 112/142] Fix style --- src/types/onyx/Policy.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 63319a1c67bc..719e0ba1fb9d 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -94,9 +94,9 @@ type Policy = { /** Whether the scheduled submit is enabled */ harvesting?: { enabled: boolean; - } + }; - /** Whether the self approval or submitting is enabled */ + /** Whether the self approval or submitting is enabled */ isPreventSelfApprovalEnabled?: boolean; /** When the monthly scheduled submit should happen */ From 022efe32a9c8dad2c62a43d646fe3e1dbbab21ce Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Wed, 31 Jan 2024 04:22:43 +0500 Subject: [PATCH 113/142] fix: minor refactors to function names --- .../ReportActionItem/MoneyReportView.tsx | 9 +++----- .../parameters/SetPolicyReportNameParams.ts | 6 ------ .../API/parameters/SetReportNameParams.ts | 6 ++++++ src/libs/API/parameters/index.ts | 2 +- src/libs/API/types.ts | 4 ++-- src/libs/ReportUtils.ts | 21 +++++++++++++++++++ src/libs/actions/Report.ts | 10 ++++++--- src/pages/EditReportFieldPage.tsx | 4 ++-- 8 files changed, 42 insertions(+), 20 deletions(-) delete mode 100644 src/libs/API/parameters/SetPolicyReportNameParams.ts create mode 100644 src/libs/API/parameters/SetReportNameParams.ts diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx index 5651d0b943ab..4af5ee7f86fa 100644 --- a/src/components/ReportActionItem/MoneyReportView.tsx +++ b/src/components/ReportActionItem/MoneyReportView.tsx @@ -59,12 +59,9 @@ function MoneyReportView({report, policy, policyReportFields, shouldShowHorizont ]; const sortedPolicyReportFields = useMemo((): PolicyReportField[] => { - const reportFields = Object.values(report.reportFields ?? {}); - const mergedFieldIds = Array.from(new Set([...policyReportFields.map(({fieldID}) => fieldID), ...reportFields.map(({fieldID}) => fieldID)])); - const mergedFields = mergedFieldIds.map((id) => report?.reportFields?.[id] ?? policyReportFields.find(({fieldID}) => fieldID === id)) as PolicyReportField[]; - const allReportFields = isSettled ? reportFields : mergedFields; - return allReportFields.sort(({orderWeight: firstOrderWeight}, {orderWeight: secondOrderWeight}) => firstOrderWeight - secondOrderWeight); - }, [policyReportFields, report.reportFields, isSettled]); + const fields = ReportUtils.getAvailableReportFields(report, policyReportFields); + return fields.sort(({orderWeight: firstOrderWeight}, {orderWeight: secondOrderWeight}) => firstOrderWeight - secondOrderWeight); + }, [policyReportFields, report]); return ( diff --git a/src/libs/API/parameters/SetPolicyReportNameParams.ts b/src/libs/API/parameters/SetPolicyReportNameParams.ts deleted file mode 100644 index d6310ee18115..000000000000 --- a/src/libs/API/parameters/SetPolicyReportNameParams.ts +++ /dev/null @@ -1,6 +0,0 @@ -type SetPolicyReportNameParams = { - reportID: string; - reportName: string; -}; - -export default SetPolicyReportNameParams; diff --git a/src/libs/API/parameters/SetReportNameParams.ts b/src/libs/API/parameters/SetReportNameParams.ts new file mode 100644 index 000000000000..784674e1486e --- /dev/null +++ b/src/libs/API/parameters/SetReportNameParams.ts @@ -0,0 +1,6 @@ +type SetReportNameParams = { + reportID: string; + reportName: string; +}; + +export default SetReportNameParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 635887b33e4b..b2a68fca8562 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -123,4 +123,4 @@ export type {default as CompleteTaskParams} from './CompleteTaskParams'; export type {default as CompleteEngagementModalParams} from './CompleteEngagementModalParams'; export type {default as SetNameValuePairParams} from './SetNameValuePairParams'; export type {default as SetPolicyReportFieldParams} from './SetPolicyReportFieldParams'; -export type {default as SetPolicyReportNameParams} from './SetPolicyReportNameParams'; +export type {default as SetReportNameParams} from './SetReportNameParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index ca13e828f1bd..83f30700a2c6 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -114,7 +114,7 @@ const WRITE_COMMANDS = { COMPLETE_ENGAGEMENT_MODAL: 'CompleteEngagementModal', SET_NAME_VALUE_PAIR: 'SetNameValuePair', SET_POLICY_REPORT_FIELD: 'Report_SetFields', - SET_POLICY_REPORT_NAME: 'RenameReport', + SET_REPORT_NAME: 'RenameReport', } as const; type WriteCommand = ValueOf; @@ -226,7 +226,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.COMPLETE_ENGAGEMENT_MODAL]: Parameters.CompleteEngagementModalParams; [WRITE_COMMANDS.SET_NAME_VALUE_PAIR]: Parameters.SetNameValuePairParams; [WRITE_COMMANDS.SET_POLICY_REPORT_FIELD]: Parameters.SetPolicyReportFieldParams; - [WRITE_COMMANDS.SET_POLICY_REPORT_NAME]: Parameters.SetPolicyReportNameParams; + [WRITE_COMMANDS.SET_REPORT_NAME]: Parameters.SetReportNameParams; }; const READ_COMMANDS = { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9f3d52eecd48..0f656b05f252 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1913,6 +1913,26 @@ function getReportFieldsByPolicyID(policyID: string) { return Object.entries(allPolicyReportFields ?? {}).find(([key]) => key.replace(ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS, '') === policyID)?.[1]; } +/** + * Get the report fields that we should display a MoneyReportView gets opened + */ + +function getAvailableReportFields(report: Report, 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.reportFields ?? {}); + 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) { + return reportFields; + } + + // If the report is unsettled, we want to merge the new fields that get added to the policy with the fields that + // are attached to the report. + const mergedFieldIds = Array.from(new Set([...policyReportFields.map(({fieldID}) => fieldID), ...reportFields.map(({fieldID}) => fieldID)])); + return mergedFieldIds.map((id) => report?.reportFields?.[id] ?? policyReportFields.find(({fieldID}) => fieldID === id)) as PolicyReportField[]; +} + /** * Get the title for an IOU or expense chat which will be showing the payer and the amount */ @@ -4843,6 +4863,7 @@ export { isValidReport, isReportFieldOfTypeTitle, isReportFieldDisabled, + getAvailableReportFields, }; export type { diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 3dad99f59df1..f60fb0a62b1f 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -1468,7 +1468,7 @@ function toggleSubscribeToChildReport(childReportID = '0', parentReportAction: P } } -function updatePolicyReportName(reportID: string, value: string) { +function updateReportName(reportID: string, value: string, previousValue: string) { const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -1486,6 +1486,7 @@ function updatePolicyReportName(reportID: string, value: string) { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { + reportName: previousValue, pendingFields: { reportName: null, }, @@ -1519,7 +1520,7 @@ function updatePolicyReportName(reportID: string, value: string) { API.write('RenameReport', parameters, {optimisticData, failureData, successData}); } -function updatePolicyReportField(reportID: string, reportField: PolicyReportField) { +function updatePolicyReportField(reportID: string, reportField: PolicyReportField, previousReportField: PolicyReportField) { const recentlyUsedValues = allRecentlyUsedReportFields?.[reportField.fieldID] ?? []; const optimisticData: OnyxUpdate[] = [ @@ -1552,6 +1553,9 @@ function updatePolicyReportField(reportID: string, reportField: PolicyReportFiel onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { + reportFields: { + [reportField.fieldID]: previousReportField, + }, pendingFields: { [reportField.fieldID]: null, }, @@ -2855,6 +2859,6 @@ export { updateLastVisitTime, clearNewRoomFormError, updatePolicyReportField, - updatePolicyReportName, + updateReportName, resolveActionableMentionWhisper, }; diff --git a/src/pages/EditReportFieldPage.tsx b/src/pages/EditReportFieldPage.tsx index 24ade45993f6..b2d04e714d27 100644 --- a/src/pages/EditReportFieldPage.tsx +++ b/src/pages/EditReportFieldPage.tsx @@ -66,9 +66,9 @@ function EditReportFieldPage({route, policy, report, policyReportFields}: EditRe const handleReportFieldChange = (form: OnyxFormValuesFields) => { const value = form[reportField.fieldID].toString() || ''; if (isReportFieldTitle) { - ReportActions.updatePolicyReportName(report.reportID, value); + ReportActions.updateReportName(report.reportID, value, report.reportName ?? ''); } else { - ReportActions.updatePolicyReportField(report.reportID, {...reportField, value}); + ReportActions.updatePolicyReportField(report.reportID, {...reportField, value}, reportField); } Navigation.dismissModal(report?.reportID); From db3738de68e25edb3bc5c7eb59b79d06329b62a3 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Wed, 31 Jan 2024 04:32:01 +0500 Subject: [PATCH 114/142] fix: lint issues --- src/libs/API/parameters/SetPolicyReportFieldParams.ts | 6 ------ src/libs/API/parameters/SetReportFieldParams.ts | 6 ++++++ src/libs/API/parameters/index.ts | 2 +- src/libs/API/types.ts | 4 ++-- src/libs/actions/Report.ts | 8 ++++---- src/pages/EditReportFieldPage.tsx | 2 +- src/pages/EditReportFieldTextPage.tsx | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) delete mode 100644 src/libs/API/parameters/SetPolicyReportFieldParams.ts create mode 100644 src/libs/API/parameters/SetReportFieldParams.ts diff --git a/src/libs/API/parameters/SetPolicyReportFieldParams.ts b/src/libs/API/parameters/SetPolicyReportFieldParams.ts deleted file mode 100644 index 94f059057b58..000000000000 --- a/src/libs/API/parameters/SetPolicyReportFieldParams.ts +++ /dev/null @@ -1,6 +0,0 @@ -type SetPolicyReportFieldParams = { - reportID: string; - reportFields: string; -}; - -export default SetPolicyReportFieldParams; diff --git a/src/libs/API/parameters/SetReportFieldParams.ts b/src/libs/API/parameters/SetReportFieldParams.ts new file mode 100644 index 000000000000..8b6c8682d657 --- /dev/null +++ b/src/libs/API/parameters/SetReportFieldParams.ts @@ -0,0 +1,6 @@ +type SetReportFieldParams = { + reportID: string; + reportFields: string; +}; + +export default SetReportFieldParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index b2a68fca8562..8c0c2fde17cf 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -122,5 +122,5 @@ export type {default as ReopenTaskParams} from './ReopenTaskParams'; export type {default as CompleteTaskParams} from './CompleteTaskParams'; export type {default as CompleteEngagementModalParams} from './CompleteEngagementModalParams'; export type {default as SetNameValuePairParams} from './SetNameValuePairParams'; -export type {default as SetPolicyReportFieldParams} from './SetPolicyReportFieldParams'; +export type {default as SetReportFieldParams} from './SetReportFieldParams'; export type {default as SetReportNameParams} from './SetReportNameParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 83f30700a2c6..05b658ee0702 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -113,7 +113,7 @@ const WRITE_COMMANDS = { COMPLETE_TASK: 'CompleteTask', COMPLETE_ENGAGEMENT_MODAL: 'CompleteEngagementModal', SET_NAME_VALUE_PAIR: 'SetNameValuePair', - SET_POLICY_REPORT_FIELD: 'Report_SetFields', + SET_REPORT_FIELD: 'Report_SetFields', SET_REPORT_NAME: 'RenameReport', } as const; @@ -225,7 +225,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.COMPLETE_TASK]: Parameters.CompleteTaskParams; [WRITE_COMMANDS.COMPLETE_ENGAGEMENT_MODAL]: Parameters.CompleteEngagementModalParams; [WRITE_COMMANDS.SET_NAME_VALUE_PAIR]: Parameters.SetNameValuePairParams; - [WRITE_COMMANDS.SET_POLICY_REPORT_FIELD]: Parameters.SetPolicyReportFieldParams; + [WRITE_COMMANDS.SET_REPORT_FIELD]: Parameters.SetReportFieldParams; [WRITE_COMMANDS.SET_REPORT_NAME]: Parameters.SetReportNameParams; }; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index f60fb0a62b1f..38a73c1d3055 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -1517,10 +1517,10 @@ function updateReportName(reportID: string, value: string, previousValue: string reportName: value, }; - API.write('RenameReport', parameters, {optimisticData, failureData, successData}); + API.write(WRITE_COMMANDS.SET_REPORT_NAME, parameters, {optimisticData, failureData, successData}); } -function updatePolicyReportField(reportID: string, reportField: PolicyReportField, previousReportField: PolicyReportField) { +function updateReportField(reportID: string, reportField: PolicyReportField, previousReportField: PolicyReportField) { const recentlyUsedValues = allRecentlyUsedReportFields?.[reportField.fieldID] ?? []; const optimisticData: OnyxUpdate[] = [ @@ -1596,7 +1596,7 @@ function updatePolicyReportField(reportID: string, reportField: PolicyReportFiel reportFields: JSON.stringify({[reportField.fieldID]: {fieldID: reportField.fieldID, value: reportField.value, type: reportField.type, name: reportField.name}}), }; - API.write('Report_SetFields', parameters, {optimisticData, failureData, successData}); + API.write(WRITE_COMMANDS.SET_REPORT_FIELD, parameters, {optimisticData, failureData, successData}); } function updateWelcomeMessage(reportID: string, previousValue: string, newValue: string) { @@ -2858,7 +2858,7 @@ export { getDraftPrivateNote, updateLastVisitTime, clearNewRoomFormError, - updatePolicyReportField, + updateReportField, updateReportName, resolveActionableMentionWhisper, }; diff --git a/src/pages/EditReportFieldPage.tsx b/src/pages/EditReportFieldPage.tsx index b2d04e714d27..602f2eed161e 100644 --- a/src/pages/EditReportFieldPage.tsx +++ b/src/pages/EditReportFieldPage.tsx @@ -68,7 +68,7 @@ function EditReportFieldPage({route, policy, report, policyReportFields}: EditRe if (isReportFieldTitle) { ReportActions.updateReportName(report.reportID, value, report.reportName ?? ''); } else { - ReportActions.updatePolicyReportField(report.reportID, {...reportField, value}, reportField); + ReportActions.updateReportField(report.reportID, {...reportField, value}, reportField); } Navigation.dismissModal(report?.reportID); diff --git a/src/pages/EditReportFieldTextPage.tsx b/src/pages/EditReportFieldTextPage.tsx index 096967cf4003..bb1a42103da1 100644 --- a/src/pages/EditReportFieldTextPage.tsx +++ b/src/pages/EditReportFieldTextPage.tsx @@ -38,7 +38,7 @@ function EditReportFieldTextPage({fieldName, onSubmit, fieldValue, isRequired, f const validate = useCallback( (values: OnyxFormValuesFields) => { const errors: Errors = {}; - if (isRequired && values[fieldID].toString().trim() === '') { + if (isRequired && (values[fieldID] as string).trim() === '') { errors[fieldID] = 'common.error.fieldRequired'; } return errors; From d0db92fda86a6f7aec6736dd21e882c11e4d4af6 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Wed, 31 Jan 2024 04:46:27 +0500 Subject: [PATCH 115/142] fix: use ucfirst str lib function and handle more lint issues --- src/ONYXKEYS.ts | 8 ++++---- src/components/ReportActionItem/MoneyReportView.tsx | 3 ++- src/pages/EditReportFieldDatePage.tsx | 8 ++++---- src/pages/EditReportFieldPage.tsx | 11 ++++++----- src/pages/EditReportFieldTextPage.tsx | 8 ++++---- src/types/onyx/Form.ts | 4 +++- src/types/onyx/index.ts | 3 ++- 7 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index e3c42238318d..b4ca0c898b0b 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -358,8 +358,8 @@ const ONYXKEYS = { REPORT_VIRTUAL_CARD_FRAUD_DRAFT: 'reportVirtualCardFraudFormDraft', GET_PHYSICAL_CARD_FORM: 'getPhysicalCardForm', GET_PHYSICAL_CARD_FORM_DRAFT: 'getPhysicalCardFormDraft', - POLICY_REPORT_FIELD_EDIT_FORM: 'policyReportFieldEditForm', - POLICY_REPORT_FIELD_EDIT_FORM_DRAFT: 'policyReportFieldEditFormDraft', + REPORT_FIELD_EDIT_FORM: 'reportFieldEditForm', + REPORT_FIELD_EDIT_FORM_DRAFT: 'reportFieldEditFormDraft', REIMBURSEMENT_ACCOUNT_FORM: 'reimbursementAccount', REIMBURSEMENT_ACCOUNT_FORM_DRAFT: 'reimbursementAccountDraft', }, @@ -542,8 +542,8 @@ type OnyxValues = { [ONYXKEYS.FORMS.REPORT_PHYSICAL_CARD_FORM_DRAFT]: OnyxTypes.Form; [ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM]: OnyxTypes.Form; [ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM_DRAFT]: OnyxTypes.Form; - [ONYXKEYS.FORMS.POLICY_REPORT_FIELD_EDIT_FORM]: OnyxTypes.Form; - [ONYXKEYS.FORMS.POLICY_REPORT_FIELD_EDIT_FORM_DRAFT]: OnyxTypes.Form; + [ONYXKEYS.FORMS.REPORT_FIELD_EDIT_FORM]: OnyxTypes.ReportFieldEditForm; + [ONYXKEYS.FORMS.REPORT_FIELD_EDIT_FORM_DRAFT]: OnyxTypes.Form; // @ts-expect-error Different values are defined under the same key: ReimbursementAccount and ReimbursementAccountForm [ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM]: OnyxTypes.Form; [ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT]: OnyxTypes.Form; diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx index 4af5ee7f86fa..3c0e50b2c940 100644 --- a/src/components/ReportActionItem/MoneyReportView.tsx +++ b/src/components/ReportActionItem/MoneyReportView.tsx @@ -1,3 +1,4 @@ +import Str from 'expensify-common/lib/str'; import React, {useMemo} from 'react'; import type {StyleProp, TextStyle} from 'react-native'; import {View} from 'react-native'; @@ -81,7 +82,7 @@ function MoneyReportView({report, policy, policyReportFields, shouldShowHorizont key={`menuItem-${reportField.fieldID}`} > Navigation.navigate(ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute(report.reportID, report.policyID ?? '', reportField.fieldID))} shouldShowRightIcon diff --git a/src/pages/EditReportFieldDatePage.tsx b/src/pages/EditReportFieldDatePage.tsx index 2496e5b23bf2..82659eca62c2 100644 --- a/src/pages/EditReportFieldDatePage.tsx +++ b/src/pages/EditReportFieldDatePage.tsx @@ -27,7 +27,7 @@ type EditReportFieldDatePageProps = { isRequired: boolean; /** Callback to fire when the Save button is pressed */ - onSubmit: (form: OnyxFormValuesFields) => void; + onSubmit: (form: OnyxFormValuesFields) => void; }; function EditReportFieldDatePage({fieldName, isRequired, onSubmit, fieldValue, fieldID}: EditReportFieldDatePageProps) { @@ -36,9 +36,9 @@ function EditReportFieldDatePage({fieldName, isRequired, onSubmit, fieldValue, f const inputRef = useRef(null); const validate = useCallback( - (value: OnyxFormValuesFields) => { + (value: OnyxFormValuesFields) => { const errors: Errors = {}; - if (isRequired && value[fieldID].toString().trim() === '') { + if (isRequired && value[fieldID].trim() === '') { errors[fieldID] = 'common.error.fieldRequired'; } return errors; @@ -58,7 +58,7 @@ function EditReportFieldDatePage({fieldName, isRequired, onSubmit, fieldValue, f ) => { - const value = form[reportField.fieldID].toString() || ''; + const handleReportFieldChange = (form: OnyxFormValuesFields) => { + const value = form[reportField.fieldID] || ''; if (isReportFieldTitle) { ReportActions.updateReportName(report.reportID, value, report.reportName ?? ''); } else { @@ -79,7 +80,7 @@ function EditReportFieldPage({route, policy, report, policyReportFields}: EditRe if (reportField.type === 'text' || isReportFieldTitle) { return ( ) => void; + onSubmit: (form: OnyxFormValuesFields) => void; }; function EditReportFieldTextPage({fieldName, onSubmit, fieldValue, isRequired, fieldID}: EditReportFieldTextPageProps) { @@ -36,9 +36,9 @@ function EditReportFieldTextPage({fieldName, onSubmit, fieldValue, isRequired, f const inputRef = useRef(null); const validate = useCallback( - (values: OnyxFormValuesFields) => { + (values: OnyxFormValuesFields) => { const errors: Errors = {}; - if (isRequired && (values[fieldID] as string).trim() === '') { + if (isRequired && values[fieldID].trim() === '') { errors[fieldID] = 'common.error.fieldRequired'; } return errors; @@ -58,7 +58,7 @@ function EditReportFieldTextPage({fieldName, onSubmit, fieldValue, isRequired, f ; +type ReportFieldEditForm = Form>; + export default Form; -export type {AddDebitCardForm, DateOfBirthForm, PrivateNotesForm, DisplayNameForm, FormValueType, NewRoomForm, BaseForm, IKnowATeacherForm, IntroSchoolPrincipalForm}; +export type {AddDebitCardForm, DateOfBirthForm, PrivateNotesForm, DisplayNameForm, FormValueType, NewRoomForm, BaseForm, IKnowATeacherForm, IntroSchoolPrincipalForm, ReportFieldEditForm}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 5b04cae58671..64eec736b5bf 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -9,7 +9,7 @@ import type Credentials from './Credentials'; import type Currency from './Currency'; import type CustomStatusDraft from './CustomStatusDraft'; import type Download from './Download'; -import type {AddDebitCardForm, DateOfBirthForm, DisplayNameForm, IKnowATeacherForm, IntroSchoolPrincipalForm, NewRoomForm, PrivateNotesForm} from './Form'; +import type {AddDebitCardForm, DateOfBirthForm, DisplayNameForm, IKnowATeacherForm, IntroSchoolPrincipalForm, NewRoomForm, PrivateNotesForm, ReportFieldEditForm} from './Form'; import type Form from './Form'; import type FrequentlyUsedEmoji from './FrequentlyUsedEmoji'; import type {FundList} from './Fund'; @@ -151,4 +151,5 @@ export type { IKnowATeacherForm, IntroSchoolPrincipalForm, PrivateNotesForm, + ReportFieldEditForm, }; From 0c9939ac37fc152c2fb7f3f73d12a86cfc77d933 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Wed, 31 Jan 2024 04:51:32 +0500 Subject: [PATCH 116/142] fix: include the whole report field object in the set report field call --- src/libs/actions/Report.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 38a73c1d3055..221c7f8d4f88 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -1593,7 +1593,7 @@ function updateReportField(reportID: string, reportField: PolicyReportField, pre const parameters = { reportID, - reportFields: JSON.stringify({[reportField.fieldID]: {fieldID: reportField.fieldID, value: reportField.value, type: reportField.type, name: reportField.name}}), + reportFields: JSON.stringify({[reportField.fieldID]: reportField}), }; API.write(WRITE_COMMANDS.SET_REPORT_FIELD, parameters, {optimisticData, failureData, successData}); From c8679c727db9755404599fa61943d3a36e1a1f68 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 31 Jan 2024 09:50:45 +0700 Subject: [PATCH 117/142] fix logic show new user engagement modal --- .../PurposeForUsingExpensifyModal.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx index c02815d74153..d464296edd84 100644 --- a/src/components/PurposeForUsingExpensifyModal.tsx +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -1,6 +1,8 @@ import {useNavigation} from '@react-navigation/native'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {ScrollView, View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -11,6 +13,7 @@ import * as Report from '@userActions/Report'; import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; +import ONYXKEYS from '@src/ONYXKEYS'; import SCREENS from '@src/SCREENS'; import HeaderWithBackButton from './HeaderWithBackButton'; import * as Expensicons from './Icon/Expensicons'; @@ -78,8 +81,12 @@ const menuIcons = { [CONST.INTRO_CHOICES.MANAGE_TEAM]: Expensicons.MoneyBag, [CONST.INTRO_CHOICES.CHAT_SPLIT]: Expensicons.Briefcase, }; +type PurposeForUsingExpensifyModalOnyxProps = { + isLoadingApp: OnyxEntry; +}; +type PurposeForUsingExpensifyModalProps = PurposeForUsingExpensifyModalOnyxProps; -function PurposeForUsingExpensifyModal() { +function PurposeForUsingExpensifyModal({isLoadingApp = false}: PurposeForUsingExpensifyModalProps) { const {translate} = useLocalize(); const StyleUtils = useStyleUtils(); const styles = useThemeStyles(); @@ -98,7 +105,7 @@ function PurposeForUsingExpensifyModal() { Welcome.show(routes, () => setIsModalOpen(true)); // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [isLoadingApp]); const closeModal = useCallback(() => { Report.dismissEngagementModal(); @@ -174,5 +181,8 @@ function PurposeForUsingExpensifyModal() { } PurposeForUsingExpensifyModal.displayName = 'PurposeForUsingExpensifyModal'; - -export default PurposeForUsingExpensifyModal; +export default withOnyx({ + isLoadingApp: { + key: ONYXKEYS.IS_LOADING_APP, + }, +})(PurposeForUsingExpensifyModal); From 5ea2e8c458a71e4d644d476307b95cf9fd442fcf Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 31 Jan 2024 14:24:51 +0700 Subject: [PATCH 118/142] fix lint --- src/components/AttachmentModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index 6176746b2758..1b986098657f 100755 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -131,7 +131,7 @@ type AttachmentModalProps = AttachmentModalOnyxProps & { isWorkspaceAvatar?: boolean; /** Denotes whether it can be an icon (ex: SVG) */ - maybeIcon?: boolean, + maybeIcon?: boolean; /** Whether it is a receipt attachment or not */ isReceiptAttachment?: boolean; From da78bf0f5dc6d4df15833b65e7044a64914c6058 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 31 Jan 2024 12:40:28 +0500 Subject: [PATCH 119/142] fix: crash and profile not updating --- src/libs/ReportUtils.ts | 2 +- .../ReportActionCompose/ReportActionCompose.js | 2 ++ src/pages/home/report/ReportActionItemCreated.tsx | 14 +++++++++++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8f0e6b7eee57..228db29aea6c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1439,7 +1439,7 @@ function getWorkspaceIcon(report: OnyxEntry, policy: OnyxEntry = */ function getIcons( report: OnyxEntry, - personalDetails: OnyxCollection = allPersonalDetails, + personalDetails: OnyxCollection, defaultIcon: UserUtils.AvatarSource | null = null, defaultName = '', defaultAccountID = -1, diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js index af871c42a439..cc07716209a2 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js @@ -341,6 +341,8 @@ function ReportActionCompose({ const isSendDisabled = isCommentEmpty || isBlockedFromConcierge || disabled || hasExceededMaxCommentLength; const handleSendMessage = useCallback(() => { + 'worklet'; + if (isSendDisabled || !isReportReadyForDisplay) { return; } diff --git a/src/pages/home/report/ReportActionItemCreated.tsx b/src/pages/home/report/ReportActionItemCreated.tsx index a7877914c7f4..f8345ca7d2d0 100644 --- a/src/pages/home/report/ReportActionItemCreated.tsx +++ b/src/pages/home/report/ReportActionItemCreated.tsx @@ -15,7 +15,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import {navigateToConciergeChatAndDeleteReport} from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Policy, Report} from '@src/types/onyx'; +import type {PersonalDetailsList, Policy, Report} from '@src/types/onyx'; import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground'; type ReportActionItemCreatedOnyxProps = { @@ -24,6 +24,9 @@ type ReportActionItemCreatedOnyxProps = { /** The policy object for the current route */ policy: OnyxEntry; + + /** Personal details of all the users */ + personalDetails: OnyxEntry; }; type ReportActionItemCreatedProps = ReportActionItemCreatedOnyxProps & { @@ -45,7 +48,7 @@ function ReportActionItemCreated(props: ReportActionItemCreatedProps) { return null; } - const icons = ReportUtils.getIcons(props.report); + const icons = ReportUtils.getIcons(props.report, props.personalDetails); const shouldDisableDetailPage = ReportUtils.shouldDisableDetailPage(props.report); return ( @@ -100,6 +103,10 @@ export default withOnyx `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, }, + + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, })( memo( ReportActionItemCreated, @@ -108,6 +115,7 @@ export default withOnyx Date: Wed, 31 Jan 2024 15:15:48 +0530 Subject: [PATCH 120/142] update: callback function to hook. Signed-off-by: Krishna Gupta --- src/hooks/useSearchTermAndSearch.ts | 22 +++++++++++++++++++ src/pages/NewChatPage.js | 14 +++--------- ...yForRefactorRequestParticipantsSelector.js | 14 ++---------- .../MoneyRequestParticipantsSelector.js | 18 ++++----------- 4 files changed, 31 insertions(+), 37 deletions(-) create mode 100644 src/hooks/useSearchTermAndSearch.ts diff --git a/src/hooks/useSearchTermAndSearch.ts b/src/hooks/useSearchTermAndSearch.ts new file mode 100644 index 000000000000..827b6c6d8bd1 --- /dev/null +++ b/src/hooks/useSearchTermAndSearch.ts @@ -0,0 +1,22 @@ +import type {Dispatch} from 'react'; +import {useCallback} from 'react'; +import * as Report from '@userActions/Report'; + +/** + * Hook for fetching reports when user updated search term and hasn't selected max number of participants + */ +const useSearchTermAndSearch = (setSearchTerm: Dispatch>, maxParticipantsReached: boolean) => { + const setSearchTermAndSearchInServer = useCallback( + (text = '') => { + if (text && !maxParticipantsReached) { + Report.searchInServer(text); + } + setSearchTerm(text); + }, + [maxParticipantsReached, setSearchTerm], + ); + + return setSearchTermAndSearchInServer; +}; + +export default useSearchTermAndSearch; diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 9624967a89a3..7dd328e6a0bd 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -11,6 +11,7 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useNetwork from '@hooks/useNetwork'; +import useSearchTermAndSearch from '@hooks/useSearchTermAndSearch'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import compose from '@libs/compose'; @@ -64,6 +65,8 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); const maxParticipantsReached = selectedOptions.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; + const setSearchTermAndSearchInServer = useSearchTermAndSearch(setSearchTerm, maxParticipantsReached); + const headerMessage = OptionsListUtils.getHeaderMessage( filteredPersonalDetails.length + filteredRecentReports.length !== 0, Boolean(filteredUserToInvite), @@ -229,17 +232,6 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i updateOptions(); }, [didScreenTransitionEnd, updateOptions]); - // When search term updates & user hasn't selected max number of participants we will fetch any reports - const setSearchTermAndSearchInServer = useCallback( - (text = '') => { - if (text && !maxParticipantsReached) { - Report.searchInServer(text); - } - setSearchTerm(text); - }, - [maxParticipantsReached], - ); - const {inputCallbackRef} = useAutoFocusInput(); return ( diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index b25b545a5382..e47b0b8198ea 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -13,8 +13,8 @@ import SelectCircle from '@components/SelectCircle'; import SelectionList from '@components/SelectionList'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useSearchTermAndSearch from '@hooks/useSearchTermAndSearch'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as Report from '@libs/actions/Report'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -88,6 +88,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ const offlineMessage = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; const maxParticipantsReached = participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; + const setSearchTermAndSearchInServer = useSearchTermAndSearch(setSearchTerm, maxParticipantsReached); /** * Returns the sections needed for the OptionsSelector @@ -244,17 +245,6 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ [maxParticipantsReached, newChatOptions, participants, searchTerm], ); - // When search term updates & user hasn't selected max number of participants we will fetch any reports - const setSearchTermAndSearchInServer = useCallback( - (text = '') => { - if (text && !maxParticipantsReached) { - Report.searchInServer(text); - } - setSearchTerm(text); - }, - [maxParticipantsReached], - ); - // Right now you can't split a request with a workspace and other additional participants // This is getting properly fixed in https://github.com/Expensify/App/issues/27508, but as a stop-gap to prevent // the app from crashing on native when you try to do this, we'll going to hide the button if you have a workspace and other participants diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index e62d77f1a52a..daaa63aae147 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -13,8 +13,8 @@ import SelectCircle from '@components/SelectCircle'; import SelectionList from '@components/SelectionList'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useSearchTermAndSearch from '@hooks/useSearchTermAndSearch'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as Report from '@libs/actions/Report'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import reportPropTypes from '@pages/reportPropTypes'; @@ -89,6 +89,9 @@ function MoneyRequestParticipantsSelector({ const {isOffline} = useNetwork(); const personalDetails = usePersonalDetails(); + const maxParticipantsReached = participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; + const setSearchTermAndSearchInServer = useSearchTermAndSearch(setSearchTerm, maxParticipantsReached); + const offlineMessage = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; const newChatOptions = useMemo(() => { @@ -124,8 +127,6 @@ function MoneyRequestParticipantsSelector({ }; }, [betas, reports, participants, personalDetails, searchTerm, iouType, isDistanceRequest]); - const maxParticipantsReached = participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; - /** * Returns the sections needed for the OptionsSelector * @@ -260,17 +261,6 @@ function MoneyRequestParticipantsSelector({ [maxParticipantsReached, newChatOptions.personalDetails.length, newChatOptions.recentReports.length, newChatOptions.userToInvite, participants, searchTerm], ); - // When search term updates & user hasn't selected max number of participants we will fetch any reports - const setSearchTermAndSearchInServer = useCallback( - (text = '') => { - if (text && !maxParticipantsReached) { - Report.searchInServer(text); - } - setSearchTerm(text); - }, - [maxParticipantsReached], - ); - // Right now you can't split a request with a workspace and other additional participants // This is getting properly fixed in https://github.com/Expensify/App/issues/27508, but as a stop-gap to prevent // the app from crashing on native when you try to do this, we'll going to show error message if you have a workspace and other participants From 6dbc81eb6db46db01a1251838042d1b412a61592 Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Wed, 31 Jan 2024 13:33:31 +0200 Subject: [PATCH 121/142] Add newest auto reporting frequency --- src/CONST.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CONST.ts b/src/CONST.ts index 1ccdfd9a82a8..f434aa281866 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1301,6 +1301,7 @@ const CONST = { USER: 'user', }, AUTO_REPORTING_FREQUENCIES: { + INSTANT: 'instant', IMMEDIATE: 'immediate', WEEKLY: 'weekly', SEMI_MONTHLY: 'semimonthly', From c9113e75123723f3a225234ed2d60431472f96fd Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Wed, 31 Jan 2024 13:33:48 +0200 Subject: [PATCH 122/142] Fix comment typo --- src/types/onyx/Policy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index eca7e9d1ee06..2a3477ba7c2c 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -85,7 +85,7 @@ type Policy = { /** Whether the auto reporting is enabled */ autoReporting?: boolean; - /** The scheduled submit frequency set up on the this policy */ + /** The scheduled submit frequency set up on this policy */ autoReportingFrequency?: ValueOf; /** Whether the scheduled submit is enabled */ From 652f54f83f7afcd22a53072b877623d12e0f4947 Mon Sep 17 00:00:00 2001 From: Conor Pendergrast Date: Thu, 1 Feb 2024 01:11:32 +1300 Subject: [PATCH 123/142] Update Preferences.md --- .../articles/new-expensify/account-settings/Preferences.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/articles/new-expensify/account-settings/Preferences.md b/docs/articles/new-expensify/account-settings/Preferences.md index a31be00a02bb..16d001d2b835 100644 --- a/docs/articles/new-expensify/account-settings/Preferences.md +++ b/docs/articles/new-expensify/account-settings/Preferences.md @@ -3,7 +3,8 @@ title: Preferences description: How to manage your Expensify Preferences --- # Overview -Your Preferences in Expensify allow you to customise how you interact with New Expensify. +Your Preferences in Expensify allow you to customise how you use New Expensify. + - Set your theme preference # How to set your theme preference in New Expensify @@ -15,6 +16,6 @@ To set or update your theme preference in New Expensify: _Use Device Settings_ is the default setting. -Selecting _Use Device Settings_ will use your device's theme settings. For example, if you have your device set to switch to light theme during daylight hours, and dark theme during after sunset, we'll match that. +Selecting _Use Device Settings_ will use your device's theme settings. For example, if your device is set to adjust the appearance from light to dark during the day, we'll match that. -Your theme preference will sync across all of the ways you use New Expensify (mobile app, web app or OSX desktop app). +Your theme preference will sync across all your New Expensify apps (mobile, web, or OSX desktop apps). From 8ed596858c400051ab3e3d2899141f3082416ed0 Mon Sep 17 00:00:00 2001 From: caitlinwhite1 Date: Wed, 31 Jan 2024 06:19:49 -0600 Subject: [PATCH 124/142] Update redirects.csv URL was missing for global reimbursmenets --- docs/redirects.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/redirects.csv b/docs/redirects.csv index 4881e3f50a31..1f90b1565771 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -30,7 +30,7 @@ https://community.expensify.com/discussion/5124/how-to-add-your-name-and-photo-t https://community.expensify.com/discussion/5149/how-to-manage-your-devices-in-expensify,https://help.expensify.com/articles/expensify-classic/account-settings/Account-Details https://community.expensify.com/discussion/4432/how-to-add-a-secondary-login,https://help.expensify.com/articles/expensify-classic/account-settings/Account-Details https://community.expensify.com/discussion/6794/how-to-change-your-email-in-expensify,https://help.expensify.com/articles/expensify-classic/account-settings/Account-Details -Reimbursements,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/Global-Reimbursements +https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/International-Reimbursements,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/Global-Reimbursements https://community.expensify.com/discussion/4452/how-to-merge-accounts,https://help.expensify.com/articles/expensify-classic/account-settings/Merge-Accounts#gsc.tab=0 https://community.expensify.com/discussion/4783/how-to-add-or-remove-a-copilot#latest,https://help.expensify.com/articles/expensify-classic/account-settings/Copilot#gsc.tab=0 https://community.expensify.com/discussion/4343/expensify-anz-partnership-announcement,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-ANZ From c1f8ca634217bc9d012b5a1a34dda9c1edf56dc8 Mon Sep 17 00:00:00 2001 From: Pavlo Tsimura Date: Wed, 31 Jan 2024 13:33:23 +0100 Subject: [PATCH 125/142] Fix policyID={false} warning --- src/pages/iou/SplitBillDetailsPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/SplitBillDetailsPage.js b/src/pages/iou/SplitBillDetailsPage.js index 94c2f1c31242..885d0487edcb 100644 --- a/src/pages/iou/SplitBillDetailsPage.js +++ b/src/pages/iou/SplitBillDetailsPage.js @@ -146,7 +146,7 @@ function SplitBillDetailsPage(props) { transaction={isEditingSplitBill ? props.draftTransaction || props.transaction : props.transaction} onConfirm={onConfirm} isPolicyExpenseChat={ReportUtils.isPolicyExpenseChat(props.report)} - policyID={ReportUtils.isPolicyExpenseChat(props.report) && props.report.policyID} + policyID={ReportUtils.isPolicyExpenseChat(props.report) ? props.report.policyID : null} /> )} From dd95e9b93d4594d25b1b38e0e164d94bd226a8b0 Mon Sep 17 00:00:00 2001 From: Github Date: Wed, 31 Jan 2024 13:39:50 +0100 Subject: [PATCH 126/142] Change navigation mock in perf tests for SignInPage --- tests/perf-test/SignInPage.perf-test.tsx | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/perf-test/SignInPage.perf-test.tsx b/tests/perf-test/SignInPage.perf-test.tsx index 80964c3c49cd..dde8596fb2ae 100644 --- a/tests/perf-test/SignInPage.perf-test.tsx +++ b/tests/perf-test/SignInPage.perf-test.tsx @@ -18,17 +18,6 @@ import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; -jest.mock('../../src/libs/Navigation/Navigation', () => { - const actualNav = jest.requireActual('../../src/libs/Navigation/Navigation'); - return { - ...actualNav, - navigationRef: { - addListener: () => jest.fn(), - removeListener: () => jest.fn(), - }, - } as typeof Navigation; -}); - const mockedNavigate = jest.fn(); jest.mock('@react-navigation/native', () => { const actualNav = jest.requireActual('@react-navigation/native'); @@ -43,7 +32,10 @@ jest.mock('@react-navigation/native', () => { navigate: jest.fn(), addListener: () => jest.fn(), }), - createNavigationContainerRef: jest.fn(), + createNavigationContainerRef: () => ({ + addListener: () => jest.fn(), + removeListener: () => jest.fn(), + }), } as typeof NativeNavigation; }); From 4241883500af7e601fae321df678af20323e513c Mon Sep 17 00:00:00 2001 From: Vit Horacek <36083550+mountiny@users.noreply.github.com> Date: Wed, 31 Jan 2024 14:41:46 +0000 Subject: [PATCH 127/142] Update src/libs/actions/IOU.js Co-authored-by: Alex Beaman --- src/libs/actions/IOU.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 43db82dd3c22..3a67706627b7 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -651,7 +651,7 @@ function buildOnyxDataForMoneyRequest( * @param {Object} [policy] * @param {Object} [policyTags] * @param {Object} [policyCategories] - * @param {Number} [moneyRequestReportID] - If user requests money from a composer on some money request report, we always add a request to that specific report. + * @param {Number} [moneyRequestReportID] - If user requests money via the report composer on some money request report, we always add a request to that specific report. * @returns {Object} data * @returns {String} data.payerEmail * @returns {Object} data.iouReport From 68070e8e3512ca50fe27fd5e6eb92757c8a02b04 Mon Sep 17 00:00:00 2001 From: Github Date: Wed, 31 Jan 2024 16:09:52 +0100 Subject: [PATCH 128/142] cr changes --- tests/perf-test/ReportScreen.perf-test.js | 2 +- tests/utils/collections/reportActions.ts | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/perf-test/ReportScreen.perf-test.js b/tests/perf-test/ReportScreen.perf-test.js index f5bc3f985227..dbc7775d066e 100644 --- a/tests/perf-test/ReportScreen.perf-test.js +++ b/tests/perf-test/ReportScreen.perf-test.js @@ -247,7 +247,7 @@ test('[ReportScreen] should press of the report item', () => { await act(triggerTransitionEnd); - // // Query for the report list + // Query for the report list await screen.findByTestId('report-actions-list'); const hintReportPreviewText = Localize.translateLocal('iou.viewDetails'); diff --git a/tests/utils/collections/reportActions.ts b/tests/utils/collections/reportActions.ts index 4efff8b3748a..bb14a2c7a41b 100644 --- a/tests/utils/collections/reportActions.ts +++ b/tests/utils/collections/reportActions.ts @@ -1,4 +1,5 @@ import {rand, randAggregation, randBoolean, randWord} from '@ngneat/falso'; +import {format} from 'date-fns'; import CONST from '@src/CONST'; import type {ReportAction} from '@src/types/onyx'; @@ -17,15 +18,11 @@ const flattenActionNamesValues = (actionNames: any) => { return result; }; -const addZero = (value: number): string | number => (value < 10 ? `0${value}` : value); - const getRandomDate = (): string => { const randomTimestamp = Math.random() * new Date().getTime(); const randomDate = new Date(randomTimestamp); - const formattedDate = `${randomDate.getFullYear()}-${addZero(randomDate.getMonth() + 1)}-${addZero(randomDate.getDate())} ${addZero(randomDate.getHours())}:${addZero( - randomDate.getMinutes(), - )}:${addZero(randomDate.getSeconds())}.${randomDate.getMilliseconds()}`; + const formattedDate = format(randomDate, CONST.DATE.FNS_DB_FORMAT_STRING); return formattedDate; }; From d336d58ef2a4a7f5ebfe2502b68415c06e06a694 Mon Sep 17 00:00:00 2001 From: Conor Pendergrast Date: Thu, 1 Feb 2024 04:22:06 +1300 Subject: [PATCH 129/142] Update docs/articles/new-expensify/account-settings/Preferences.md Co-authored-by: Amy Evans --- docs/articles/new-expensify/account-settings/Preferences.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/new-expensify/account-settings/Preferences.md b/docs/articles/new-expensify/account-settings/Preferences.md index 16d001d2b835..ae2efffc19b7 100644 --- a/docs/articles/new-expensify/account-settings/Preferences.md +++ b/docs/articles/new-expensify/account-settings/Preferences.md @@ -3,7 +3,7 @@ title: Preferences description: How to manage your Expensify Preferences --- # Overview -Your Preferences in Expensify allow you to customise how you use New Expensify. +Your Preferences in Expensify allow you to customize how you use New Expensify. - Set your theme preference From 9eb5967d2dc45823c69ab30f1f33e8d20fd8b77c Mon Sep 17 00:00:00 2001 From: Conor Pendergrast Date: Thu, 1 Feb 2024 04:23:15 +1300 Subject: [PATCH 130/142] Update Preferences.md --- docs/articles/new-expensify/account-settings/Preferences.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/articles/new-expensify/account-settings/Preferences.md b/docs/articles/new-expensify/account-settings/Preferences.md index ae2efffc19b7..5ec854e9bb81 100644 --- a/docs/articles/new-expensify/account-settings/Preferences.md +++ b/docs/articles/new-expensify/account-settings/Preferences.md @@ -14,8 +14,6 @@ To set or update your theme preference in New Expensify: 2. Tap on **Theme** 3. You can choose between the _Dark_ theme, the _Light_ theme, or _Use Device Settings_ (which is the default setting) -_Use Device Settings_ is the default setting. - Selecting _Use Device Settings_ will use your device's theme settings. For example, if your device is set to adjust the appearance from light to dark during the day, we'll match that. Your theme preference will sync across all your New Expensify apps (mobile, web, or OSX desktop apps). From c122efbb35301b737bdcdc6020f09a37ad34149b Mon Sep 17 00:00:00 2001 From: Conor Pendergrast Date: Thu, 1 Feb 2024 04:24:13 +1300 Subject: [PATCH 131/142] Update Preferences.md --- docs/articles/new-expensify/account-settings/Preferences.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/articles/new-expensify/account-settings/Preferences.md b/docs/articles/new-expensify/account-settings/Preferences.md index 5ec854e9bb81..b94c9d35c1a1 100644 --- a/docs/articles/new-expensify/account-settings/Preferences.md +++ b/docs/articles/new-expensify/account-settings/Preferences.md @@ -12,7 +12,9 @@ Your Preferences in Expensify allow you to customize how you use New Expensify. To set or update your theme preference in New Expensify: 1. Go to **Settings > Preferences** 2. Tap on **Theme** -3. You can choose between the _Dark_ theme, the _Light_ theme, or _Use Device Settings_ (which is the default setting) +3. You can choose between the _Dark_ theme, the _Light_ theme, or _Use Device Settings_ + +_Use Device Settings_ is the default setting. Selecting _Use Device Settings_ will use your device's theme settings. For example, if your device is set to adjust the appearance from light to dark during the day, we'll match that. From f14a7cac12167a2ec165f081a15e26a0eea3271e Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Wed, 31 Jan 2024 18:26:05 +0100 Subject: [PATCH 132/142] fix start date --- src/components/ReportActionItem/ChronosOOOListActions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/ChronosOOOListActions.tsx b/src/components/ReportActionItem/ChronosOOOListActions.tsx index 616404195c2f..52e0c52873d6 100644 --- a/src/components/ReportActionItem/ChronosOOOListActions.tsx +++ b/src/components/ReportActionItem/ChronosOOOListActions.tsx @@ -37,7 +37,7 @@ function ChronosOOOListActions({reportID, action}: ChronosOOOListActionsProps) { {events.map((event) => { - const start = DateUtils.getLocalDateFromDatetime(preferredLocale, event?.end?.date ?? ''); + const start = DateUtils.getLocalDateFromDatetime(preferredLocale, event?.start?.date ?? ''); const end = DateUtils.getLocalDateFromDatetime(preferredLocale, event?.end?.date ?? ''); return ( Date: Wed, 31 Jan 2024 17:30:29 -0300 Subject: [PATCH 133/142] Revert "fix: changed color on splash screen" --- assets/images/new-expensify.svg | 2 +- web/index.html | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/assets/images/new-expensify.svg b/assets/images/new-expensify.svg index 264821d4f86e..7bfef1fd38b4 100644 --- a/assets/images/new-expensify.svg +++ b/assets/images/new-expensify.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/web/index.html b/web/index.html index aff3feb87dbb..49ffd0d0a62f 100644 --- a/web/index.html +++ b/web/index.html @@ -111,10 +111,6 @@ transition-property: opacity; } - .splash-logo > svg { - color: #03d47c; - } - .animation { display: flex; } From ed95bc03c14e81d6ea78508b2d318ac5231d9b97 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Wed, 31 Jan 2024 21:05:04 +0000 Subject: [PATCH 134/142] Update version to 1.4.34-1 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index b67cb294e29b..c3cadff67f72 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,8 +98,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001043400 - versionName "1.4.34-0" + versionCode 1001043401 + versionName "1.4.34-1" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 364f33a02c30..55271b2cbb2f 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.34.0 + 1.4.34.1 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 6a90575d81fd..a9df427b80ff 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.34.0 + 1.4.34.1 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index e90a61461c2b..123b145af00a 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.34 CFBundleVersion - 1.4.34.0 + 1.4.34.1 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index e31aa2e80538..1ea7f01e0714 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.34-0", + "version": "1.4.34-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.34-0", + "version": "1.4.34-1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 92c906251fdb..c67b618f60fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.34-0", + "version": "1.4.34-1", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 488919c8914e4f4b862e718ea06cba8c48109b28 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 1 Feb 2024 03:54:41 +0530 Subject: [PATCH 135/142] fix style on mobile --- docs/_sass/_main.scss | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index cfdf4ff3a2bc..d1638a85edfd 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -458,6 +458,25 @@ button { opacity: 0.8; } } + + .info { + padding: 12px; + border-radius: 8px; + background-color: $color-highlightBG; + display: flex; + gap: 12px; + align-items: center; + + img { + height: 16px; + width: 16px; + } + + * { + padding: 0; + margin: 0; + } + } } } From 061d4a097ad692076018b0674472fba26c0c44e6 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 1 Feb 2024 03:54:59 +0530 Subject: [PATCH 136/142] rename to info --- docs/_includes/end-info.html | 2 ++ docs/_includes/info.html | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 docs/_includes/end-info.html create mode 100644 docs/_includes/info.html diff --git a/docs/_includes/end-info.html b/docs/_includes/end-info.html new file mode 100644 index 000000000000..50b9e11847ef --- /dev/null +++ b/docs/_includes/end-info.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/docs/_includes/info.html b/docs/_includes/info.html new file mode 100644 index 000000000000..c253f3cbc1de --- /dev/null +++ b/docs/_includes/info.html @@ -0,0 +1,3 @@ +

+ +
\ No newline at end of file From a21cfd6441cf121ba29db3bdbfad47b4dcb2661f Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 1 Feb 2024 03:55:22 +0530 Subject: [PATCH 137/142] add info img --- docs/assets/images/info.svg | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 docs/assets/images/info.svg diff --git a/docs/assets/images/info.svg b/docs/assets/images/info.svg new file mode 100644 index 000000000000..96924fbb6cf7 --- /dev/null +++ b/docs/assets/images/info.svg @@ -0,0 +1,9 @@ + + + + + + + + + From 995902a7fda139b95c60849a85a292f593623ddf Mon Sep 17 00:00:00 2001 From: OSBotify Date: Wed, 31 Jan 2024 22:38:50 +0000 Subject: [PATCH 138/142] Update version to 1.4.35-0 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 4 ++-- ios/NewExpensifyTests/Info.plist | 4 ++-- ios/NotificationServiceExtension/Info.plist | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index c3cadff67f72..4ea2098787f6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,8 +98,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001043401 - versionName "1.4.34-1" + versionCode 1001043500 + versionName "1.4.35-0" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 55271b2cbb2f..341edd0c9dfe 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.34 + 1.4.35 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.34.1 + 1.4.35.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index a9df427b80ff..6b39db8b2f27 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.34 + 1.4.35 CFBundleSignature ???? CFBundleVersion - 1.4.34.1 + 1.4.35.0 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 123b145af00a..ff9b56d72408 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 1.4.34 + 1.4.35 CFBundleVersion - 1.4.34.1 + 1.4.35.0 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 1ea7f01e0714..acadf891d8d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.34-1", + "version": "1.4.35-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.34-1", + "version": "1.4.35-0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index c67b618f60fd..bdbdb27dbf59 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.34-1", + "version": "1.4.35-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From e9ac5700fbb60cf8526d6400d0a91b680bf01ee2 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 1 Feb 2024 04:08:57 +0530 Subject: [PATCH 139/142] fix color --- docs/_sass/_main.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index d1638a85edfd..e05f7d4c08ea 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -463,6 +463,7 @@ button { padding: 12px; border-radius: 8px; background-color: $color-highlightBG; + color: $color-text; display: flex; gap: 12px; align-items: center; From 147d1a0168a328581f98ff81ba9c80eb0537ed43 Mon Sep 17 00:00:00 2001 From: Francois Laithier Date: Wed, 31 Jan 2024 15:35:40 -0800 Subject: [PATCH 140/142] Use new `lastActorDisplayName` to display alternate text for room invite actions in LHN --- src/libs/SidebarUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 9119907e9393..066f6fa65bd1 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -373,7 +373,7 @@ function getOptionData({ ? Localize.translate(preferredLocale, 'workspace.invite.invited') : Localize.translate(preferredLocale, 'workspace.invite.removed'); const users = Localize.translate(preferredLocale, targetAccountIDs.length > 1 ? 'workspace.invite.users' : 'workspace.invite.user'); - result.alternateText = `${verb} ${targetAccountIDs.length} ${users}`; + result.alternateText = `${lastActorDisplayName} ${verb} ${targetAccountIDs.length} ${users}`.trim(); const roomName = lastAction?.originalMessage?.roomName ?? ''; if (roomName) { From b9bd10b6880b1832ea146c542a999318c9b8dafc Mon Sep 17 00:00:00 2001 From: Vit Horacek <36083550+mountiny@users.noreply.github.com> Date: Thu, 1 Feb 2024 10:26:01 +0000 Subject: [PATCH 141/142] Revert "[NoQA] Enable and refactor ReportScreen perf tests" --- tests/perf-test/ReportScreen.perf-test.js | 58 ++++++++++++----------- tests/utils/ReportTestUtils.js | 9 +--- tests/utils/collections/reportActions.ts | 18 ++----- 3 files changed, 36 insertions(+), 49 deletions(-) diff --git a/tests/perf-test/ReportScreen.perf-test.js b/tests/perf-test/ReportScreen.perf-test.js index dbc7775d066e..5a144e715f5b 100644 --- a/tests/perf-test/ReportScreen.perf-test.js +++ b/tests/perf-test/ReportScreen.perf-test.js @@ -15,10 +15,7 @@ import * as Localize from '../../src/libs/Localize'; import ONYXKEYS from '../../src/ONYXKEYS'; import {ReportAttachmentsProvider} from '../../src/pages/home/report/ReportAttachmentsContext'; import ReportScreen from '../../src/pages/home/ReportScreen'; -import createCollection from '../utils/collections/createCollection'; -import createPersonalDetails from '../utils/collections/personalDetails'; -import createRandomPolicy from '../utils/collections/policies'; -import createRandomReport from '../utils/collections/reports'; +import * as LHNTestUtils from '../utils/LHNTestUtils'; import PusherHelper from '../utils/PusherHelper'; import * as ReportTestUtils from '../utils/ReportTestUtils'; import * as TestHelper from '../utils/TestHelper'; @@ -59,7 +56,6 @@ jest.mock('../../src/hooks/useEnvironment', () => jest.mock('../../src/libs/Permissions', () => ({ canUseLinkPreviews: jest.fn(() => true), - canUseDefaultRooms: jest.fn(() => true), })); jest.mock('../../src/hooks/usePermissions.ts'); @@ -107,18 +103,6 @@ afterEach(() => { PusherHelper.teardown(); }); -const policies = createCollection( - (item) => `${ONYXKEYS.COLLECTION.POLICY}${item.id}`, - (index) => createRandomPolicy(index), - 10, -); - -const personalDetails = createCollection( - (item) => item.accountID, - (index) => createPersonalDetails(index), - 20, -); - /** * This is a helper function to create a mock for the addListener function of the react-navigation library. * The reason we need this is because we need to trigger the transitionEnd event in our tests to simulate @@ -168,11 +152,7 @@ function ReportScreenWrapper(args) { ); } -const report = {...createRandomReport(1), policyID: '1'}; -const reportActions = ReportTestUtils.getMockedReportActionsMap(500); -const mockRoute = {params: {reportID: '1'}}; - -test('[ReportScreen] should render ReportScreen with composer interactions', () => { +test.skip('[ReportScreen] should render ReportScreen with composer interactions', () => { const {triggerTransitionEnd, addListener} = createAddListenerMock(); const scenario = async () => { /** @@ -186,6 +166,9 @@ test('[ReportScreen] should render ReportScreen with composer interactions', () await act(triggerTransitionEnd); + // Query for the report list + await screen.findByTestId('report-actions-list'); + // Query for the composer const composer = await screen.findByTestId('composer'); @@ -206,6 +189,15 @@ test('[ReportScreen] should render ReportScreen with composer interactions', () await screen.findByLabelText(hintHeaderText); }; + const policy = { + policyID: 1, + name: 'Testing Policy', + }; + + const report = LHNTestUtils.getFakeReport(); + const reportActions = ReportTestUtils.getMockedReportActionsMap(1000); + const mockRoute = {params: {reportID: '1'}}; + const navigation = {addListener}; return waitForBatchedUpdates() @@ -214,9 +206,9 @@ test('[ReportScreen] should render ReportScreen with composer interactions', () [ONYXKEYS.IS_SIDEBAR_LOADED]: true, [`${ONYXKEYS.COLLECTION.REPORT}${mockRoute.params.reportID}`]: report, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${mockRoute.params.reportID}`]: reportActions, - [ONYXKEYS.PERSONAL_DETAILS_LIST]: personalDetails, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS], - [`${ONYXKEYS.COLLECTION.POLICY}`]: policies, + [`${ONYXKEYS.COLLECTION.POLICY}${policy.policyID}`]: policy, [`${ONYXKEYS.COLLECTION.REPORT_METADATA}${mockRoute.params.reportID}`]: { isLoadingReportActions: false, }, @@ -233,7 +225,7 @@ test('[ReportScreen] should render ReportScreen with composer interactions', () ); }); -test('[ReportScreen] should press of the report item', () => { +test.skip('[ReportScreen] should press of the report item', () => { const {triggerTransitionEnd, addListener} = createAddListenerMock(); const scenario = async () => { /** @@ -250,6 +242,9 @@ test('[ReportScreen] should press of the report item', () => { // Query for the report list await screen.findByTestId('report-actions-list'); + // Query for the composer + await screen.findByTestId('composer'); + const hintReportPreviewText = Localize.translateLocal('iou.viewDetails'); // Query for report preview buttons @@ -259,6 +254,15 @@ test('[ReportScreen] should press of the report item', () => { fireEvent.press(reportPreviewButtons[0]); }; + const policy = { + policyID: 123, + name: 'Testing Policy', + }; + + const report = LHNTestUtils.getFakeReport(); + const reportActions = ReportTestUtils.getMockedReportActionsMap(1000); + const mockRoute = {params: {reportID: '2'}}; + const navigation = {addListener}; return waitForBatchedUpdates() @@ -267,9 +271,9 @@ test('[ReportScreen] should press of the report item', () => { [ONYXKEYS.IS_SIDEBAR_LOADED]: true, [`${ONYXKEYS.COLLECTION.REPORT}${mockRoute.params.reportID}`]: report, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${mockRoute.params.reportID}`]: reportActions, - [ONYXKEYS.PERSONAL_DETAILS_LIST]: personalDetails, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS], - [`${ONYXKEYS.COLLECTION.POLICY}`]: policies, + [`${ONYXKEYS.COLLECTION.POLICY}${policy.policyID}`]: policy, [`${ONYXKEYS.COLLECTION.REPORT_METADATA}${mockRoute.params.reportID}`]: { isLoadingReportActions: false, }, diff --git a/tests/utils/ReportTestUtils.js b/tests/utils/ReportTestUtils.js index 86899e4045f6..910f2200876b 100644 --- a/tests/utils/ReportTestUtils.js +++ b/tests/utils/ReportTestUtils.js @@ -1,5 +1,4 @@ import _ from 'underscore'; -import createRandomReportAction from './collections/reportActions'; const actionNames = ['ADDCOMMENT', 'IOU', 'REPORTPREVIEW', 'CLOSED']; @@ -52,13 +51,7 @@ const getMockedReportActionsMap = (length = 100) => { const mockReports = Array.from({length}, (__, i) => { const reportID = i + 1; const actionName = i === 0 ? 'CREATED' : actionNames[i % actionNames.length]; - const reportAction = { - ...createRandomReportAction(reportID), - actionName, - originalMessage: { - linkedReportID: reportID.toString(), - }, - }; + const reportAction = getFakeReportAction(reportID, actionName); return {[reportID]: reportAction}; }); diff --git a/tests/utils/collections/reportActions.ts b/tests/utils/collections/reportActions.ts index bb14a2c7a41b..cc258e89c041 100644 --- a/tests/utils/collections/reportActions.ts +++ b/tests/utils/collections/reportActions.ts @@ -1,5 +1,4 @@ -import {rand, randAggregation, randBoolean, randWord} from '@ngneat/falso'; -import {format} from 'date-fns'; +import {rand, randAggregation, randBoolean, randPastDate, randWord} from '@ngneat/falso'; import CONST from '@src/CONST'; import type {ReportAction} from '@src/types/onyx'; @@ -18,15 +17,6 @@ const flattenActionNamesValues = (actionNames: any) => { return result; }; -const getRandomDate = (): string => { - const randomTimestamp = Math.random() * new Date().getTime(); - const randomDate = new Date(randomTimestamp); - - const formattedDate = format(randomDate, CONST.DATE.FNS_DB_FORMAT_STRING); - - return formattedDate; -}; - export default function createRandomReportAction(index: number): ReportAction { return { // we need to add any here because of the way we are generating random values @@ -42,7 +32,7 @@ export default function createRandomReportAction(index: number): ReportAction { text: randWord(), }, ], - created: getRandomDate(), + created: randPastDate().toISOString(), message: [ { type: randWord(), @@ -67,13 +57,13 @@ export default function createRandomReportAction(index: number): ReportAction { ], originalMessage: { html: randWord(), - lastModified: getRandomDate(), + type: rand(Object.values(CONST.IOU.REPORT_ACTION_TYPE)), }, whisperedToAccountIDs: randAggregation(), avatar: randWord(), automatic: randBoolean(), shouldShow: randBoolean(), - lastModified: getRandomDate(), + lastModified: randPastDate().toISOString(), pendingAction: rand(Object.values(CONST.RED_BRICK_ROAD_PENDING_ACTION)), delegateAccountID: index, errors: {}, From 26ae9a15dcfda7464295efb7c194d4b05231ad55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Thu, 1 Feb 2024 13:19:52 +0100 Subject: [PATCH 142/142] fix writing inputs to env file --- .../composite/buildAndroidE2EAPK/action.yml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/actions/composite/buildAndroidE2EAPK/action.yml b/.github/actions/composite/buildAndroidE2EAPK/action.yml index 0c5f70929c27..146ddb3a1a66 100644 --- a/.github/actions/composite/buildAndroidE2EAPK/action.yml +++ b/.github/actions/composite/buildAndroidE2EAPK/action.yml @@ -58,21 +58,15 @@ runs: - name: Append environment variables to env file shell: bash run: | - echo "EXPENSIFY_PARTNER_NAME=${EXPENSIFY_PARTNER_NAME}" >> ${{ inputs.PATH_ENV_FILE }} - echo "EXPENSIFY_PARTNER_PASSWORD=${EXPENSIFY_PARTNER_PASSWORD}" >> ${{ inputs.PATH_ENV_FILE }} - echo "EXPENSIFY_PARTNER_USER_ID=${EXPENSIFY_PARTNER_USER_ID}" >> ${{ inputs.PATH_ENV_FILE }} - echo "EXPENSIFY_PARTNER_USER_SECRET=${EXPENSIFY_PARTNER_USER_SECRET}" >> ${{ inputs.PATH_ENV_FILE }} - echo "EXPENSIFY_PARTNER_PASSWORD_EMAIL=${EXPENSIFY_PARTNER_PASSWORD_EMAIL}" >> ${{ inputs.PATH_ENV_FILE }} + echo "EXPENSIFY_PARTNER_NAME=${{ inputs.EXPENSIFY_PARTNER_NAME }}" >> ${{ inputs.PATH_ENV_FILE }} + echo "EXPENSIFY_PARTNER_PASSWORD=${{ inputs.EXPENSIFY_PARTNER_PASSWORD }}" >> ${{ inputs.PATH_ENV_FILE }} + echo "EXPENSIFY_PARTNER_USER_ID=${{ inputs.EXPENSIFY_PARTNER_USER_ID }}" >> ${{ inputs.PATH_ENV_FILE }} + echo "EXPENSIFY_PARTNER_USER_SECRET=${{ inputs.EXPENSIFY_PARTNER_USER_SECRET }}" >> ${{ inputs.PATH_ENV_FILE }} + echo "EXPENSIFY_PARTNER_PASSWORD_EMAIL=${{ inputs.EXPENSIFY_PARTNER_PASSWORD_EMAIL }}" >> ${{ inputs.PATH_ENV_FILE }} - name: Build APK run: npm run ${{ inputs.PACKAGE_SCRIPT_NAME }} shell: bash - env: - EXPENSIFY_PARTNER_NAME: ${{ inputs.EXPENSIFY_PARTNER_NAME }} - EXPENSIFY_PARTNER_PASSWORD: ${{ inputs.EXPENSIFY_PARTNER_PASSWORD }} - EXPENSIFY_PARTNER_USER_ID: ${{ inputs.EXPENSIFY_PARTNER_USER_ID }} - EXPENSIFY_PARTNER_USER_SECRET: ${{ inputs.EXPENSIFY_PARTNER_USER_SECRET }} - EXPENSIFY_PARTNER_PASSWORD_EMAIL: ${{ inputs.EXPENSIFY_PARTNER_PASSWORD_EMAIL }} - name: Upload APK uses: actions/upload-artifact@65d862660abb392b8c4a3d1195a2108db131dd05