From a56bce1cd7715499377d77dc02ad26755f314fd4 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 15 Jan 2024 12:11:19 +0100 Subject: [PATCH 01/15] Migrate MoneyRequestAction to TypeScript --- ...equestAction.js => MoneyRequestAction.tsx} | 129 ++++++++---------- .../ContextMenu/ReportActionContextMenu.ts | 7 +- 2 files changed, 59 insertions(+), 77 deletions(-) rename src/components/ReportActionItem/{MoneyRequestAction.js => MoneyRequestAction.tsx} (58%) diff --git a/src/components/ReportActionItem/MoneyRequestAction.js b/src/components/ReportActionItem/MoneyRequestAction.tsx similarity index 58% rename from src/components/ReportActionItem/MoneyRequestAction.js rename to src/components/ReportActionItem/MoneyRequestAction.tsx index e0a3152a41b4..e0c44dfc65b9 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.js +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -1,80 +1,63 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import networkPropTypes from '@components/networkPropTypes'; -import {withNetwork} from '@components/OnyxProvider'; -import refPropTypes from '@components/refPropTypes'; +import type {OnyxEntry} from 'react-native-onyx/lib/types'; import RenderHTML from '@components/RenderHTML'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; -import iouReportPropTypes from '@pages/iouReportPropTypes'; -import reportPropTypes from '@pages/reportPropTypes'; +import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type * as OnyxTypes from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import MoneyRequestPreview from './MoneyRequestPreview'; -const propTypes = { +type MoneyRequestActionOnyxProps = { + /** Chat report associated with iouReport */ + chatReport: OnyxEntry; + + /** IOU report data object */ + iouReport: OnyxEntry; + + /** Report actions for this report */ + reportActions: OnyxEntry; +}; + +type MoneyRequestActionProps = MoneyRequestActionOnyxProps & { /** All the data of the action */ - action: PropTypes.shape(reportActionPropTypes).isRequired, + action: OnyxTypes.ReportAction; /** The ID of the associated chatReport */ - chatReportID: PropTypes.string.isRequired, + chatReportID: string; /** The ID of the associated request report */ - requestReportID: PropTypes.string.isRequired, + requestReportID: string; /** Is this IOUACTION the most recent? */ - isMostRecentIOUReportAction: PropTypes.bool.isRequired, + isMostRecentIOUReportAction: boolean; /** Popover context menu anchor, used for showing context menu */ - contextMenuAnchor: refPropTypes, + contextMenuAnchor?: ContextMenuAnchor; /** Callback for updating context menu active state, used for showing context menu */ - checkIfContextMenuActive: PropTypes.func, - - /* Onyx Props */ - /** chatReport associated with iouReport */ - chatReport: reportPropTypes, - - /** IOU report data object */ - iouReport: iouReportPropTypes, - - /** Array of report actions for this report */ - reportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), + checkIfContextMenuActive?: () => void; /** Whether the IOU is hovered so we can modify its style */ - isHovered: PropTypes.bool, - - network: networkPropTypes.isRequired, + isHovered?: boolean; /** Whether a message is a whisper */ - isWhisper: PropTypes.bool, + isWhisper?: boolean; /** Styles to be assigned to Container */ - // eslint-disable-next-line react/forbid-prop-types - style: PropTypes.arrayOf(PropTypes.object), -}; - -const defaultProps = { - contextMenuAnchor: undefined, - checkIfContextMenuActive: () => {}, - chatReport: {}, - iouReport: {}, - reportActions: {}, - isHovered: false, - style: [], - isWhisper: false, + style?: StyleProp; }; function MoneyRequestAction({ @@ -83,31 +66,32 @@ function MoneyRequestAction({ requestReportID, isMostRecentIOUReportAction, contextMenuAnchor, - checkIfContextMenuActive, + checkIfContextMenuActive = () => {}, chatReport, iouReport, reportActions, - isHovered, - network, + isHovered = false, style, - isWhisper, -}) { + isWhisper = false, +}: MoneyRequestActionProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const isSplitBillAction = lodashGet(action, 'originalMessage.type', '') === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; + const {isOffline} = useNetwork(); + + const isSplitBillAction = action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && action.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; const onMoneyRequestPreviewPressed = () => { if (isSplitBillAction) { - const reportActionID = lodashGet(action, 'reportActionID', '0'); + const reportActionID = action.reportActionID ?? '0'; Navigation.navigate(ROUTES.SPLIT_BILL_DETAILS.getRoute(chatReportID, reportActionID)); return; } // If the childReportID is not present, we need to create a new thread - const childReportID = lodashGet(action, 'childReportID', 0); + const childReportID = action?.childReportID ?? '0'; if (!childReportID) { const thread = ReportUtils.buildTransactionThread(action, requestReportID); - const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread.participantAccountIDs); + const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread.participantAccountIDs ?? []); Report.openReport(thread.reportID, userLogins, thread, action.reportActionID); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(thread.reportID)); return; @@ -120,12 +104,12 @@ function MoneyRequestAction({ const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(action); const isReversedTransaction = ReportActionsUtils.isReversedTransaction(action); if ( - !_.isEmpty(iouReport) && - !_.isEmpty(reportActions) && - chatReport.iouReportID && + !isEmptyObject(iouReport) && + !isEmptyObject(reportActions) && + chatReport?.iouReportID && isMostRecentIOUReportAction && action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD && - network.isOffline + isOffline ) { shouldShowPendingConversionMessage = IOUUtils.isIOUReportPendingCurrencyConversion(iouReport); } @@ -142,29 +126,24 @@ function MoneyRequestAction({ checkIfContextMenuActive={checkIfContextMenuActive} shouldShowPendingConversionMessage={shouldShowPendingConversionMessage} onPreviewPressed={onMoneyRequestPreviewPressed} - containerStyles={[styles.cursorPointer, isHovered ? styles.reportPreviewBoxHoverBorder : undefined, ...style]} + containerStyles={[styles.cursorPointer, isHovered ? styles.reportPreviewBoxHoverBorder : undefined, style]} isHovered={isHovered} isWhisper={isWhisper} /> ); } -MoneyRequestAction.propTypes = propTypes; -MoneyRequestAction.defaultProps = defaultProps; MoneyRequestAction.displayName = 'MoneyRequestAction'; -export default compose( - withOnyx({ - chatReport: { - key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, - }, - iouReport: { - key: ({requestReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${requestReportID}`, - }, - reportActions: { - key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, - canEvict: false, - }, - }), - withNetwork(), -)(MoneyRequestAction); +export default withOnyx({ + chatReport: { + key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, + }, + iouReport: { + key: ({requestReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${requestReportID}`, + }, + reportActions: { + key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, + canEvict: false, + }, +})(MoneyRequestAction); diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts index 5b64d90da5da..9553d8207a2f 100644 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts @@ -14,11 +14,13 @@ type OnCancel = () => void; type ContextMenuType = ValueOf; +type ContextMenuAnchor = View | RNText | null; + type ShowContextMenu = ( type: ContextMenuType, event: GestureResponderEvent | MouseEvent, selection: string, - contextMenuAnchor: View | RNText | null, + contextMenuAnchor: ContextMenuAnchor, reportID?: string, reportActionID?: string, originalReportID?: string, @@ -96,7 +98,7 @@ function showContextMenu( type: ContextMenuType, event: GestureResponderEvent | MouseEvent, selection: string, - contextMenuAnchor: View | RNText | null, + contextMenuAnchor: ContextMenuAnchor, reportID = '0', reportActionID = '0', originalReportID = '0', @@ -175,3 +177,4 @@ function clearActiveReportAction() { } export {contextMenuRef, showContextMenu, hideContextMenu, isActiveReportAction, clearActiveReportAction, showDeleteModal, hideDeleteModal}; +export type {ContextMenuAnchor}; From 0f627bab72b127aeca7969b2b8633f49a064e38d Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 16 Jan 2024 09:36:03 +0100 Subject: [PATCH 02/15] Migrate MoneyRequestPreview to TypeScript --- .../Pressable/GenericPressable/types.ts | 2 +- .../Pressable/PressableWithDelayToggle.tsx | 2 +- .../Pressable/PressableWithoutFocus.tsx | 2 +- ...uestPreview.js => MoneyRequestPreview.tsx} | 252 +++++++++--------- src/languages/types.ts | 2 +- src/libs/ReceiptUtils.ts | 3 +- src/libs/TransactionUtils.ts | 14 +- 7 files changed, 132 insertions(+), 145 deletions(-) rename src/components/ReportActionItem/{MoneyRequestPreview.js => MoneyRequestPreview.tsx} (63%) diff --git a/src/components/Pressable/GenericPressable/types.ts b/src/components/Pressable/GenericPressable/types.ts index dc04b6fcf329..03cdb30c67fc 100644 --- a/src/components/Pressable/GenericPressable/types.ts +++ b/src/components/Pressable/GenericPressable/types.ts @@ -40,7 +40,7 @@ type PressableProps = RNPressableProps & /** * onPress callback */ - onPress: (event?: GestureResponderEvent | KeyboardEvent) => void | Promise; + onPress: ((event?: GestureResponderEvent | KeyboardEvent) => void | Promise) | undefined; /** * Specifies keyboard shortcut to trigger onPressHandler diff --git a/src/components/Pressable/PressableWithDelayToggle.tsx b/src/components/Pressable/PressableWithDelayToggle.tsx index ab1fa95efeb5..86f6c9d8aff8 100644 --- a/src/components/Pressable/PressableWithDelayToggle.tsx +++ b/src/components/Pressable/PressableWithDelayToggle.tsx @@ -78,7 +78,7 @@ function PressableWithDelayToggle( return; } temporarilyDisableInteractions(); - onPress(); + onPress?.(); }; // Due to limitations in RN regarding the vertical text alignment of non-Text elements, diff --git a/src/components/Pressable/PressableWithoutFocus.tsx b/src/components/Pressable/PressableWithoutFocus.tsx index f887b0ea9b7d..240ef4a9873a 100644 --- a/src/components/Pressable/PressableWithoutFocus.tsx +++ b/src/components/Pressable/PressableWithoutFocus.tsx @@ -15,7 +15,7 @@ function PressableWithoutFocus({children, onPress, onLongPress, ...rest}: Pressa const pressAndBlur = () => { ref?.current?.blur(); - onPress(); + onPress?.(); }; return ( diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.tsx similarity index 63% rename from src/components/ReportActionItem/MoneyRequestPreview.js rename to src/components/ReportActionItem/MoneyRequestPreview.tsx index 96c9e1b364d6..a6df98973670 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.tsx @@ -1,20 +1,18 @@ import {truncate} from 'lodash'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; +import lodashSortBy from 'lodash/sortBy'; import React from 'react'; import {View} from 'react-native'; +import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {OnyxEntry} from 'react-native-onyx/lib/types'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import MoneyRequestSkeletonView from '@components/MoneyRequestSkeletonView'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithFeedback from '@components/Pressable/PressableWithoutFeedback'; -import refPropTypes from '@components/refPropTypes'; import {showContextMenuForReport} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; -import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; @@ -29,144 +27,135 @@ import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; -import walletTermsPropTypes from '@pages/EnablePayments/walletTermsPropTypes'; -import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; -import iouReportPropTypes from '@pages/iouReportPropTypes'; -import reportPropTypes from '@pages/reportPropTypes'; +import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import * as PaymentMethods from '@userActions/PaymentMethods'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import * as Localize from '@src/libs/Localize'; import ONYXKEYS from '@src/ONYXKEYS'; +import type * as OnyxTypes from '@src/types/onyx'; +import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import ReportActionItemImages from './ReportActionItemImages'; -const propTypes = { +type MoneyRequestPreviewOnyxProps = { + /** All of the personal details for everyone */ + personalDetails: OnyxEntry; + + /** Chat report associated with iouReport */ + chatReport: OnyxEntry; + + /** IOU report data object */ + iouReport: OnyxEntry; + + /** Session info for the currently logged in user. */ + session: OnyxEntry; + + /** The transaction attached to the action.message.iouTransactionID */ + transaction: OnyxEntry; + + /** Information about the user accepting the terms for payments */ + walletTerms: OnyxEntry; +}; + +type MoneyRequestPreviewProps = MoneyRequestPreviewOnyxProps & { /** The active IOUReport, used for Onyx subscription */ + // The iouReportID is used inside withOnyx HOC // eslint-disable-next-line react/no-unused-prop-types - iouReportID: PropTypes.string.isRequired, + iouReportID: string; /** The associated chatReport */ - chatReportID: PropTypes.string.isRequired, + chatReportID: string; /** Callback for the preview pressed */ - onPreviewPressed: PropTypes.func, + onPreviewPressed: (event?: GestureResponderEvent | KeyboardEvent) => void; /** All the data of the action, used for showing context menu */ - action: PropTypes.shape(reportActionPropTypes), + action: OnyxTypes.ReportAction; /** Popover context menu anchor, used for showing context menu */ - contextMenuAnchor: refPropTypes, + contextMenuAnchor?: ContextMenuAnchor; /** Callback for updating context menu active state, used for showing context menu */ - checkIfContextMenuActive: PropTypes.func, + checkIfContextMenuActive?: () => void; /** Extra styles to pass to View wrapper */ - // eslint-disable-next-line react/forbid-prop-types - containerStyles: PropTypes.arrayOf(PropTypes.object), - - /* Onyx Props */ - - /** chatReport associated with iouReport */ - chatReport: reportPropTypes, - - /** IOU report data object */ - iouReport: iouReportPropTypes, + containerStyles: StyleProp; /** True if this is this IOU is a split instead of a 1:1 request */ - isBillSplit: PropTypes.bool.isRequired, + isBillSplit: boolean; /** True if the IOU Preview card is hovered */ - isHovered: PropTypes.bool, - - /** All of the personal details for everyone */ - personalDetails: PropTypes.objectOf( - PropTypes.shape({ - /** This is either the user's full name, or their login if full name is an empty string */ - displayName: PropTypes.string, - }), - ), - - /** The transaction attached to the action.message.iouTransactionID */ - transaction: transactionPropTypes, - - /** Session info for the currently logged in user. */ - session: PropTypes.shape({ - /** Currently logged in user email */ - email: PropTypes.string, - }), - - /** Information about the user accepting the terms for payments */ - walletTerms: walletTermsPropTypes, + isHovered?: boolean; /** Whether or not an IOU report contains money requests in a different currency * that are either created or cancelled offline, and thus haven't been converted to the report's currency yet */ - shouldShowPendingConversionMessage: PropTypes.bool, + shouldShowPendingConversionMessage?: boolean; /** Whether a message is a whisper */ - isWhisper: PropTypes.bool, + isWhisper?: boolean; }; -const defaultProps = { - iouReport: {}, - onPreviewPressed: null, - action: undefined, - contextMenuAnchor: undefined, - checkIfContextMenuActive: () => {}, - containerStyles: [], - walletTerms: {}, - chatReport: {}, - isHovered: false, - personalDetails: {}, - session: { - email: null, - }, - transaction: {}, - shouldShowPendingConversionMessage: false, - isWhisper: false, -}; - -function MoneyRequestPreview(props) { +function MoneyRequestPreview({ + iouReport, + isBillSplit, + session, + action, + personalDetails, + chatReport, + transaction, + contextMenuAnchor, + chatReportID, + onPreviewPressed, + containerStyles, + walletTerms, + checkIfContextMenuActive = () => {}, + shouldShowPendingConversionMessage = false, + isHovered = false, + isWhisper = false, +}: MoneyRequestPreviewProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const {isSmallScreenWidth, windowWidth} = useWindowDimensions(); - if (_.isEmpty(props.iouReport) && !props.isBillSplit) { + if (isEmptyObject(iouReport) && !isBillSplit) { return null; } - const sessionAccountID = lodashGet(props.session, 'accountID', null); - const managerID = props.iouReport.managerID || ''; - const ownerAccountID = props.iouReport.ownerAccountID || ''; - const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(props.chatReport); - - const participantAccountIDs = props.isBillSplit ? lodashGet(props.action, 'originalMessage.participantAccountIDs', []) : [managerID, ownerAccountID]; - const participantAvatars = OptionsListUtils.getAvatarsForAccountIDs(participantAccountIDs, props.personalDetails); - const sortedParticipantAvatars = _.sortBy(participantAvatars, (avatar) => avatar.id); - if (isPolicyExpenseChat && props.isBillSplit) { - sortedParticipantAvatars.push(ReportUtils.getWorkspaceIcon(props.chatReport)); + const sessionAccountID = session?.accountID; + const managerID = iouReport?.managerID ?? -1; + const ownerAccountID = iouReport?.ownerAccountID ?? -1; + const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport); + + const participantAccountIDs = action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && isBillSplit ? action.originalMessage.participantAccountIDs ?? [] : [managerID, ownerAccountID]; + // TODO: Remove the assertion once OptionsListUtils (https://github.com/Expensify/App/issues/24921) is migrated to TypeScript. + const participantAvatars = OptionsListUtils.getAvatarsForAccountIDs(participantAccountIDs, personalDetails ?? {}) as OnyxCommon.Icon[]; + const sortedParticipantAvatars = lodashSortBy(participantAvatars, (avatar) => avatar.id); + if (isPolicyExpenseChat && isBillSplit) { + sortedParticipantAvatars.push(ReportUtils.getWorkspaceIcon(chatReport)); } // Pay button should only be visible to the manager of the report. const isCurrentUserManager = managerID === sessionAccountID; - const {amount: requestAmount, currency: requestCurrency, comment: requestComment, merchant} = ReportUtils.getTransactionDetails(props.transaction); + const {amount: requestAmount, currency: requestCurrency, comment: requestComment, merchant} = ReportUtils.getTransactionDetails(transaction) ?? {}; const description = truncate(requestComment, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); - const hasReceipt = TransactionUtils.hasReceipt(props.transaction); - const isScanning = hasReceipt && TransactionUtils.isReceiptBeingScanned(props.transaction); - const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(props.transaction); - const isDistanceRequest = TransactionUtils.isDistanceRequest(props.transaction); - const isExpensifyCardTransaction = TransactionUtils.isExpensifyCardTransaction(props.transaction); - const isSettled = ReportUtils.isSettled(props.iouReport.reportID); - const isDeleted = lodashGet(props.action, 'pendingAction', null) === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; + const hasReceipt = TransactionUtils.hasReceipt(transaction); + const isScanning = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction); + const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction); + const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); + const isExpensifyCardTransaction = TransactionUtils.isExpensifyCardTransaction(transaction); + const isSettled = ReportUtils.isSettled(iouReport?.reportID); + const isDeleted = action?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; // Show the merchant for IOUs and expenses only if they are custom or not related to scanning smartscan - const shouldShowMerchant = !_.isEmpty(requestMerchant) && requestMerchant !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT && requestMerchant !== CONST.TRANSACTION.DEFAULT_MERCHANT; - const shouldShowDescription = !_.isEmpty(description) && !shouldShowMerchant && !isScanning; - const hasPendingWaypoints = lodashGet(props.transaction, 'pendingFields.waypoints', null); + const shouldShowMerchant = !!requestMerchant && requestMerchant !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT && requestMerchant !== CONST.TRANSACTION.DEFAULT_MERCHANT; + const shouldShowDescription = !!description && !shouldShowMerchant && !isScanning; + const hasPendingWaypoints = transaction?.pendingFields?.waypoints; let merchantOrDescription = requestMerchant; if (!shouldShowMerchant) { @@ -175,20 +164,21 @@ function MoneyRequestPreview(props) { merchantOrDescription = requestMerchant.replace(CONST.REGEX.FIRST_SPACE, translate('common.tbd')); } - const receiptImages = hasReceipt ? [ReceiptUtils.getThumbnailAndImageURIs(props.transaction)] : []; + const receiptImages = hasReceipt ? [ReceiptUtils.getThumbnailAndImageURIs(transaction)] : []; - const getSettledMessage = () => { + const getSettledMessage = (): string => { if (isExpensifyCardTransaction) { return translate('common.done'); } return translate('iou.settledExpensify'); }; - const showContextMenu = (event) => { - showContextMenuForReport(event, props.contextMenuAnchor, props.chatReportID, props.action, props.checkIfContextMenuActive); + const showContextMenu = (event: GestureResponderEvent) => { + // @ts-expect-error TODO: Remove this once ShowContextMenuContext (https://github.com/Expensify/App/issues/24980) is migrated to TypeScript. + showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive); }; - const getPreviewHeaderText = () => { + const getPreviewHeaderText = (): string => { if (isDistanceRequest) { return translate('common.distance'); } @@ -197,30 +187,30 @@ function MoneyRequestPreview(props) { return translate('common.receipt'); } - if (props.isBillSplit) { + if (isBillSplit) { return translate('iou.split'); } if (isExpensifyCardTransaction) { let message = translate('iou.card'); - if (TransactionUtils.isPending(props.transaction)) { + if (TransactionUtils.isPending(transaction)) { message += ` • ${translate('iou.pending')}`; } return message; } let message = translate('iou.cash'); - if (ReportUtils.isPaidGroupPolicyExpenseReport(props.iouReport) && ReportUtils.isReportApproved(props.iouReport) && !ReportUtils.isSettled(props.iouReport)) { + if (ReportUtils.isPaidGroupPolicyExpenseReport(iouReport) && ReportUtils.isReportApproved(iouReport) && !ReportUtils.isSettled(iouReport?.reportID)) { message += ` • ${translate('iou.approved')}`; - } else if (props.iouReport.isWaitingOnBankAccount) { + } else if (iouReport?.isWaitingOnBankAccount) { message += ` • ${translate('iou.pending')}`; - } else if (props.iouReport.isCancelledIOU) { + } else if (iouReport?.isCancelledIOU) { message += ` • ${translate('iou.canceled')}`; } return message; }; - const getDisplayAmountText = () => { + const getDisplayAmountText = (): string => { if (isDistanceRequest) { return requestAmount && !hasPendingWaypoints ? CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency) : translate('common.tbd'); } @@ -229,18 +219,18 @@ function MoneyRequestPreview(props) { return translate('iou.receiptScanning'); } - if (TransactionUtils.hasMissingSmartscanFields(props.transaction)) { + if (TransactionUtils.hasMissingSmartscanFields(transaction)) { return Localize.translateLocal('iou.receiptMissingDetails'); } return CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency); }; - const getDisplayDeleteAmountText = () => { - const {amount, currency} = ReportUtils.getTransactionDetails(props.action.originalMessage); + const getDisplayDeleteAmountText = (): string => { + const {amount, currency} = ReportUtils.getTransactionDetails(action.originalMessage as OnyxTypes.Transaction) ?? {}; if (isDistanceRequest) { - return CurrencyUtils.convertToDisplayString(TransactionUtils.getAmount(props.action.originalMessage), currency); + return CurrencyUtils.convertToDisplayString(TransactionUtils.getAmount(action.originalMessage as OnyxTypes.Transaction), currency); } return CurrencyUtils.convertToDisplayString(amount, currency); @@ -251,36 +241,35 @@ function MoneyRequestPreview(props) { const childContainer = ( { PaymentMethods.clearWalletTermsError(); - Report.clearIOUError(props.chatReportID); + Report.clearIOUError(chatReportID); }} errorRowStyles={[styles.mbn1]} needsOffscreenAlphaCompositing > {hasReceipt && ( )} - {_.isEmpty(props.transaction) && - !ReportActionsUtils.isMessageDeleted(props.action) && - lodashGet(props.action, 'pendingAction') !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? ( + {isEmptyObject(transaction) && !ReportActionsUtils.isMessageDeleted(action) && action.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? ( ) : ( - {getPreviewHeaderText() + (isSettled && !props.iouReport.isCancelledIOU ? ` • ${getSettledMessage()}` : '')} + {getPreviewHeaderText() + (isSettled && !iouReport?.isCancelledIOU ? ` • ${getSettledMessage()}` : '')} {!isSettled && hasFieldErrors && ( {displayAmount} - {ReportUtils.isSettled(props.iouReport.reportID) && !props.isBillSplit && ( + {ReportUtils.isSettled(iouReport?.reportID) && !isBillSplit && ( )} - {props.isBillSplit && ( + {isBillSplit && ( @@ -325,16 +313,16 @@ function MoneyRequestPreview(props) { - {!isCurrentUserManager && props.shouldShowPendingConversionMessage && ( + {!isCurrentUserManager && shouldShowPendingConversionMessage && ( {translate('iou.pendingConversionMessage')} )} {(shouldShowDescription || shouldShowMerchant) && {merchantOrDescription}} - {props.isBillSplit && !_.isEmpty(participantAccountIDs) && requestAmount > 0 && ( + {isBillSplit && participantAccountIDs.length > 0 && requestAmount && requestAmount > 0 && ( {translate('iou.amountEach', { amount: CurrencyUtils.convertToDisplayString( - IOUUtils.calculateAmount(isPolicyExpenseChat ? 1 : participantAccountIDs.length - 1, requestAmount, requestCurrency), + IOUUtils.calculateAmount(isPolicyExpenseChat ? 1 : participantAccountIDs.length - 1, requestAmount, requestCurrency ?? ''), requestCurrency, ), })} @@ -348,32 +336,30 @@ function MoneyRequestPreview(props) { ); - if (!props.onPreviewPressed) { + if (!onPreviewPressed) { return childContainer; } - const shouldDisableOnPress = props.isBillSplit && _.isEmpty(props.transaction); + const shouldDisableOnPress = isBillSplit && isEmptyObject(transaction); return ( DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} onPressOut={() => ControlSelection.unblock()} onLongPress={showContextMenu} - accessibilityLabel={props.isBillSplit ? translate('iou.split') : translate('iou.cash')} + accessibilityLabel={isBillSplit ? translate('iou.split') : translate('iou.cash')} accessibilityHint={CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency)} - style={[styles.moneyRequestPreviewBox, ...props.containerStyles, shouldDisableOnPress && styles.cursorDefault]} + style={[styles.moneyRequestPreviewBox, containerStyles, shouldDisableOnPress && styles.cursorDefault]} > {childContainer} ); } -MoneyRequestPreview.propTypes = propTypes; -MoneyRequestPreview.defaultProps = defaultProps; MoneyRequestPreview.displayName = 'MoneyRequestPreview'; -export default withOnyx({ +export default withOnyx({ personalDetails: { key: ONYXKEYS.PERSONAL_DETAILS_LIST, }, @@ -387,7 +373,7 @@ export default withOnyx({ key: ONYXKEYS.SESSION, }, transaction: { - key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${(action && action.originalMessage && action.originalMessage.IOUTransactionID) || 0}`, + key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${(action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && action?.originalMessage?.IOUTransactionID) || 0}`, }, walletTerms: { key: ONYXKEYS.WALLET_TERMS, diff --git a/src/languages/types.ts b/src/languages/types.ts index 3185b7a8f6f1..7fd9794e01c3 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -113,7 +113,7 @@ type SplitAmountParams = {amount: number}; type DidSplitAmountMessageParams = {formattedAmount: string; comment: string}; -type AmountEachParams = {amount: number}; +type AmountEachParams = {amount: string}; type PayerOwesAmountParams = {payer: string; amount: number | string}; diff --git a/src/libs/ReceiptUtils.ts b/src/libs/ReceiptUtils.ts index bcba68a3a0bd..72d37abc1b4e 100644 --- a/src/libs/ReceiptUtils.ts +++ b/src/libs/ReceiptUtils.ts @@ -1,5 +1,6 @@ import Str from 'expensify-common/lib/str'; import type {ImageSourcePropType} from 'react-native'; +import {OnyxEntry} from 'react-native-onyx'; import ReceiptDoc from '@assets/images/receipt-doc.png'; import ReceiptGeneric from '@assets/images/receipt-generic.png'; import ReceiptHTML from '@assets/images/receipt-html.png'; @@ -28,7 +29,7 @@ type FileNameAndExtension = { * @param receiptPath * @param receiptFileName */ -function getThumbnailAndImageURIs(transaction: Transaction, receiptPath: string | null = null, receiptFileName: string | null = null): ThumbnailAndImageURI { +function getThumbnailAndImageURIs(transaction: OnyxEntry, receiptPath: string | null = null, receiptFileName: string | null = null): ThumbnailAndImageURI { // URI to image, i.e. blob:new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg const path = transaction?.receipt?.source ?? receiptPath ?? ''; // filename of uploaded image or last part of remote URI diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index c34a6753c1d5..061ce683b33c 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -37,10 +37,10 @@ Onyx.connect({ callback: (value) => (allReports = value), }); -function isDistanceRequest(transaction: Transaction): boolean { +function isDistanceRequest(transaction: OnyxEntry): boolean { // This is used during the request creation flow before the transaction has been saved to the server if (lodashHas(transaction, 'iouRequestType')) { - return transaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE; + return transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE; } // This is the case for transaction objects once they have been saved to the server @@ -258,7 +258,7 @@ function getDescription(transaction: OnyxEntry): string { /** * Return the amount field from the transaction, return the modifiedAmount if present. */ -function getAmount(transaction: OnyxEntry, isFromExpenseReport: boolean): number { +function getAmount(transaction: OnyxEntry, isFromExpenseReport?: boolean): number { // IOU requests cannot have negative values but they can be stored as negative values, let's return absolute value if (!isFromExpenseReport) { const amount = transaction?.modifiedAmount ?? 0; @@ -385,8 +385,8 @@ function getHeaderTitleTranslationKey(transaction: Transaction): string { /** * Determine whether a transaction is made with an Expensify card. */ -function isExpensifyCardTransaction(transaction: Transaction): boolean { - if (!transaction.cardID) { +function isExpensifyCardTransaction(transaction: OnyxEntry): boolean { + if (!transaction?.cardID) { return false; } return isExpensifyCard(transaction.cardID); @@ -403,8 +403,8 @@ function isCardTransaction(transaction: Transaction): boolean { /** * Check if the transaction status is set to Pending. */ -function isPending(transaction: Transaction): boolean { - if (!transaction.status) { +function isPending(transaction: OnyxEntry): boolean { + if (!transaction?.status) { return false; } return transaction.status === CONST.TRANSACTION.STATUS.PENDING; From 02296e884a636fa8772b27013ec52878ea059af7 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 16 Jan 2024 12:42:24 +0100 Subject: [PATCH 03/15] Migrate MoneyRequestView to TypeScript --- src/ONYXKEYS.ts | 3 +- src/components/MenuItem.tsx | 2 +- ...neyRequestView.js => MoneyRequestView.tsx} | 257 ++++++++---------- src/libs/TransactionUtils.ts | 2 +- src/libs/actions/IOU.js | 4 +- src/types/onyx/Policy.ts | 3 + src/types/onyx/PolicyTag.ts | 3 + src/types/onyx/Transaction.ts | 6 +- src/types/onyx/TransactionViolation.ts | 4 +- src/types/onyx/index.ts | 3 +- 10 files changed, 132 insertions(+), 155 deletions(-) rename src/components/ReportActionItem/{MoneyRequestView.js => MoneyRequestView.tsx} (70%) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 98e3856f4544..909ec9f8f50d 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -437,7 +437,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.DOWNLOAD]: OnyxTypes.Download; [ONYXKEYS.COLLECTION.POLICY]: OnyxTypes.Policy; [ONYXKEYS.COLLECTION.POLICY_DRAFTS]: OnyxTypes.Policy; - [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategory; + [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategories; [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTags; [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMembers; [ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS]: OnyxTypes.PolicyMember; @@ -459,6 +459,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.SECURITY_GROUP]: OnyxTypes.SecurityGroup; [ONYXKEYS.COLLECTION.TRANSACTION]: OnyxTypes.Transaction; [ONYXKEYS.COLLECTION.TRANSACTION_DRAFT]: OnyxTypes.Transaction; + [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: OnyxTypes.TransactionViolations; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS]: OnyxTypes.RecentlyUsedTags; [ONYXKEYS.COLLECTION.SELECTED_TAB]: string; [ONYXKEYS.COLLECTION.PRIVATE_NOTES_DRAFT]: string; diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index 334fa9895205..5f82e1da94ed 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -204,7 +204,7 @@ type MenuItemProps = (IconProps | AvatarProps | NoIcon) & { shouldBlockSelection?: boolean; /** Whether should render title as HTML or as Text */ - shouldParseTitle?: false; + shouldParseTitle?: boolean; /** Should check anonymous user in onPress function */ shouldCheckActionAllowedOnPress?: boolean; diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.tsx similarity index 70% rename from src/components/ReportActionItem/MoneyRequestView.js rename to src/components/ReportActionItem/MoneyRequestView.tsx index 036b64af1e4b..124a6ab4c9d3 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -1,30 +1,24 @@ -import lodashGet from 'lodash/get'; -import lodashValues from 'lodash/values'; -import PropTypes from 'prop-types'; import React, {useCallback} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import categoryPropTypes from '@components/categoryPropTypes'; +import type {OnyxEntry} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ReceiptEmptyState from '@components/ReceiptEmptyState'; import SpacerView from '@components/SpacerView'; import Switch from '@components/Switch'; -import tagPropTypes from '@components/tagPropTypes'; import Text from '@components/Text'; -import transactionPropTypes from '@components/transactionPropTypes'; import ViolationMessages from '@components/ViolationMessages'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useViolations from '@hooks/useViolations'; +import type {ViolationField} from '@hooks/useViolations'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CardUtils from '@libs/CardUtils'; -import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; @@ -34,96 +28,67 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground'; -import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; -import iouReportPropTypes from '@pages/iouReportPropTypes'; -import reportPropTypes from '@pages/reportPropTypes'; -import {policyDefaultProps, policyPropTypes} from '@pages/workspace/withPolicy'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {TransactionPendingFieldsKey} from '@src/types/onyx/Transaction'; import ReportActionItemImage from './ReportActionItemImage'; -const violationNames = lodashValues(CONST.VIOLATIONS); +type MoneyRequestViewTransactionOnyxProps = { + /** The transaction associated with the transactionThread */ + transaction: OnyxEntry; +}; -const transactionViolationPropType = PropTypes.shape({ - type: PropTypes.string.isRequired, - name: PropTypes.oneOf(violationNames).isRequired, - data: PropTypes.shape({ - rejectedBy: PropTypes.string, - rejectReason: PropTypes.string, - amount: PropTypes.string, - surcharge: PropTypes.number, - invoiceMarkup: PropTypes.number, - maxAge: PropTypes.number, - tagName: PropTypes.string, - formattedLimitAmount: PropTypes.string, - categoryLimit: PropTypes.string, - limit: PropTypes.string, - category: PropTypes.string, - brokenBankConnection: PropTypes.bool, - isAdmin: PropTypes.bool, - email: PropTypes.string, - isTransactionOlderThan7Days: PropTypes.bool, - member: PropTypes.string, - taxName: PropTypes.string, - }), -}); +type MoneyRequestViewOnyxPropsWithoutTransaction = { + /** The policy object for the current route */ + policy: OnyxEntry; -const propTypes = { - /** The report currently being looked at */ - report: reportPropTypes.isRequired, + /** Collection of categories attached to a policy */ + policyCategories: OnyxEntry; - /** Whether we should display the horizontal rule below the component */ - shouldShowHorizontalRule: PropTypes.bool.isRequired, + /** Collection of tags attached to a policy */ + policyTags: OnyxEntry; - /* Onyx Props */ /** The expense report or iou report (only will have a value if this is a transaction thread) */ - parentReport: iouReportPropTypes, + parentReport: OnyxEntry; /** The actions from the parent report */ - parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), - - /** The policy the report is tied to */ - ...policyPropTypes, - - /** Collection of categories attached to a policy */ - policyCategories: PropTypes.objectOf(categoryPropTypes), - - /** The transaction associated with the transactionThread */ - transaction: transactionPropTypes, + parentReportActions: OnyxEntry; /** Violations detected in this transaction */ - transactionViolations: PropTypes.arrayOf(transactionViolationPropType), + transactionViolations: OnyxEntry; +}; - /** Collection of tags attached to a policy */ - policyTags: tagPropTypes, +type MoneyRequestViewPropsWithoutTransaction = MoneyRequestViewOnyxPropsWithoutTransaction & { + /** The report currently being looked at */ + report: OnyxTypes.Report; - ...withCurrentUserPersonalDetailsPropTypes, + /** Whether we should display the horizontal rule below the component */ + shouldShowHorizontalRule: boolean; }; -const defaultProps = { - parentReport: {}, - parentReportActions: {}, - transaction: { - amount: 0, - currency: CONST.CURRENCY.USD, - comment: {comment: ''}, - }, - transactionViolations: [], - policyCategories: {}, - policyTags: {}, - ...policyDefaultProps, -}; +type MoneyRequestViewProps = MoneyRequestViewTransactionOnyxProps & MoneyRequestViewPropsWithoutTransaction; -function MoneyRequestView({report, parentReport, parentReportActions, policyCategories, shouldShowHorizontalRule, transaction, policyTags, policy, transactionViolations}) { +function MoneyRequestView({ + report, + parentReport, + parentReportActions, + policyCategories, + shouldShowHorizontalRule, + transaction, + policyTags, + policy, + transactionViolations, +}: MoneyRequestViewProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); const {canUseViolations} = usePermissions(); - const parentReportAction = parentReportActions[report.parentReportActionID] || {}; + const parentReportAction = parentReportActions?.[report.parentReportActionID ?? ''] ?? null; const moneyRequestReport = parentReport; const { created: transactionDate, @@ -137,20 +102,20 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate originalAmount: transactionOriginalAmount, originalCurrency: transactionOriginalCurrency, cardID: transactionCardID, - } = ReportUtils.getTransactionDetails(transaction); + } = ReportUtils.getTransactionDetails(transaction) ?? {}; const isEmptyMerchant = transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); let formattedTransactionAmount = transactionAmount ? CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency) : ''; - const hasPendingWaypoints = lodashGet(transaction, 'pendingFields.waypoints', null); + const hasPendingWaypoints = transaction?.pendingFields?.waypoints; if (isDistanceRequest && (!formattedTransactionAmount || hasPendingWaypoints)) { formattedTransactionAmount = translate('common.tbd'); } const formattedOriginalAmount = transactionOriginalAmount && transactionOriginalCurrency && CurrencyUtils.convertToDisplayString(transactionOriginalAmount, transactionOriginalCurrency); const isCardTransaction = TransactionUtils.isCardTransaction(transaction); - const cardProgramName = isCardTransaction ? CardUtils.getCardDescription(transactionCardID) : ''; + const cardProgramName = isCardTransaction && transactionCardID !== undefined ? CardUtils.getCardDescription(transactionCardID) : ''; // Flags for allowing or disallowing editing a money request - const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); + const isSettled = ReportUtils.isSettled(moneyRequestReport?.reportID); const isCancelled = moneyRequestReport && moneyRequestReport.isCancelledIOU; // Used for non-restricted fields such as: description, category, tag, billable, etc. @@ -167,26 +132,30 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate // Fetches only the first tag, for now const policyTag = PolicyUtils.getTag(policyTags); - const policyTagsList = lodashGet(policyTag, 'tags', {}); + const policyTagsList = policyTag?.tags ?? {}; // Flags for showing categories and tags - const shouldShowCategory = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); - const shouldShowTag = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledOptions(lodashValues(policyTagsList))); - const shouldShowBillable = isPolicyExpenseChat && (transactionBillable || !lodashGet(policy, 'disabledFields.defaultBillable', true)); + // transactionCategory can be an empty string + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const shouldShowCategory = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); + // transactionTag can be an empty string + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const shouldShowTag = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledOptions(Object.values(policyTagsList))); + const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true)); - const {getViolationsForField} = useViolations(transactionViolations); - const hasViolations = useCallback((field) => canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); + const {getViolationsForField} = useViolations(transactionViolations ?? []); + const hasViolations = useCallback((field: ViolationField): boolean => !!canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); let amountDescription = `${translate('iou.amount')}`; const saveBillable = useCallback( - (newBillable) => { + (newBillable: boolean) => { // If the value hasn't changed, don't request to save changes on the server and just close the modal if (newBillable === TransactionUtils.getBillable(transaction)) { Navigation.dismissModal(); return; } - IOU.updateMoneyRequestBillable(transaction.transactionID, report.reportID, newBillable); + IOU.updateMoneyRequestBillable(transaction?.transactionID ?? '', report?.reportID, newBillable); Navigation.dismissModal(); }, [transaction, report], @@ -225,8 +194,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate hasErrors = canEdit && TransactionUtils.hasMissingSmartscanFields(transaction); } - const pendingAction = lodashGet(transaction, 'pendingAction'); - const getPendingFieldAction = (fieldPath) => lodashGet(transaction, fieldPath) || pendingAction; + const pendingAction = transaction?.pendingAction; + const getPendingFieldAction = (fieldPath: TransactionPendingFieldsKey) => transaction?.pendingFields?.[fieldPath] ?? pendingAction; return ( @@ -236,9 +205,11 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate )} {canUseViolations && } - + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.AMOUNT))} - brickRoadIndicator={hasViolations('amount') || (hasErrors && transactionAmount === 0) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={hasViolations('amount') || (hasErrors && transactionAmount === 0) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} error={hasErrors && transactionAmount === 0 ? translate('common.error.enterAmount') : ''} /> {canUseViolations && } - + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DESCRIPTION))} wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} - brickRoadIndicator={hasViolations('comment') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={hasViolations('comment') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} numberOfLinesTitle={0} /> {canUseViolations && } {isDistanceRequest ? ( - + ) : ( - + // Merchant can be an empty string + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.MERCHANT))} - brickRoadIndicator={hasViolations('merchant') || (hasErrors && isEmptyMerchant) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={hasViolations('merchant') || (hasErrors && isEmptyMerchant) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} error={hasErrors && isEmptyMerchant ? translate('common.error.enterMerchant') : ''} /> {canUseViolations && } )} - + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DATE))} - brickRoadIndicator={hasViolations('date') || (hasErrors && transactionDate === '') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={hasViolations('date') || (hasErrors && transactionDate === '') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} error={hasErrors && transactionDate === '' ? translate('common.error.enterDate') : ''} /> {canUseViolations && } {shouldShowCategory && ( - + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.CATEGORY))} - brickRoadIndicator={hasViolations('category') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={hasViolations('category') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} /> {canUseViolations && } )} {shouldShowTag && ( - + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.TAG))} - brickRoadIndicator={hasViolations('tag') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={hasViolations('tag') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} /> {canUseViolations && } )} {isCardTransaction && ( - + {translate('common.billable')} @@ -397,50 +370,42 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate ); } -MoneyRequestView.propTypes = propTypes; -MoneyRequestView.defaultProps = defaultProps; MoneyRequestView.displayName = 'MoneyRequestView'; -export default compose( - withCurrentUserPersonalDetails, - withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - policy: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, - }, - policyCategories: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report.policyID}`, - }, - policyTags: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report.policyID}`, - }, - parentReport: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, - }, - parentReportActions: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`, - canEvict: false, +export default withOnyx({ + policy: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, + }, + policyCategories: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report.policyID}`, + }, + policyTags: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report.policyID}`, + }, + parentReport: { + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, + }, + parentReportActions: { + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`, + canEvict: false, + }, + transactionViolations: { + key: ({report}) => { + const parentReportAction = ReportActionsUtils.getParentReportAction(report); + const originalMessage = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage : undefined; + const transactionID = originalMessage?.IOUTransactionID ?? 0; + return `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`; }, - }), - withOnyx({ + }, +})( + withOnyx({ transaction: { key: ({report, parentReportActions}) => { - const parentReportAction = parentReportActions[report.parentReportActionID]; - const transactionID = lodashGet(parentReportAction, ['originalMessage', 'IOUTransactionID'], 0); + const parentReportAction = parentReportActions?.[report.parentReportActionID ?? '']; + const originalMessage = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage : undefined; + const transactionID = originalMessage?.IOUTransactionID ?? 0; return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`; }, }, - transactionViolation: { - key: ({report}) => { - const parentReportAction = ReportActionsUtils.getParentReportAction(report); - const transactionID = lodashGet(parentReportAction, ['originalMessage', 'IOUTransactionID'], 0); - return `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`; - }, - }, - policyTags: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report.policyID}`, - }, - }), -)(MoneyRequestView); + })(MoneyRequestView), +); diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 061ce683b33c..d92c2b9b3293 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -395,7 +395,7 @@ function isExpensifyCardTransaction(transaction: OnyxEntry): boolea /** * Determine whether a transaction is made with a card (Expensify or Company Card). */ -function isCardTransaction(transaction: Transaction): boolean { +function isCardTransaction(transaction: OnyxEntry): boolean { const cardID = transaction?.cardID ?? 0; return isCorporateCard(cardID); } diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 9aa7c52b1ea0..53cc81ef96a5 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1117,8 +1117,8 @@ function updateMoneyRequestDate(transactionID, transactionThreadReportID, val) { * Updates the billable field of a money request * * @param {String} transactionID - * @param {Number} transactionThreadReportID - * @param {String} val + * @param {String} transactionThreadReportID + * @param {Boolean} val */ function updateMoneyRequestBillable(transactionID, transactionThreadReportID, val) { const transactionChanges = { diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index da4522487a7a..e58e8b56b92e 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -82,6 +82,9 @@ type Policy = { /** The employee list of the policy */ employeeList?: []; + + /** A list of disabled fields */ + disabledFields?: Record; }; export default Policy; diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index 58a21dcf4df5..c14ede73b622 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -8,6 +8,9 @@ type PolicyTag = { /** "General Ledger code" that corresponds to this tag in an accounting system. Similar to an ID. */ // eslint-disable-next-line @typescript-eslint/naming-convention 'GL Code': string; + + /** Nested tags */ + tags: PolicyTags; }; type PolicyTags = Record; diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 8b7e26280305..3a652ba0ef8c 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -49,6 +49,8 @@ type Route = { type Routes = Record; +type TransactionPendingFieldsKey = keyof Transaction | keyof Comment; + type Transaction = { amount: number; billable: boolean; @@ -76,7 +78,7 @@ type Transaction = { routes?: Routes; transactionID: string; tag: string; - pendingFields?: Partial<{[K in keyof Transaction | keyof Comment]: ValueOf}>; + pendingFields?: Partial<{[K in TransactionPendingFieldsKey]: ValueOf}>; /** Card Transactions */ @@ -97,4 +99,4 @@ type Transaction = { }; export default Transaction; -export type {WaypointCollection, Comment, Receipt, Waypoint}; +export type {WaypointCollection, Comment, Receipt, Waypoint, TransactionPendingFieldsKey}; diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index dd7a9ef65746..6b5882cfc73d 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -31,4 +31,6 @@ type TransactionViolation = { }; }; -export type {TransactionViolation, ViolationName}; +type TransactionViolations = TransactionViolation[]; + +export type {TransactionViolation, TransactionViolations, ViolationName}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 8cba351d0f45..d613d8c5d2da 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -55,7 +55,7 @@ import type SecurityGroup from './SecurityGroup'; import type Session from './Session'; import type Task from './Task'; import type Transaction from './Transaction'; -import type {TransactionViolation, ViolationName} from './TransactionViolation'; +import type {TransactionViolation, TransactionViolations, ViolationName} from './TransactionViolation'; import type User from './User'; import type UserLocation from './UserLocation'; import type UserWallet from './UserWallet'; @@ -126,6 +126,7 @@ export type { Task, Transaction, TransactionViolation, + TransactionViolations, User, UserLocation, UserWallet, From 03a0c4cdc55ab72b61987e9e3d2e10b68cf3c69f Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 16 Jan 2024 13:03:16 +0100 Subject: [PATCH 04/15] Fix lint errors --- src/components/ReportActionItem/MoneyRequestPreview.tsx | 5 ++++- src/libs/ReceiptUtils.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.tsx b/src/components/ReportActionItem/MoneyRequestPreview.tsx index a6df98973670..f997cd7273a9 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview.tsx @@ -373,7 +373,10 @@ export default withOnyx( key: ONYXKEYS.SESSION, }, transaction: { - key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${(action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && action?.originalMessage?.IOUTransactionID) || 0}`, + key: ({action}) => { + const originalMessage = action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? action.originalMessage : undefined; + return `${ONYXKEYS.COLLECTION.TRANSACTION}${originalMessage?.IOUTransactionID ?? 0}`; + }, }, walletTerms: { key: ONYXKEYS.WALLET_TERMS, diff --git a/src/libs/ReceiptUtils.ts b/src/libs/ReceiptUtils.ts index 72d37abc1b4e..29535e2a07aa 100644 --- a/src/libs/ReceiptUtils.ts +++ b/src/libs/ReceiptUtils.ts @@ -1,6 +1,6 @@ import Str from 'expensify-common/lib/str'; import type {ImageSourcePropType} from 'react-native'; -import {OnyxEntry} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import ReceiptDoc from '@assets/images/receipt-doc.png'; import ReceiptGeneric from '@assets/images/receipt-generic.png'; import ReceiptHTML from '@assets/images/receipt-html.png'; From 663758370fbf00a38bad24a58fa99c9cc604c49e Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 17 Jan 2024 08:47:08 +0100 Subject: [PATCH 05/15] Use ContextMenuAnchor type for anchor typing --- src/components/ReportActionItem/MoneyRequestPreview.tsx | 1 - src/components/ShowContextMenuContext.ts | 7 ++++--- .../report/ContextMenu/PopoverReportActionContextMenu.tsx | 6 +----- .../home/report/ContextMenu/ReportActionContextMenu.ts | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.tsx b/src/components/ReportActionItem/MoneyRequestPreview.tsx index 8677b7e5cbb8..b40773302695 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview.tsx @@ -177,7 +177,6 @@ function MoneyRequestPreview({ }; const showContextMenu = (event: GestureResponderEvent) => { - // @ts-expect-error TODO: Remove this once ShowContextMenuContext (https://github.com/Expensify/App/issues/24980) is migrated to TypeScript. showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive); }; diff --git a/src/components/ShowContextMenuContext.ts b/src/components/ShowContextMenuContext.ts index 17557051bef9..374ca8a2f1e5 100644 --- a/src/components/ShowContextMenuContext.ts +++ b/src/components/ShowContextMenuContext.ts @@ -1,15 +1,16 @@ import {createContext} from 'react'; // eslint-disable-next-line no-restricted-imports -import type {GestureResponderEvent, Text as RNText} from 'react-native'; +import type {GestureResponderEvent} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as ReportUtils from '@libs/ReportUtils'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; +import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; import type {Report, ReportAction} from '@src/types/onyx'; type ShowContextMenuContextProps = { - anchor: RNText | null; + anchor: ContextMenuAnchor; report: OnyxEntry; action: OnyxEntry; checkIfContextMenuActive: () => void; @@ -36,7 +37,7 @@ ShowContextMenuContext.displayName = 'ShowContextMenuContext'; */ function showContextMenuForReport( event: GestureResponderEvent | MouseEvent, - anchor: RNText | null, + anchor: ContextMenuAnchor, reportID: string, action: OnyxEntry, checkIfContextMenuActive: () => void, diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx index 46b783bca3f9..bbd048b0d82c 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -12,11 +12,7 @@ import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import type {ReportAction} from '@src/types/onyx'; import BaseReportActionContextMenu from './BaseReportActionContextMenu'; -import type {ContextMenuType, ReportActionContextMenu} from './ReportActionContextMenu'; - -type ContextMenuAnchorCallback = (x: number, y: number) => void; - -type ContextMenuAnchor = {measureInWindow: (callback: ContextMenuAnchorCallback) => void}; +import type {ContextMenuAnchor, ContextMenuType, ReportActionContextMenu} from './ReportActionContextMenu'; type Location = { x: number; diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts index 19d46c1fdc4a..7080c02775ce 100644 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts @@ -15,7 +15,7 @@ type OnCancel = () => void; type ContextMenuType = ValueOf; -type ContextMenuAnchor = View | RNText | null; +type ContextMenuAnchor = View | RNText | null | undefined; type ShowContextMenu = ( type: ContextMenuType, From e1e39e130489d4a801161dcb25399bcae380870c Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 17 Jan 2024 11:48:28 +0100 Subject: [PATCH 06/15] Code improvements --- src/components/Pressable/GenericPressable/types.ts | 2 +- .../ReportActionItem/MoneyRequestPreview.tsx | 2 +- src/components/ReportActionItem/MoneyRequestView.tsx | 10 ++++------ 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/Pressable/GenericPressable/types.ts b/src/components/Pressable/GenericPressable/types.ts index 03cdb30c67fc..2dd2e17e0454 100644 --- a/src/components/Pressable/GenericPressable/types.ts +++ b/src/components/Pressable/GenericPressable/types.ts @@ -40,7 +40,7 @@ type PressableProps = RNPressableProps & /** * onPress callback */ - onPress: ((event?: GestureResponderEvent | KeyboardEvent) => void | Promise) | undefined; + onPress?: (event?: GestureResponderEvent | KeyboardEvent) => void | Promise; /** * Specifies keyboard shortcut to trigger onPressHandler diff --git a/src/components/ReportActionItem/MoneyRequestPreview.tsx b/src/components/ReportActionItem/MoneyRequestPreview.tsx index b40773302695..1a685b85bf11 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview.tsx @@ -82,7 +82,7 @@ type MoneyRequestPreviewProps = MoneyRequestPreviewOnyxProps & { checkIfContextMenuActive?: () => void; /** Extra styles to pass to View wrapper */ - containerStyles: StyleProp; + containerStyles?: StyleProp; /** True if this is this IOU is a split instead of a 1:1 request */ isBillSplit: boolean; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 28a7e1593dab..30ebce9f566c 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -265,7 +265,7 @@ function MoneyRequestView({ {canUseViolations && } {isDistanceRequest ? ( - + ) : ( - // Merchant can be an empty string - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - + } {shouldShowCategory && ( - + )} {shouldShowTag && ( - + Date: Thu, 18 Jan 2024 17:54:53 +0100 Subject: [PATCH 07/15] Update getDisplayDeleteAmountText function, move transactionViolations back to the second hoc --- .../ReportActionItem/MoneyRequestPreview.tsx | 9 ++++---- .../ReportActionItem/MoneyRequestView.tsx | 22 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.tsx b/src/components/ReportActionItem/MoneyRequestPreview.tsx index 1a685b85bf11..d2792c3964e4 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview.tsx @@ -37,7 +37,9 @@ import * as Localize from '@src/libs/Localize'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; +import type {IOUMessage} from '@src/types/onyx/OriginalMessage'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import type {EmptyObject} from '@src/types/utils/EmptyObject'; import ReportActionItemImages from './ReportActionItemImages'; type MoneyRequestPreviewOnyxProps = { @@ -229,11 +231,8 @@ function MoneyRequestPreview({ }; const getDisplayDeleteAmountText = (): string => { - const {amount, currency} = ReportUtils.getTransactionDetails(action.originalMessage as OnyxTypes.Transaction) ?? {}; - - if (isDistanceRequest) { - return CurrencyUtils.convertToDisplayString(TransactionUtils.getAmount(action.originalMessage as OnyxTypes.Transaction), currency); - } + const iouOriginalMessage: IOUMessage | EmptyObject = action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? action.originalMessage : {}; + const {amount = 0, currency = CONST.CURRENCY.USD} = iouOriginalMessage; return CurrencyUtils.convertToDisplayString(amount, currency); }; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 30ebce9f566c..7aeb86a3d23e 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -39,6 +39,9 @@ import ReportActionItemImage from './ReportActionItemImage'; type MoneyRequestViewTransactionOnyxProps = { /** The transaction associated with the transactionThread */ transaction: OnyxEntry; + + /** Violations detected in this transaction */ + transactionViolations: OnyxEntry; }; type MoneyRequestViewOnyxPropsWithoutTransaction = { @@ -56,9 +59,6 @@ type MoneyRequestViewOnyxPropsWithoutTransaction = { /** The actions from the parent report */ parentReportActions: OnyxEntry; - - /** Violations detected in this transaction */ - transactionViolations: OnyxEntry; }; type MoneyRequestViewPropsWithoutTransaction = MoneyRequestViewOnyxPropsWithoutTransaction & { @@ -387,14 +387,6 @@ export default withOnyx `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`, canEvict: false, }, - transactionViolations: { - key: ({report}) => { - const parentReportAction = ReportActionsUtils.getParentReportAction(report); - const originalMessage = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage : undefined; - const transactionID = originalMessage?.IOUTransactionID ?? 0; - return `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`; - }, - }, })( withOnyx({ transaction: { @@ -405,5 +397,13 @@ export default withOnyx { + const parentReportAction = ReportActionsUtils.getParentReportAction(report); + const originalMessage = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage : undefined; + const transactionID = originalMessage?.IOUTransactionID ?? 0; + return `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`; + }, + }, })(MoneyRequestView), ); From be3755861d75e8bc7e2dfc3e2eecdbfa99dcdd5e Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 22 Jan 2024 10:02:33 +0100 Subject: [PATCH 08/15] TS fixes after merging main --- .../ReportActionItem/MoneyRequestPreview.tsx | 1 - src/components/ReportActionItem/MoneyRequestView.tsx | 2 -- .../ReportActionItem/ReportActionItemImage.tsx | 7 ++++--- .../ReportActionItem/ReportActionItemImages.tsx | 11 ++--------- src/libs/ReceiptUtils.ts | 8 ++++---- 5 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.tsx b/src/components/ReportActionItem/MoneyRequestPreview.tsx index 3ac97d4552af..2d566ca36cda 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview.tsx @@ -258,7 +258,6 @@ function MoneyRequestPreview({ > {hasReceipt && ( ; /** whether thumbnail is refer the local file or not */ isLocalFile?: boolean; diff --git a/src/components/ReportActionItem/ReportActionItemImages.tsx b/src/components/ReportActionItem/ReportActionItemImages.tsx index c24defb8ac08..06edea95fb89 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.tsx +++ b/src/components/ReportActionItem/ReportActionItemImages.tsx @@ -6,20 +6,13 @@ import Text from '@components/Text'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {ThumbnailAndImageURI} from '@libs/ReceiptUtils'; import variables from '@styles/variables'; -import type {Transaction} from '@src/types/onyx'; import ReportActionItemImage from './ReportActionItemImage'; -type Image = { - thumbnail: string | number; - image: string | number; - transaction: Transaction; - isLocalFile: boolean; -}; - type ReportActionItemImagesProps = { /** array of image and thumbnail URIs */ - images: Image[]; + images: ThumbnailAndImageURI[]; // We're not providing default values for size and total and disabling the ESLint rule // because we want them to default to the length of images, but we can't set default props diff --git a/src/libs/ReceiptUtils.ts b/src/libs/ReceiptUtils.ts index 29535e2a07aa..305720eb8067 100644 --- a/src/libs/ReceiptUtils.ts +++ b/src/libs/ReceiptUtils.ts @@ -1,5 +1,4 @@ import Str from 'expensify-common/lib/str'; -import type {ImageSourcePropType} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import ReceiptDoc from '@assets/images/receipt-doc.png'; import ReceiptGeneric from '@assets/images/receipt-generic.png'; @@ -11,8 +10,8 @@ import type {Transaction} from '@src/types/onyx'; import * as FileUtils from './fileDownload/FileUtils'; type ThumbnailAndImageURI = { - image: ImageSourcePropType | string; - thumbnail: ImageSourcePropType | string | null; + image: number | string; + thumbnail: number | string | null; transaction?: Transaction; isLocalFile?: boolean; }; @@ -68,8 +67,9 @@ function getThumbnailAndImageURIs(transaction: OnyxEntry, receiptPa } const isLocalFile = typeof path === 'number' || path.startsWith('blob:') || path.startsWith('file:') || path.startsWith('/'); - return {thumbnail: image, image: path, isLocalFile}; + return {thumbnail: image as string | number, image: path, isLocalFile}; } // eslint-disable-next-line import/prefer-default-export export {getThumbnailAndImageURIs}; +export type {ThumbnailAndImageURI}; From 01fa10442b48b2f13bb2de632e328344703ebbd3 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 23 Jan 2024 10:00:52 +0100 Subject: [PATCH 09/15] Fix TS issues --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- src/libs/ReceiptUtils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 17748ca20789..877094ca509a 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -319,7 +319,7 @@ function MoneyRequestView({ , receiptPath: string | null = null, receiptFileName: string | null = null): ThumbnailAndImageURI { if (Object.hasOwn(transaction?.pendingFields ?? {}, 'waypoints')) { - return {thumbnail: null, image: ReceiptGeneric, isLocalFile: true}; + return {thumbnail: null, image: ReceiptGeneric as string | number, isLocalFile: true}; } // URI to image, i.e. blob:new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg const path = transaction?.receipt?.source ?? receiptPath ?? ''; From f65fbe0f2fa83701041d9dccf742890107d9dd48 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 23 Jan 2024 13:46:50 +0100 Subject: [PATCH 10/15] Update image typing --- src/components/ImageWithSizeCalculation.tsx | 4 ++-- .../ReportActionItem/ReportActionItemImage.tsx | 5 +++-- .../ReportActionItem/ReportActionItemImages.tsx | 2 +- src/components/ThumbnailImage.tsx | 4 ++-- src/libs/ReceiptUtils.ts | 9 +++++---- src/libs/tryResolveUrlFromApiRoot.ts | 9 +++++---- 6 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/components/ImageWithSizeCalculation.tsx b/src/components/ImageWithSizeCalculation.tsx index c65faef53748..d0559327274a 100644 --- a/src/components/ImageWithSizeCalculation.tsx +++ b/src/components/ImageWithSizeCalculation.tsx @@ -1,6 +1,6 @@ import delay from 'lodash/delay'; import React, {useEffect, useRef, useState} from 'react'; -import type {StyleProp, ViewStyle} from 'react-native'; +import type {ImageSourcePropType, StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import Log from '@libs/Log'; @@ -19,7 +19,7 @@ type OnLoadNativeEvent = { type ImageWithSizeCalculationProps = { /** Url for image to display */ - url: string | number; + url: string | ImageSourcePropType; /** Any additional styles to apply */ style?: StyleProp; diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index fa47f3c7f316..c014a71a4789 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -2,6 +2,7 @@ import Str from 'expensify-common/lib/str'; import React from 'react'; import type {ReactElement} from 'react'; import {View} from 'react-native'; +import type {ImageSourcePropType} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx/lib/types'; import AttachmentModal from '@components/AttachmentModal'; import EReceiptThumbnail from '@components/EReceiptThumbnail'; @@ -18,10 +19,10 @@ import type {Transaction} from '@src/types/onyx'; type ReportActionItemImageProps = { /** thumbnail URI for the image */ - thumbnail?: string | number | null; + thumbnail?: string | ImageSourcePropType | null; /** URI for the image or local numeric reference for the image */ - image?: string | number; + image?: string | ImageSourcePropType; /** whether or not to enable the image preview modal */ enablePreviewModal?: boolean; diff --git a/src/components/ReportActionItem/ReportActionItemImages.tsx b/src/components/ReportActionItem/ReportActionItemImages.tsx index 06edea95fb89..00b91bf4f862 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.tsx +++ b/src/components/ReportActionItem/ReportActionItemImages.tsx @@ -72,7 +72,7 @@ function ReportActionItemImages({images, size, total, isHovered = false}: Report const borderStyle = shouldShowBorder ? styles.reportActionItemImageBorder : {}; return ( ; diff --git a/src/libs/ReceiptUtils.ts b/src/libs/ReceiptUtils.ts index f523632cd8b6..76887fe71107 100644 --- a/src/libs/ReceiptUtils.ts +++ b/src/libs/ReceiptUtils.ts @@ -1,4 +1,5 @@ import Str from 'expensify-common/lib/str'; +import type {ImageSourcePropType} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import ReceiptDoc from '@assets/images/receipt-doc.png'; import ReceiptGeneric from '@assets/images/receipt-generic.png'; @@ -10,8 +11,8 @@ import type {Transaction} from '@src/types/onyx'; import * as FileUtils from './fileDownload/FileUtils'; type ThumbnailAndImageURI = { - image: number | string; - thumbnail: number | string | null; + image: ImageSourcePropType | string; + thumbnail: ImageSourcePropType | string | null; transaction?: Transaction; isLocalFile?: boolean; }; @@ -30,7 +31,7 @@ type FileNameAndExtension = { */ function getThumbnailAndImageURIs(transaction: OnyxEntry, receiptPath: string | null = null, receiptFileName: string | null = null): ThumbnailAndImageURI { if (Object.hasOwn(transaction?.pendingFields ?? {}, 'waypoints')) { - return {thumbnail: null, image: ReceiptGeneric as string | number, isLocalFile: true}; + return {thumbnail: null, image: ReceiptGeneric, isLocalFile: true}; } // URI to image, i.e. blob:new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg const path = transaction?.receipt?.source ?? receiptPath ?? ''; @@ -67,7 +68,7 @@ function getThumbnailAndImageURIs(transaction: OnyxEntry, receiptPa } const isLocalFile = typeof path === 'number' || path.startsWith('blob:') || path.startsWith('file:') || path.startsWith('/'); - return {thumbnail: image as string | number, image: path, isLocalFile}; + return {thumbnail: image, image: path, isLocalFile}; } // eslint-disable-next-line import/prefer-default-export diff --git a/src/libs/tryResolveUrlFromApiRoot.ts b/src/libs/tryResolveUrlFromApiRoot.ts index adf717d500be..df80211ff6f1 100644 --- a/src/libs/tryResolveUrlFromApiRoot.ts +++ b/src/libs/tryResolveUrlFromApiRoot.ts @@ -1,3 +1,4 @@ +import type {ImageSourcePropType} from 'react-native'; import Config from '@src/CONFIG'; import type {Request} from '@src/types/onyx'; import * as ApiUtils from './ApiUtils'; @@ -18,12 +19,12 @@ const ORIGIN_PATTERN = new RegExp(`^(${ORIGINS_TO_REPLACE.join('|')})`); * - Unmatched URLs (non expensify) are returned with no modifications */ function tryResolveUrlFromApiRoot(url: string): string; -function tryResolveUrlFromApiRoot(url: number): number; -function tryResolveUrlFromApiRoot(url: string | number): string | number; -function tryResolveUrlFromApiRoot(url: string | number): string | number { +function tryResolveUrlFromApiRoot(url: ImageSourcePropType): ImageSourcePropType; +function tryResolveUrlFromApiRoot(url: string | ImageSourcePropType): string | ImageSourcePropType; +function tryResolveUrlFromApiRoot(url: string | ImageSourcePropType): string | ImageSourcePropType { // in native, when we import an image asset, it will have a number representation which can be used in `source` of Image // in this case we can skip the url resolving - if (typeof url === 'number') { + if (typeof url !== 'string') { return url; } const apiRoot = ApiUtils.getApiRoot({shouldUseSecure: false} as Request); From c07a39eff58ee3093ebf2a5436fd340b94d8ec4e Mon Sep 17 00:00:00 2001 From: VickyStash Date: Fri, 26 Jan 2024 14:54:09 +0100 Subject: [PATCH 11/15] TS fixes after merging OptionsListUtils migration --- .../ReportActionItem/MoneyRequestPreview.tsx | 4 +--- src/libs/OptionsListUtils.ts | 20 ++++++++++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.tsx b/src/components/ReportActionItem/MoneyRequestPreview.tsx index 2d566ca36cda..83a287d68c55 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview.tsx @@ -36,7 +36,6 @@ import CONST from '@src/CONST'; import * as Localize from '@src/libs/Localize'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; -import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import type {IOUMessage} from '@src/types/onyx/OriginalMessage'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; @@ -136,8 +135,7 @@ function MoneyRequestPreview({ const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport); const participantAccountIDs = action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && isBillSplit ? action.originalMessage.participantAccountIDs ?? [] : [managerID, ownerAccountID]; - // TODO: Remove the assertion once OptionsListUtils (https://github.com/Expensify/App/issues/24921) is migrated to TypeScript. - const participantAvatars = OptionsListUtils.getAvatarsForAccountIDs(participantAccountIDs, personalDetails ?? {}) as OnyxCommon.Icon[]; + const participantAvatars = OptionsListUtils.getAvatarsForAccountIDs(participantAccountIDs, personalDetails ?? {}); const sortedParticipantAvatars = lodashSortBy(participantAvatars, (avatar) => avatar.id); if (isPolicyExpenseChat && isBillSplit) { sortedParticipantAvatars.push(ReportUtils.getWorkspaceIcon(chatReport)); diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 2621e4d7f12b..463c3e7d5ee8 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -10,7 +10,21 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Beta, Login, PersonalDetails, PersonalDetailsList, Policy, PolicyCategories, Report, ReportAction, ReportActions, Transaction, TransactionViolation} from '@src/types/onyx'; +import type { + Beta, + Login, + PersonalDetails, + PersonalDetailsList, + Policy, + PolicyCategories, + PolicyCategory, + PolicyTag, + Report, + ReportAction, + ReportActions, + Transaction, + TransactionViolation, +} from '@src/types/onyx'; import type {Participant} from '@src/types/onyx/IOU'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import type {PolicyTaxRate, PolicyTaxRates} from '@src/types/onyx/PolicyTaxRates'; @@ -767,8 +781,8 @@ function getEnabledCategoriesCount(options: PolicyCategories): number { /** * Verifies that there is at least one enabled option */ -function hasEnabledOptions(options: PolicyCategories): boolean { - return Object.values(options).some((option) => option.enabled); +function hasEnabledOptions(options: PolicyCategory[] | PolicyTag[]): boolean { + return options.some((option) => option.enabled); } /** From eb4631e30198957d678326b9dd0f37d7156a442e Mon Sep 17 00:00:00 2001 From: VickyStash Date: Fri, 26 Jan 2024 15:00:17 +0100 Subject: [PATCH 12/15] TS fixes after merging main --- .../report/ContextMenu/PopoverReportActionContextMenu.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx index b28374fae04a..f37eabaa7c1d 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -2,7 +2,7 @@ import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; /* eslint-disable no-restricted-imports */ -import type {EmitterSubscription, GestureResponderEvent, NativeTouchEvent, Text as RNText, View} from 'react-native'; +import type {EmitterSubscription, GestureResponderEvent, NativeTouchEvent, View} from 'react-native'; import {Dimensions} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import ConfirmModal from '@components/ConfirmModal'; @@ -16,7 +16,7 @@ import CONST from '@src/CONST'; import type {ReportAction} from '@src/types/onyx'; import BaseReportActionContextMenu from './BaseReportActionContextMenu'; import type {ContextMenuAction} from './ContextMenuActions'; -import type {ContextMenuType, ReportActionContextMenu} from './ReportActionContextMenu'; +import type {ContextMenuAnchor, ContextMenuType, ReportActionContextMenu} from './ReportActionContextMenu'; type Location = { x: number; @@ -67,7 +67,7 @@ function PopoverReportActionContextMenu(_props: never, ref: ForwardedRef(null); const anchorRef = useRef(null); const dimensionsEventListener = useRef(null); - const contextMenuAnchorRef = useRef(null); + const contextMenuAnchorRef = useRef(null); const contextMenuTargetNode = useRef(null); const onPopoverShow = useRef(() => {}); From 15cffe2c5e3d97036d855bbd8237a092cd3b29f5 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 30 Jan 2024 11:41:30 +0100 Subject: [PATCH 13/15] Fix Onyx types import --- src/components/ReportActionItem/MoneyRequestAction.tsx | 2 +- src/components/ReportActionItem/MoneyRequestPreview.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index 51c814c1b2c6..ff29bf5b0ee8 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -1,7 +1,7 @@ import React from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx/lib/types'; +import type {OnyxEntry} from 'react-native-onyx'; import RenderHTML from '@components/RenderHTML'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; diff --git a/src/components/ReportActionItem/MoneyRequestPreview.tsx b/src/components/ReportActionItem/MoneyRequestPreview.tsx index c17c5ecd4129..bde8063690b6 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview.tsx @@ -5,7 +5,7 @@ import React from 'react'; import {View} from 'react-native'; import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx/lib/types'; +import type {OnyxEntry} from 'react-native-onyx'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import MoneyRequestSkeletonView from '@components/MoneyRequestSkeletonView'; From e7ff2176847948ce3c0a5d5121393c08bbabd5e0 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 31 Jan 2024 12:39:06 +0100 Subject: [PATCH 14/15] Fix TS issues after merging main --- src/components/DotIndicatorMessage.tsx | 5 ++--- src/components/MessagesRow.tsx | 3 ++- src/components/OfflineWithFeedback.tsx | 5 +++-- src/components/ReportActionItem/MoneyRequestView.tsx | 6 +++++- src/libs/Localize/index.ts | 6 +++++- src/libs/ReceiptUtils.ts | 2 +- src/types/onyx/Transaction.ts | 2 +- 7 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/components/DotIndicatorMessage.tsx b/src/components/DotIndicatorMessage.tsx index d18704fdfb05..d2143f5b48da 100644 --- a/src/components/DotIndicatorMessage.tsx +++ b/src/components/DotIndicatorMessage.tsx @@ -8,13 +8,12 @@ import useThemeStyles from '@hooks/useThemeStyles'; import fileDownload from '@libs/fileDownload'; import * as Localize from '@libs/Localize'; import CONST from '@src/CONST'; +import type {ReceiptError} from '@src/types/onyx/Transaction'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import {PressableWithoutFeedback} from './Pressable'; import Text from './Text'; -type ReceiptError = {error?: string; source: string; filename: string}; - type DotIndicatorMessageProps = { /** * In most cases this should just be errors from onxyData @@ -23,7 +22,7 @@ type DotIndicatorMessageProps = { * timestamp: 'message', * } */ - messages: Record; + messages: Record; /** The type of message, 'error' shows a red dot, 'success' shows a green dot */ type: 'error' | 'success'; diff --git a/src/components/MessagesRow.tsx b/src/components/MessagesRow.tsx index cfec6fd292e9..7c764ec94fcd 100644 --- a/src/components/MessagesRow.tsx +++ b/src/components/MessagesRow.tsx @@ -6,6 +6,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import type * as Localize from '@libs/Localize'; import CONST from '@src/CONST'; +import type {ReceiptError} from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import DotIndicatorMessage from './DotIndicatorMessage'; import Icon from './Icon'; @@ -15,7 +16,7 @@ import Tooltip from './Tooltip'; type MessagesRowProps = { /** The messages to display */ - messages: Record; + messages: Record; /** The type of message, 'error' shows a red dot, 'success' shows a green dot */ type: 'error' | 'success'; diff --git a/src/components/OfflineWithFeedback.tsx b/src/components/OfflineWithFeedback.tsx index 1a8f313af267..2fad21fb54ef 100644 --- a/src/components/OfflineWithFeedback.tsx +++ b/src/components/OfflineWithFeedback.tsx @@ -8,6 +8,7 @@ import mapChildrenFlat from '@libs/mapChildrenFlat'; import shouldRenderOffscreen from '@libs/shouldRenderOffscreen'; import CONST from '@src/CONST'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; +import type {ReceiptError, ReceiptErrors} from '@src/types/onyx/Transaction'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import MessagesRow from './MessagesRow'; @@ -26,7 +27,7 @@ type OfflineWithFeedbackProps = ChildrenProps & { shouldHideOnDelete?: boolean; /** The errors to display */ - errors?: OnyxCommon.Errors | null; + errors?: OnyxCommon.Errors | ReceiptErrors | null; /** Whether we should show the error messages */ shouldShowErrorMessages?: boolean; @@ -84,7 +85,7 @@ function OfflineWithFeedback({ const hasErrors = !isEmptyObject(errors ?? {}); // Some errors have a null message. This is used to apply opacity only and to avoid showing redundant messages. - const errorMessages = omitBy(errors, (e) => e === null); + const errorMessages = omitBy(errors, (e: string | ReceiptError) => e === null); const hasErrorMessages = !isEmptyObject(errorMessages); const isOfflinePendingAction = !!isOffline && !!pendingAction; const isUpdateOrDeleteError = hasErrors && (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 6e814811f858..ee727ad81518 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -205,9 +205,13 @@ function MoneyRequestView({ {hasReceipt && ( { + if (!transaction?.transactionID) { + return; + } + Transaction.clearError(transaction.transactionID); }} > diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index bc40f93dd13b..46ca550eaa1a 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -7,6 +7,7 @@ import CONST from '@src/CONST'; import translations from '@src/languages/translations'; import type {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {ReceiptError} from '@src/types/onyx/Transaction'; import LocaleListener from './LocaleListener'; import BaseLocaleListener from './LocaleListener/BaseLocaleListener'; @@ -102,7 +103,10 @@ type MaybePhraseKey = string | [string, Record & {isTranslated? /** * Return translated string for given error. */ -function translateIfPhraseKey(message: MaybePhraseKey): string { +function translateIfPhraseKey(message: MaybePhraseKey): string; +function translateIfPhraseKey(message: ReceiptError): ReceiptError; +function translateIfPhraseKey(message: MaybePhraseKey | ReceiptError): string | ReceiptError; +function translateIfPhraseKey(message: MaybePhraseKey | ReceiptError): string | ReceiptError { if (!message || (Array.isArray(message) && message.length === 0)) { return ''; } diff --git a/src/libs/ReceiptUtils.ts b/src/libs/ReceiptUtils.ts index cbffb5b454b3..444b9b0f3954 100644 --- a/src/libs/ReceiptUtils.ts +++ b/src/libs/ReceiptUtils.ts @@ -37,7 +37,7 @@ function getThumbnailAndImageURIs(transaction: OnyxEntry, receiptPa } // URI to image, i.e. blob:new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg // If there're errors, we need to display them in preview. We can store many files in errors, but we just need to get the last one - const errors = _.findLast(transaction.errors) as ReceiptError | undefined; + const errors = _.findLast(transaction?.errors) as ReceiptError | undefined; const path = errors?.source ?? transaction?.receipt?.source ?? receiptPath ?? ''; // filename of uploaded image or last part of remote URI const filename = errors?.filename ?? transaction?.filename ?? receiptFileName ?? ''; diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 8f2b9d03dc9e..fe7ca7436a81 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -103,4 +103,4 @@ type Transaction = { }; export default Transaction; -export type {WaypointCollection, Comment, Receipt, Waypoint, ReceiptError, TransactionPendingFieldsKey}; +export type {WaypointCollection, Comment, Receipt, Waypoint, ReceiptError, ReceiptErrors, TransactionPendingFieldsKey}; From d3f64c9c932c5ae6fdcb06ffd8a2722c4b67d28a Mon Sep 17 00:00:00 2001 From: VickyStash Date: Fri, 2 Feb 2024 21:18:16 +0100 Subject: [PATCH 15/15] Retry performance test