From 229a4331aeddc934748757b76b226361f93ce6dc Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Thu, 2 Nov 2023 21:37:02 -0400 Subject: [PATCH 01/69] update subsribe on leave --- src/libs/actions/Report.js | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 1de15c1184cb..5ddb2355f1c9 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2059,6 +2059,32 @@ function leaveRoom(reportID, isWorkspaceMemberLeavingWorkspaceRoom = false) { }, ]; + const failureData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: report, + }, + ]; + + if (report.parentReportID && report.parentReportActionID) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, + value: {[report.parentReportActionID]: {childReportNotificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}}, + }); + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, + value: {[report.parentReportActionID]: {childReportNotificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}}, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, + value: {[parentReportActionID]: {childReportNotificationPreference: report.notificationPreference}}, + }); + } + API.write( 'LeaveRoom', { @@ -2067,13 +2093,7 @@ function leaveRoom(reportID, isWorkspaceMemberLeavingWorkspaceRoom = false) { { optimisticData, successData, - failureData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: report, - }, - ], + failureData, }, ); From 1e060e2bc927673cb8988e9da5e97bb9c7a728f4 Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Thu, 9 Nov 2023 04:35:12 -0500 Subject: [PATCH 02/69] fix variable names --- src/libs/actions/Report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 27396c875d34..638c83862cfc 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2085,7 +2085,7 @@ function leaveRoom(reportID, isWorkspaceMemberLeavingWorkspaceRoom = false) { failureData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, - value: {[parentReportActionID]: {childReportNotificationPreference: report.notificationPreference}}, + value: {[report.parentReportActionID]: {childReportNotificationPreference: report.notificationPreference}}, }); } From 004a2225c4c600a8ce1eb69c1faf57b3bbda70e8 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Mon, 20 Nov 2023 13:02:41 +0100 Subject: [PATCH 03/69] Remove unnecessary --- src/libs/updatePropsPaperWorklet/index.js | 3 --- src/libs/updatePropsPaperWorklet/index.native.js | 13 ------------- .../ReportActionCompose/ReportActionCompose.js | 9 +++------ 3 files changed, 3 insertions(+), 22 deletions(-) delete mode 100644 src/libs/updatePropsPaperWorklet/index.js delete mode 100644 src/libs/updatePropsPaperWorklet/index.native.js diff --git a/src/libs/updatePropsPaperWorklet/index.js b/src/libs/updatePropsPaperWorklet/index.js deleted file mode 100644 index 1bca6ea13cdc..000000000000 --- a/src/libs/updatePropsPaperWorklet/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function () { - 'worklet'; -} diff --git a/src/libs/updatePropsPaperWorklet/index.native.js b/src/libs/updatePropsPaperWorklet/index.native.js deleted file mode 100644 index ed79b38ffab5..000000000000 --- a/src/libs/updatePropsPaperWorklet/index.native.js +++ /dev/null @@ -1,13 +0,0 @@ -export default function (viewTag, viewName, updates) { - 'worklet'; - - // _updatePropsPaper is a function that is worklet function from react-native-reanimated which is not available on web - // eslint-disable-next-line no-undef - _updatePropsPaper([ - { - tag: viewTag, - name: viewName, - updates, - }, - ]); -} diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js index 7bce37dc3826..adab1b007843 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import {runOnJS, useAnimatedRef} from 'react-native-reanimated'; +import {runOnJS, setNativeProps, useAnimatedRef} from 'react-native-reanimated'; import _ from 'underscore'; import AttachmentModal from '@components/AttachmentModal'; import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton'; @@ -22,7 +22,6 @@ import getDraftComment from '@libs/ComposerUtils/getDraftComment'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import getModalState from '@libs/getModalState'; import * as ReportUtils from '@libs/ReportUtils'; -import updatePropsPaperWorklet from '@libs/updatePropsPaperWorklet'; import willBlurTextInputOnTapOutsideFunc from '@libs/willBlurTextInputOnTapOutside'; import ParticipantLocalTime from '@pages/home/report/ParticipantLocalTime'; import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; @@ -332,13 +331,11 @@ function ReportActionCompose({ return; } - const viewTag = animatedRef(); - const viewName = 'RCTMultilineTextInputView'; - const updates = {text: ''}; // We are setting the isCommentEmpty flag to true so the status of it will be in sync of the native text input state runOnJS(setIsCommentEmpty)(true); runOnJS(resetFullComposerSize)(); - updatePropsPaperWorklet(viewTag, viewName, updates); // clears native text input on the UI thread + setNativeProps(animatedRef, {text: ''}); + // updatePropsPaperWorklet(viewTag, viewName, updates); // clears native text input on the UI thread runOnJS(submitForm)(); }, [isSendDisabled, resetFullComposerSize, submitForm, animatedRef, isReportReadyForDisplay]); From 6d5966f43a61ce45111e68a0ca6843401b679d96 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Thu, 23 Nov 2023 13:05:38 +0100 Subject: [PATCH 04/69] remove unnecessary commented code --- .../home/report/ReportActionCompose/ReportActionCompose.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js index adab1b007843..670ee07a5bcb 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js @@ -334,8 +334,7 @@ function ReportActionCompose({ // We are setting the isCommentEmpty flag to true so the status of it will be in sync of the native text input state runOnJS(setIsCommentEmpty)(true); runOnJS(resetFullComposerSize)(); - setNativeProps(animatedRef, {text: ''}); - // updatePropsPaperWorklet(viewTag, viewName, updates); // clears native text input on the UI thread + setNativeProps(animatedRef, {text: ''}); // clears native text input on the UI thread runOnJS(submitForm)(); }, [isSendDisabled, resetFullComposerSize, submitForm, animatedRef, isReportReadyForDisplay]); From d830b403575611eee443c265e801452eee1bef03 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Mon, 18 Dec 2023 13:41:13 +0100 Subject: [PATCH 05/69] rename --- src/components/AmountTextInput.js | 2 +- src/components/TextInput/BaseTextInput/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AmountTextInput.js b/src/components/AmountTextInput.js index 25e1ce6f05ec..e174ad27e7c3 100644 --- a/src/components/AmountTextInput.js +++ b/src/components/AmountTextInput.js @@ -67,7 +67,7 @@ function AmountTextInput(props) { onSelectionChange={props.onSelectionChange} role={CONST.ROLE.PRESENTATION} onKeyPress={props.onKeyPress} - containerStyles={[...StyleUtils.parseStyleAsArray(props.containerStyles)]} + testInputWrapper={[...StyleUtils.parseStyleAsArray(props.containerStyles)]} /> ); } diff --git a/src/components/TextInput/BaseTextInput/index.js b/src/components/TextInput/BaseTextInput/index.js index 67776d6c7e91..a9b9876b2f1d 100644 --- a/src/components/TextInput/BaseTextInput/index.js +++ b/src/components/TextInput/BaseTextInput/index.js @@ -262,7 +262,7 @@ function BaseTextInput(props) { style={[ props.autoGrowHeight && styles.autoGrowHeightInputContainer(textInputHeight, variables.componentSizeLarge, maxHeight), !isMultiline && styles.componentHeightLarge, - ...props.containerStyles, + props.testInputWrapper, ]} > Date: Thu, 21 Dec 2023 15:43:24 -0500 Subject: [PATCH 06/69] optimistic data for join room --- src/pages/home/HeaderView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 2182cc5f6359..5c0be79d1d85 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -147,7 +147,7 @@ function HeaderView(props) { icon: Expensicons.ChatBubbles, text: translate('common.join'), onSelected: Session.checkIfActionIsAllowed(() => - Report.updateNotificationPreference(props.report.reportID, props.report.notificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false), + Report.updateNotificationPreference(props.report.reportID, props.report.notificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false, props.report.parentReportID, parentReportAction), ), }); } else if ((isChatThread && props.report.notificationPreference.length) || isUserCreatedPolicyRoom || canLeaveRoom) { From ba930e4be7da76fa339d2ca4a60e5806ad71d849 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Mon, 25 Dec 2023 02:05:43 +0100 Subject: [PATCH 07/69] Add support for editing approved reports --- src/components/AttachmentModal.js | 13 ++-- .../ReportActionItem/MoneyRequestView.js | 33 ++++----- .../ReportActionItem/ReportActionItemImage.js | 7 +- src/libs/ReportUtils.ts | 74 +++++++++++++------ src/pages/EditRequestPage.js | 13 +--- 5 files changed, 81 insertions(+), 59 deletions(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 863e59aa4474..7e9e828e4780 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -19,7 +19,6 @@ import fileDownload from '@libs/fileDownload'; import * as FileUtils from '@libs/fileDownload/FileUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import useNativeDriver from '@libs/useNativeDriver'; import reportPropTypes from '@pages/reportPropTypes'; @@ -95,6 +94,9 @@ const propTypes = { /** Whether it is a receipt attachment or not */ isReceiptAttachment: PropTypes.bool, + + /** Whether the receipt can be replaced */ + canEditReceipt: PropTypes.bool, }; const defaultProps = { @@ -113,6 +115,7 @@ const defaultProps = { onCarouselAttachmentChange: () => {}, isWorkspaceAvatar: false, isReceiptAttachment: false, + canEditReceipt: false, }; function AttachmentModal(props) { @@ -365,12 +368,8 @@ function AttachmentModal(props) { return []; } const menuItems = []; - const parentReportAction = props.parentReportActions[props.report.parentReportActionID]; - const canEdit = - ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, props.parentReport.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT) && - !TransactionUtils.isDistanceRequest(props.transaction); - if (canEdit) { + if (props.canEditReceipt) { menuItems.push({ icon: Expensicons.Camera, text: props.translate('common.replace'), @@ -385,7 +384,7 @@ function AttachmentModal(props) { text: props.translate('common.download'), onSelected: () => downloadAttachment(source), }); - if (TransactionUtils.hasReceipt(props.transaction) && !TransactionUtils.isReceiptBeingScanned(props.transaction) && canEdit) { + if (TransactionUtils.hasReceipt(props.transaction) && !TransactionUtils.isReceiptBeingScanned(props.transaction) && props.canEditReceipt) { menuItems.push({ icon: Expensicons.Trashcan, text: props.translate('receipt.deleteReceipt'), diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 47031bfc164c..1e9873e1cf5b 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -28,7 +28,6 @@ import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; 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 AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground'; @@ -115,9 +114,14 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate // Flags for allowing or disallowing editing a money request const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const isCancelled = moneyRequestReport && moneyRequestReport.isCancelledIOU; + const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction); - const canEditAmount = canEdit && !isSettled && !isCardTransaction; - const canEditReceipt = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, moneyRequestReport.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT); + const canEditAmount = useMemo(() => ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.AMOUNT), [transaction, parentReportAction]); + console.log('can edit amount', canEditAmount); + const canEditMerchant = useMemo(() => ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.MERCHANT), [transaction, parentReportAction]); + const canEditDate = useMemo(() => ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.DATE), [transaction, parentReportAction]); + const canEditReceipt = useMemo(() => ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.RECEIPT), [transaction, parentReportAction]); + const canEditDistance = useMemo(() => ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.DISTANCE), [transaction, parentReportAction]); // A flag for verifying that the current report is a sub-report of a workspace chat const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]); @@ -158,18 +162,12 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate } } - // A temporary solution to hide the transaction detail - // This will be removed after we properly add the transaction as a prop - if (ReportActionsUtils.isDeletedAction(parentReportAction)) { - return null; - } - const hasReceipt = TransactionUtils.hasReceipt(transaction); let receiptURIs; let hasErrors = false; if (hasReceipt) { receiptURIs = ReceiptUtils.getThumbnailAndImageURIs(transaction); - hasErrors = canEdit && TransactionUtils.hasMissingSmartscanFields(transaction); + hasErrors = canEditReceipt && TransactionUtils.hasMissingSmartscanFields(transaction); } const pendingAction = lodashGet(transaction, 'pendingAction'); @@ -188,11 +186,12 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate isLocalFile={receiptURIs.isLocalFile} transaction={transaction} enablePreviewModal + canEditReceipt={canEditReceipt} /> )} - {!hasReceipt && canEditReceipt && !isSettled && canUseViolations && ( + {!hasReceipt && canEditReceipt && canUseViolations && ( Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT))} @@ -230,8 +229,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} /> @@ -241,8 +240,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.MERCHANT))} brickRoadIndicator={hasErrors && isEmptyMerchant ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} @@ -254,8 +253,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DATE))} brickRoadIndicator={hasErrors && transactionDate === '' ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} diff --git a/src/components/ReportActionItem/ReportActionItemImage.js b/src/components/ReportActionItem/ReportActionItemImage.js index 2c5ef22b1b8e..4ea4de6cb0ef 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.js +++ b/src/components/ReportActionItem/ReportActionItemImage.js @@ -30,6 +30,9 @@ const propTypes = { /** whether thumbnail is refer the local file or not */ isLocalFile: PropTypes.bool, + + /** whether the receipt can be replaced */ + canEditReceipt: PropTypes.bool, }; const defaultProps = { @@ -37,6 +40,7 @@ const defaultProps = { transaction: {}, enablePreviewModal: false, isLocalFile: false, + canEditReceipt: false, }; /** @@ -45,7 +49,7 @@ const defaultProps = { * and optional preview modal as well. */ -function ReportActionItemImage({thumbnail, image, enablePreviewModal, transaction, isLocalFile}) { +function ReportActionItemImage({thumbnail, image, enablePreviewModal, transaction, canEditReceipt, isLocalFile}) { const styles = useThemeStyles(); const {translate} = useLocalize(); const imageSource = tryResolveUrlFromApiRoot(image || ''); @@ -87,6 +91,7 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal, transactio isAuthTokenRequired={!isLocalFile} report={report} isReceiptAttachment + canEditReceipt={canEditReceipt} allowToDownload originalFileName={transaction.filename} > diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8e0ff19bf3dc..325bc749ecb9 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -486,7 +486,8 @@ function isExpenseReport(report: OnyxEntry | EmptyObject): boolean { /** * Checks if a report is an IOU report. */ -function isIOUReport(report: OnyxEntry): boolean { +function isIOUReport(reportOrID: OnyxEntry | string | EmptyObject): boolean { + const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null : reportOrID; return report?.type === CONST.REPORT.TYPE.IOU; } @@ -542,7 +543,8 @@ function isReportManager(report: OnyxEntry): boolean { /** * Checks if the supplied report has been approved */ -function isReportApproved(report: OnyxEntry | EmptyObject): boolean { +function isReportApproved(reportOrID: OnyxEntry | string | EmptyObject): boolean { + const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null : reportOrID; return report?.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && report?.statusNum === CONST.REPORT.STATUS.APPROVED; } @@ -759,10 +761,17 @@ function isConciergeChatReport(report: OnyxEntry): boolean { /** * Returns true if report is still being processed */ -function isProcessingReport(report: OnyxEntry): boolean { +function isProcessingReport(report: OnyxEntry | EmptyObject): boolean { return report?.stateNum === CONST.REPORT.STATE_NUM.PROCESSING && report?.statusNum === CONST.REPORT.STATUS.SUBMITTED; } +/** + * Returns true if report is still open + */ +function isOpenReport(report: OnyxEntry | EmptyObject): boolean { + return report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS.OPEN; +} + /** * Check if the report is a single chat report that isn't a thread * and personal detail of participant is optimistic data @@ -1806,8 +1815,11 @@ function getTransactionDetails(transaction: OnyxEntry, createdDateF * - in case of expense report * - the current user is the requestor and is not settled yet * - or the user is an admin on the policy the expense report is tied to + * + * This is used in conjuction with canEditRestrictedField to control editing of specific fields like amount, currency, created, and receipt. + * On its own, it only controls allowing/disallowing navigating to the editing pages or showing/hiding the 'Edit' icon on report actions */ -function canEditMoneyRequest(reportAction: OnyxEntry, fieldToEdit = ''): boolean { +function canEditMoneyRequest(reportAction: OnyxEntry): boolean { const isDeleted = ReportActionsUtils.isDeletedAction(reportAction); if (isDeleted) { @@ -1830,44 +1842,58 @@ function canEditMoneyRequest(reportAction: OnyxEntry, fieldToEdit } const moneyRequestReport = getReport(String(moneyRequestReportID)); - const isReportSettled = isSettled(moneyRequestReport?.reportID); - const isApproved = isReportApproved(moneyRequestReport); - const isAdmin = isExpenseReport(moneyRequestReport) && (getPolicy(moneyRequestReport?.policyID ?? '')?.role ?? '') === CONST.POLICY.ROLE.ADMIN; + const policy = getPolicy(moneyRequestReport?.policyID ?? ''); + const isAdmin = isExpenseReport(moneyRequestReport) && policy.role === CONST.POLICY.ROLE.ADMIN; + const isManager = isExpenseReport(moneyRequestReport) && currentUserAccountID === moneyRequestReport?.managerID; const isRequestor = currentUserAccountID === reportAction?.actorAccountID; - if (isAdmin && !isRequestor && fieldToEdit === CONST.EDIT_REQUEST_FIELD.RECEIPT) { - return false; + // Admin & managers can always allow coding fields such as tag, category, billable, etc. As long as the report has a state higher than OPEN. + if ((isAdmin || isManager) && !isOpenReport(moneyRequestReport)) { + return true; } - if (isAdmin) { - return true; + // We allow editing of PROCESSING reports in Free policies, but not in paid policie (yet) + if (policy.type === CONST.POLICY.TYPE.FREE) { + return isRequestor && isProcessingReport(moneyRequestReport); } - return !isApproved && !isReportSettled && isRequestor; + return !isReportApproved(moneyRequestReport) && !isSettled(moneyRequestReport?.reportID) && isRequestor; } -/** - * Checks if the current user can edit the provided property of a money request - * - */ -function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, reportID: string, fieldToEdit: ValueOf): boolean { +function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, transaction: Transaction, fieldToEdit: ValueOf): boolean { // A list of fields that cannot be edited by anyone, once a money request has been settled - const nonEditableFieldsWhenSettled: string[] = [ + const restrictedFields: string[] = [ CONST.EDIT_REQUEST_FIELD.AMOUNT, CONST.EDIT_REQUEST_FIELD.CURRENCY, + CONST.EDIT_REQUEST_FIELD.MERCHANT, CONST.EDIT_REQUEST_FIELD.DATE, CONST.EDIT_REQUEST_FIELD.RECEIPT, CONST.EDIT_REQUEST_FIELD.DISTANCE, ]; - // Checks if this user has permissions to edit this money request - if (!canEditMoneyRequest(reportAction, fieldToEdit)) { - return false; // User doesn't have permission to edit + if (!canEditMoneyRequest(reportAction)) { + return false; + } + + if (!restrictedFields.includes(fieldToEdit)) { + return true; + } + + const reportID = reportAction?.originalMessage?.IOUReportID ?? 0; + if (isSettled(reportID) || isReportApproved(reportID)) { + return false; + } + + if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.AMOUNT || fieldToEdit === CONST.EDIT_REQUEST_FIELD.CURRENCY) { + return !TransactionUtils.isCardTransaction(transaction); } - // Checks if the report is settled - // Checks if the provided property is a restricted one - return !isSettled(reportID) || !nonEditableFieldsWhenSettled.includes(fieldToEdit); + if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.RECEIPT) { + const isRequestor = currentUserAccountID === reportAction?.actorAccountID; + return !TransactionUtils.isDistanceRequest(transaction) && isRequestor; + } + + return true; } /** diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 2bdf3d19f16a..c257b21adb9e 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -47,9 +47,6 @@ const propTypes = { /** The report object for the thread report */ report: reportPropTypes, - /** The parent report object for the thread report */ - parentReport: reportPropTypes, - /** Collection of categories attached to a policy */ policyCategories: PropTypes.objectOf(categoryPropTypes), @@ -65,14 +62,13 @@ const propTypes = { const defaultProps = { report: {}, - parentReport: {}, policyCategories: {}, policyTags: {}, parentReportActions: {}, transaction: {}, }; -function EditRequestPage({report, route, parentReport, policyCategories, policyTags, parentReportActions, transaction}) { +function EditRequestPage({report, route, policyCategories, policyTags, parentReportActions, transaction}) { const parentReportActionID = lodashGet(report, 'parentReportActionID', '0'); const parentReportAction = lodashGet(parentReportActions, parentReportActionID, {}); const { @@ -104,7 +100,7 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT // Decides whether to allow or disallow editing a money request useEffect(() => { // Do not dismiss the modal, when a current user can edit this property of the money request. - if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, parentReport.reportID, fieldToEdit)) { + if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, fieldToEdit)) { return; } @@ -112,7 +108,7 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT Navigation.isNavigationReady().then(() => { Navigation.dismissModal(); }); - }, [parentReportAction, parentReport.reportID, fieldToEdit]); + }, [parentReportAction, transaction, fieldToEdit]); // Update the transaction object and close the modal function editMoneyRequest(transactionChanges) { @@ -284,9 +280,6 @@ export default compose( policyTags: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, }, - parentReport: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report ? report.parentReportID : '0'}`, - }, parentReportActions: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`, canEvict: false, From bcb8dc405362a16fbdd2841033ce5ef777dd04bd Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Mon, 25 Dec 2023 02:57:39 +0100 Subject: [PATCH 08/69] Cleanup --- src/components/ReportActionItem/MoneyRequestView.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 1e9873e1cf5b..dffe6746b63d 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -117,7 +117,6 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction); const canEditAmount = useMemo(() => ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.AMOUNT), [transaction, parentReportAction]); - console.log('can edit amount', canEditAmount); const canEditMerchant = useMemo(() => ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.MERCHANT), [transaction, parentReportAction]); const canEditDate = useMemo(() => ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.DATE), [transaction, parentReportAction]); const canEditReceipt = useMemo(() => ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.RECEIPT), [transaction, parentReportAction]); From c41b554fb17d25e5695b69db9c9553b0127a2a5a Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Mon, 25 Dec 2023 03:29:49 +0100 Subject: [PATCH 09/69] Fix bugs --- src/components/ReportActionItem/MoneyRequestView.js | 2 +- src/libs/ReportUtils.ts | 2 +- src/pages/EditRequestPage.js | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index dffe6746b63d..dbd95c268678 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -123,7 +123,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const canEditDistance = useMemo(() => ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.DISTANCE), [transaction, parentReportAction]); // A flag for verifying that the current report is a sub-report of a workspace chat - const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]); + const isPolicyExpenseChat = policy && policy.type !== CONST.POLICY.TYPE.PERSONAL; // Fetches only the first tag, for now const policyTag = PolicyUtils.getTag(policyTags); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 325bc749ecb9..a86adfb4f500 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1880,7 +1880,7 @@ function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, trans } const reportID = reportAction?.originalMessage?.IOUReportID ?? 0; - if (isSettled(reportID) || isReportApproved(reportID)) { + if (isSettled(String(reportID)) || isReportApproved(String(reportID))) { return false; } diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index c257b21adb9e..978ec93546de 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -89,7 +89,8 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep const tagListName = PolicyUtils.getTagListName(policyTags); // A flag for verifying that the current report is a sub-report of a workspace chat - const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]); + const policy = ReportUtils.getPolicy(report.policyID || ''); + const isPolicyExpenseChat = policy && policy.type !== CONST.POLICY.TYPE.PERSONAL; // A flag for showing the categories page const shouldShowCategories = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); From 0459190cd7582cc8fc1b049877bb955b39bdcf4c Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Tue, 26 Dec 2023 23:36:42 +0100 Subject: [PATCH 10/69] Lint --- src/components/ReportActionItem/MoneyRequestView.js | 2 +- src/pages/EditRequestPage.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index dbd95c268678..db1e302958d9 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -85,7 +85,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); const {canUseViolations} = usePermissions(); - const parentReportAction = parentReportActions[report.parentReportActionID] || {}; + const parentReportAction = useMemo(() => parentReportActions[report.parentReportActionID] || {}, [parentReportActions, report.parentReportActionID]); const moneyRequestReport = parentReport; const { created: transactionDate, diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 978ec93546de..19edc74f290f 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -1,7 +1,7 @@ import lodashGet from 'lodash/get'; import lodashValues from 'lodash/values'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo} from 'react'; +import React, {useCallback, useEffect} from 'react'; import {withOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import categoryPropTypes from '@components/categoryPropTypes'; From 37e0afad6a2fb7011f52cc30a45af13a2e940846 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 27 Dec 2023 14:04:49 +0700 Subject: [PATCH 11/69] fix: prevent double click outside RHP --- .../Navigators/RightModalNavigator.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index bd790589c8d1..6a449aa2e3e9 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -1,5 +1,5 @@ import {createStackNavigator, StackScreenProps} from '@react-navigation/stack'; -import React, {useMemo} from 'react'; +import React, {useMemo, useRef} from 'react'; import {View} from 'react-native'; import NoDropZone from '@components/DragAndDrop/NoDropZone'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -19,10 +19,21 @@ function RightModalNavigator({navigation}: RightModalNavigatorProps) { const styles = useThemeStyles(); const {isSmallScreenWidth} = useWindowDimensions(); const screenOptions = useMemo(() => RHPScreenOptions(styles), [styles]); + const isExecutingRef = useRef(false); return ( - {!isSmallScreenWidth && } + {!isSmallScreenWidth && ( + { + if (isExecutingRef.current) { + return; + } + isExecutingRef.current = true; + navigation.goBack(); + }} + /> + )} Date: Wed, 27 Dec 2023 17:03:34 +0100 Subject: [PATCH 12/69] [TS migration] Migrate 'Section.js' component to TypeScript --- src/components/MenuItem.tsx | 2 +- src/components/MenuItemList.js | 62 --------- src/components/MenuItemList.tsx | 65 ++++++++++ src/components/Section/IconSection.js | 41 ------ src/components/Section/IconSection.tsx | 30 +++++ src/components/Section/index.js | 122 ------------------ src/components/Section/index.tsx | 104 +++++++++++++++ .../ContextMenu/ReportActionContextMenu.ts | 6 +- 8 files changed, 203 insertions(+), 229 deletions(-) delete mode 100644 src/components/MenuItemList.js create mode 100644 src/components/MenuItemList.tsx delete mode 100644 src/components/Section/IconSection.js create mode 100644 src/components/Section/IconSection.tsx delete mode 100644 src/components/Section/index.js create mode 100644 src/components/Section/index.tsx diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index db150d55f0d2..9e5d84166c0e 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -220,7 +220,7 @@ type MenuItemProps = (ResponsiveProps | UnresponsiveProps) & furtherDetails?: string; /** The function that should be called when this component is LongPressed or right-clicked. */ - onSecondaryInteraction: () => void; + onSecondaryInteraction?: (event: GestureResponderEvent | MouseEvent) => void; /** Array of objects that map display names to their corresponding tooltip */ titleWithTooltips: DisplayNameWithTooltip[]; diff --git a/src/components/MenuItemList.js b/src/components/MenuItemList.js deleted file mode 100644 index c9eee8e888e1..000000000000 --- a/src/components/MenuItemList.js +++ /dev/null @@ -1,62 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import _ from 'underscore'; -import useSingleExecution from '@hooks/useSingleExecution'; -import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; -import CONST from '@src/CONST'; -import MenuItem from './MenuItem'; -import menuItemPropTypes from './menuItemPropTypes'; - -const propTypes = { - /** An array of props that are pass to individual MenuItem components */ - menuItems: PropTypes.arrayOf(PropTypes.shape(menuItemPropTypes)), - - /** Whether or not to use the single execution hook */ - shouldUseSingleExecution: PropTypes.bool, -}; -const defaultProps = { - menuItems: [], - shouldUseSingleExecution: false, -}; - -function MenuItemList(props) { - let popoverAnchor; - const {isExecuting, singleExecution} = useSingleExecution(); - - /** - * Handle the secondary interaction for a menu item. - * - * @param {*} link the menu item link or function to get the link - * @param {Event} e the interaction event - */ - const secondaryInteraction = (link, e) => { - if (typeof link === 'function') { - link().then((url) => ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, e, url, popoverAnchor)); - } else if (!_.isEmpty(link)) { - ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, e, link, popoverAnchor); - } - }; - - return ( - <> - {_.map(props.menuItems, (menuItemProps) => ( - secondaryInteraction(menuItemProps.link, e) : undefined} - ref={(el) => (popoverAnchor = el)} - shouldBlockSelection={Boolean(menuItemProps.link)} - // eslint-disable-next-line react/jsx-props-no-spreading - {...menuItemProps} - disabled={menuItemProps.disabled || isExecuting} - onPress={props.shouldUseSingleExecution ? singleExecution(menuItemProps.onPress) : menuItemProps.onPress} - /> - ))} - - ); -} - -MenuItemList.displayName = 'MenuItemList'; -MenuItemList.propTypes = propTypes; -MenuItemList.defaultProps = defaultProps; - -export default MenuItemList; diff --git a/src/components/MenuItemList.tsx b/src/components/MenuItemList.tsx new file mode 100644 index 000000000000..2e1015d092ca --- /dev/null +++ b/src/components/MenuItemList.tsx @@ -0,0 +1,65 @@ +import React, {useRef} from 'react'; +import {GestureResponderEvent, View} from 'react-native'; +import useSingleExecution from '@hooks/useSingleExecution'; +import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; +import CONST from '@src/CONST'; +import MenuItem, {MenuItemProps} from './MenuItem'; + +type MenuItemWithLink = MenuItemProps & { + /** The link to open when the menu item is clicked */ + link: string | (() => Promise); +}; + +type MenuItemListProps = { + /** An array of props that are pass to individual MenuItem components */ + menuItems: MenuItemWithLink[]; + + /** Whether or not to use the single execution hook */ + shouldUseSingleExecution?: boolean; +}; + +function MenuItemList({menuItems = [], shouldUseSingleExecution = false}: MenuItemListProps) { + const popoverAnchor = useRef(null); + const {isExecuting, singleExecution} = useSingleExecution(); + + /** + * Handle the secondary interaction for a menu item. + * + * @param link the menu item link or function to get the link + * @param e the interaction event + */ + const secondaryInteraction = (link: MenuItemWithLink['link'], event: GestureResponderEvent | MouseEvent) => { + if (typeof link === 'function') { + link().then((url) => ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, event, url, popoverAnchor.current)); + } else if (link) { + ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, event, link, popoverAnchor.current); + } + }; + + return ( + <> + {menuItems.map((menuItemProps) => { + const onPress = menuItemProps.onPress ?? (() => {}); + + return ( + secondaryInteraction(menuItemProps.link, e) : undefined} + ref={popoverAnchor} + shouldBlockSelection={!!menuItemProps.link} + // eslint-disable-next-line react/jsx-props-no-spreading + {...menuItemProps} + disabled={menuItemProps.disabled ?? isExecuting} + onPress={shouldUseSingleExecution ? singleExecution(onPress) : onPress} + interactive + /> + ); + })} + + ); +} + +MenuItemList.displayName = 'MenuItemList'; + +export type {MenuItemWithLink}; +export default MenuItemList; diff --git a/src/components/Section/IconSection.js b/src/components/Section/IconSection.js deleted file mode 100644 index 307331aa36d6..000000000000 --- a/src/components/Section/IconSection.js +++ /dev/null @@ -1,41 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {View} from 'react-native'; -import Icon from '@components/Icon'; -import sourcePropTypes from '@components/Image/sourcePropTypes'; -import useThemeStyles from '@hooks/useThemeStyles'; - -const iconSectionPropTypes = { - icon: sourcePropTypes, - IconComponent: PropTypes.IconComponent, - iconContainerStyles: PropTypes.iconContainerStyles, -}; - -const defaultIconSectionPropTypes = { - icon: null, - IconComponent: null, - iconContainerStyles: [], -}; - -function IconSection({icon, IconComponent, iconContainerStyles}) { - const styles = useThemeStyles(); - - return ( - - {Boolean(icon) && ( - - )} - {Boolean(IconComponent) && } - - ); -} - -IconSection.displayName = 'IconSection'; -IconSection.propTypes = iconSectionPropTypes; -IconSection.defaultProps = defaultIconSectionPropTypes; - -export default IconSection; diff --git a/src/components/Section/IconSection.tsx b/src/components/Section/IconSection.tsx new file mode 100644 index 000000000000..ddadbcdb1d77 --- /dev/null +++ b/src/components/Section/IconSection.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import {StyleProp, View, ViewStyle} from 'react-native'; +import Icon from '@components/Icon'; +import useThemeStyles from '@hooks/useThemeStyles'; +import IconAsset from '@src/types/utils/IconAsset'; + +type IconSectionProps = { + icon?: IconAsset; + iconContainerStyles?: StyleProp; +}; + +function IconSection({icon, iconContainerStyles}: IconSectionProps) { + const styles = useThemeStyles(); + + return ( + + {!!icon && ( + + )} + + ); +} + +IconSection.displayName = 'IconSection'; + +export default IconSection; diff --git a/src/components/Section/index.js b/src/components/Section/index.js deleted file mode 100644 index 50576abef025..000000000000 --- a/src/components/Section/index.js +++ /dev/null @@ -1,122 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {View} from 'react-native'; -import sourcePropTypes from '@components/Image/sourcePropTypes'; -import MenuItemList from '@components/MenuItemList'; -import menuItemPropTypes from '@components/menuItemPropTypes'; -import Text from '@components/Text'; -import useThemeStyles from '@hooks/useThemeStyles'; -import IconSection from './IconSection'; - -const CARD_LAYOUT = { - ICON_ON_TOP: 'iconOnTop', - ICON_ON_RIGHT: 'iconOnRight', -}; - -const propTypes = { - /** An array of props that are pass to individual MenuItem components */ - menuItems: PropTypes.arrayOf(PropTypes.shape(menuItemPropTypes)), - - /** The text to display in the title of the section */ - title: PropTypes.string.isRequired, - - /** The text to display in the subtitle of the section */ - subtitle: PropTypes.string, - - /** The icon to display along with the title */ - icon: sourcePropTypes, - - /** Icon component */ - IconComponent: PropTypes.func, - - /** Card layout that affects icon positioning, margins, sizes. */ - // eslint-disable-next-line rulesdir/prefer-underscore-method - cardLayout: PropTypes.oneOf(Object.values(CARD_LAYOUT)), - - /** Contents to display inside the section */ - children: PropTypes.node, - - /** Customize the Section container */ - // eslint-disable-next-line react/forbid-prop-types - containerStyles: PropTypes.arrayOf(PropTypes.object), - - /** Customize the Section container */ - // eslint-disable-next-line react/forbid-prop-types - titleStyles: PropTypes.arrayOf(PropTypes.object), - - /** Customize the Section container */ - // eslint-disable-next-line react/forbid-prop-types - subtitleStyles: PropTypes.arrayOf(PropTypes.object), - - /** Whether the subtitle should have a muted style */ - subtitleMuted: PropTypes.bool, - - /** Customize the Section container */ - // eslint-disable-next-line react/forbid-prop-types - childrenStyles: PropTypes.arrayOf(PropTypes.object), - - /** Customize the Icon container */ - // eslint-disable-next-line react/forbid-prop-types - iconContainerStyles: PropTypes.arrayOf(PropTypes.object), -}; - -const defaultProps = { - menuItems: null, - children: null, - icon: null, - IconComponent: null, - cardLayout: CARD_LAYOUT.ICON_ON_RIGHT, - containerStyles: [], - iconContainerStyles: [], - titleStyles: [], - subtitleStyles: [], - subtitleMuted: false, - childrenStyles: [], - subtitle: null, -}; - -function Section({children, childrenStyles, containerStyles, icon, IconComponent, cardLayout, iconContainerStyles, menuItems, subtitle, subtitleStyles, subtitleMuted, title, titleStyles}) { - const styles = useThemeStyles(); - - return ( - <> - - {cardLayout === CARD_LAYOUT.ICON_ON_TOP && ( - - )} - - - {title} - - {cardLayout === CARD_LAYOUT.ICON_ON_RIGHT && ( - - )} - - - {Boolean(subtitle) && ( - - {subtitle} - - )} - - {children} - - {Boolean(menuItems) && } - - - ); -} -Section.displayName = 'Section'; -Section.propTypes = propTypes; -Section.defaultProps = defaultProps; - -export {CARD_LAYOUT}; -export default Section; diff --git a/src/components/Section/index.tsx b/src/components/Section/index.tsx new file mode 100644 index 000000000000..7f458d2ed8a5 --- /dev/null +++ b/src/components/Section/index.tsx @@ -0,0 +1,104 @@ +import React from 'react'; +import {StyleProp, View, ViewStyle} from 'react-native'; +import {ValueOf} from 'type-fest'; +import MenuItemList, {MenuItemWithLink} from '@components/MenuItemList'; +import Text from '@components/Text'; +import useThemeStyles from '@hooks/useThemeStyles'; +import ChildrenProps from '@src/types/utils/ChildrenProps'; +import IconAsset from '@src/types/utils/IconAsset'; +import IconSection from './IconSection'; + +const CARD_LAYOUT = { + ICON_ON_TOP: 'iconOnTop', + ICON_ON_RIGHT: 'iconOnRight', +} as const; + +type SectionProps = ChildrenProps & { + /** An array of props that are passed to individual MenuItem components */ + menuItems?: MenuItemWithLink[]; + + /** The text to display in the title of the section */ + title: string; + + /** The text to display in the subtitle of the section */ + subtitle?: string; + + /** The icon to display along with the title */ + icon?: IconAsset; + + /** Card layout that affects icon positioning, margins, sizes. */ + cardLayout?: ValueOf; + + /** Whether the subtitle should have a muted style */ + subtitleMuted?: boolean; + + /** Customize the Section container */ + containerStyles?: StyleProp; + + /** Customize the Section container */ + titleStyles?: StyleProp; + + /** Customize the Section container */ + subtitleStyles?: StyleProp; + + /** Customize the Section container */ + childrenStyles?: StyleProp; + + /** Customize the Icon container */ + iconContainerStyles?: StyleProp; +}; + +function Section({ + children, + childrenStyles, + containerStyles, + icon, + cardLayout = CARD_LAYOUT.ICON_ON_RIGHT, + iconContainerStyles, + menuItems, + subtitle, + subtitleStyles, + subtitleMuted = false, + title, + titleStyles, +}: SectionProps) { + const styles = useThemeStyles(); + + return ( + <> + + {cardLayout === CARD_LAYOUT.ICON_ON_TOP && ( + + )} + + + {title} + + {cardLayout === CARD_LAYOUT.ICON_ON_RIGHT && ( + + )} + + + {!!subtitle && ( + + {subtitle} + + )} + + {children} + + {!!menuItems && } + + + ); +} +Section.displayName = 'Section'; + +export {CARD_LAYOUT}; +export default Section; diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts index b269bc276b55..c7e712afc559 100644 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts @@ -1,5 +1,5 @@ import React from 'react'; -import {GestureResponderEvent, Text as RNText} from 'react-native'; +import {GestureResponderEvent, Text as RNText, View} from 'react-native'; import {OnyxEntry} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; @@ -17,7 +17,7 @@ type ShowContextMenu = ( type: ContextMenuType, event: GestureResponderEvent | MouseEvent, selection: string, - contextMenuAnchor: RNText | null, + contextMenuAnchor: View | RNText | null, reportID?: string, reportActionID?: string, originalReportID?: string, @@ -94,7 +94,7 @@ function showContextMenu( type: ContextMenuType, event: GestureResponderEvent | MouseEvent, selection: string, - contextMenuAnchor: RNText | null, + contextMenuAnchor: View | RNText | null, reportID = '0', reportActionID = '0', originalReportID = '0', From fe574012fb26f2d2be3567ea1a69b0cc79a9e02a Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 27 Dec 2023 18:07:23 +0100 Subject: [PATCH 13/69] Adjust MenuItem and MenuItemList --- package.json | 2 +- src/components/MenuItem.tsx | 241 +++++++++++++------------- src/components/MenuItemList.tsx | 39 ++--- src/components/Section/index.tsx | 2 +- src/hooks/useSingleExecution/index.ts | 4 +- 5 files changed, 138 insertions(+), 150 deletions(-) diff --git a/package.json b/package.json index df30c549c515..43b74ab0de55 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "private": true, "scripts": { "configure-mapbox": "scripts/setup-mapbox-sdk-walkthrough.sh", - "setupNewDotWebForEmulators": "scripts/setup-newdot-web-emulators.sh", + "setupNewDotWebForEmulators": "scripts/psetup-newdot-web-emulators.sh", "startAndroidEmulator": "scripts/start-android.sh", "postinstall": "scripts/postInstall.sh", "clean": "npx react-native clean-project-auto", diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index 9e5d84166c0e..9ac65e438a48 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -33,20 +33,6 @@ import RenderHTML from './RenderHTML'; import SelectCircle from './SelectCircle'; import Text from './Text'; -type ResponsiveProps = { - /** Function to fire when component is pressed */ - onPress: (event: GestureResponderEvent | KeyboardEvent) => void; - - interactive?: true; -}; - -type UnresponsiveProps = { - onPress?: undefined; - - /** Whether the menu item should be interactive at all */ - interactive: false; -}; - type IconProps = { /** Flag to choose between avatar image or an icon */ iconType: typeof CONST.ICON_TYPE_ICON; @@ -67,170 +53,175 @@ type NoIcon = { icon?: undefined; }; -type MenuItemProps = (ResponsiveProps | UnresponsiveProps) & - (IconProps | AvatarProps | NoIcon) & { - /** Text to be shown as badge near the right end. */ - badgeText?: string; +type MenuItemProps = (IconProps | AvatarProps | NoIcon) & { + /** Function to fire when component is pressed */ + onPress?: (event: GestureResponderEvent | KeyboardEvent) => void; - /** Used to apply offline styles to child text components */ - style?: ViewStyle; + /** Whether the menu item should be interactive at all */ + interactive?: boolean; - /** Any additional styles to apply */ - wrapperStyle?: StyleProp; + /** Text to be shown as badge near the right end. */ + badgeText?: string; - /** Any additional styles to apply on the outer element */ - containerStyle?: StyleProp; + /** Used to apply offline styles to child text components */ + style?: ViewStyle; - /** Used to apply styles specifically to the title */ - titleStyle?: ViewStyle; + /** Any additional styles to apply */ + wrapperStyle?: StyleProp; - /** Any adjustments to style when menu item is hovered or pressed */ - hoverAndPressStyle: StyleProp>; + /** Any additional styles to apply on the outer element */ + containerStyle?: StyleProp; - /** Additional styles to style the description text below the title */ - descriptionTextStyle?: StyleProp; + /** Used to apply styles specifically to the title */ + titleStyle?: ViewStyle; - /** The fill color to pass into the icon. */ - iconFill?: string; + /** Any adjustments to style when menu item is hovered or pressed */ + hoverAndPressStyle: StyleProp>; - /** Secondary icon to display on the left side of component, right of the icon */ - secondaryIcon?: IconAsset; + /** Additional styles to style the description text below the title */ + descriptionTextStyle?: StyleProp; - /** The fill color to pass into the secondary icon. */ - secondaryIconFill?: string; + /** The fill color to pass into the icon. */ + iconFill?: string; - /** Icon Width */ - iconWidth?: number; + /** Secondary icon to display on the left side of component, right of the icon */ + secondaryIcon?: IconAsset; - /** Icon Height */ - iconHeight?: number; + /** The fill color to pass into the secondary icon. */ + secondaryIconFill?: string; - /** Any additional styles to pass to the icon container. */ - iconStyles?: StyleProp; + /** Icon Width */ + iconWidth?: number; - /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */ - fallbackIcon?: IconAsset; + /** Icon Height */ + iconHeight?: number; - /** An icon to display under the main item */ - furtherDetailsIcon?: IconAsset; + /** Any additional styles to pass to the icon container. */ + iconStyles?: StyleProp; - /** Boolean whether to display the title right icon */ - shouldShowTitleIcon?: boolean; + /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */ + fallbackIcon?: IconAsset; - /** Icon to display at right side of title */ - titleIcon?: IconAsset; + /** An icon to display under the main item */ + furtherDetailsIcon?: IconAsset; - /** Boolean whether to display the right icon */ - shouldShowRightIcon?: boolean; + /** Boolean whether to display the title right icon */ + shouldShowTitleIcon?: boolean; - /** Overrides the icon for shouldShowRightIcon */ - iconRight?: IconAsset; + /** Icon to display at right side of title */ + titleIcon?: IconAsset; - /** Should render component on the right */ - shouldShowRightComponent?: boolean; + /** Boolean whether to display the right icon */ + shouldShowRightIcon?: boolean; - /** Component to be displayed on the right */ - rightComponent?: ReactNode; + /** Overrides the icon for shouldShowRightIcon */ + iconRight?: IconAsset; - /** A description text to show under the title */ - description?: string; + /** Should render component on the right */ + shouldShowRightComponent?: boolean; - /** Should the description be shown above the title (instead of the other way around) */ - shouldShowDescriptionOnTop?: boolean; + /** Component to be displayed on the right */ + rightComponent?: ReactNode; - /** Error to display below the title */ - error?: string; + /** A description text to show under the title */ + description?: string; - /** Error to display at the bottom of the component */ - errorText?: string; + /** Should the description be shown above the title (instead of the other way around) */ + shouldShowDescriptionOnTop?: boolean; - /** A boolean flag that gives the icon a green fill if true */ - success?: boolean; + /** Error to display below the title */ + error?: string; - /** Whether item is focused or active */ - focused?: boolean; + /** Error to display at the bottom of the component */ + errorText?: string; - /** Should we disable this menu item? */ - disabled?: boolean; + /** A boolean flag that gives the icon a green fill if true */ + success?: boolean; - /** Text that appears above the title */ - label?: string; + /** Whether item is focused or active */ + focused?: boolean; - /** Label to be displayed on the right */ - rightLabel?: string; + /** Should we disable this menu item? */ + disabled?: boolean; - /** Text to display for the item */ - title?: string; + /** Text that appears above the title */ + label?: string; - /** A right-aligned subtitle for this menu option */ - subtitle?: string | number; + /** Label to be displayed on the right */ + rightLabel?: string; - /** Should the title show with normal font weight (not bold) */ - shouldShowBasicTitle?: boolean; + /** Text to display for the item */ + title?: string; - /** Should we make this selectable with a checkbox */ - shouldShowSelectedState?: boolean; + /** A right-aligned subtitle for this menu option */ + subtitle?: string | number; - /** Whether this item is selected */ - isSelected?: boolean; + /** Should the title show with normal font weight (not bold) */ + shouldShowBasicTitle?: boolean; - /** Prop to identify if we should load avatars vertically instead of diagonally */ - shouldStackHorizontally: boolean; + /** Should we make this selectable with a checkbox */ + shouldShowSelectedState?: boolean; - /** Prop to represent the size of the avatar images to be shown */ - avatarSize?: (typeof CONST.AVATAR_SIZE)[keyof typeof CONST.AVATAR_SIZE]; + /** Whether this item is selected */ + isSelected?: boolean; - /** Avatars to show on the right of the menu item */ - floatRightAvatars?: IconType[]; + /** Prop to identify if we should load avatars vertically instead of diagonally */ + shouldStackHorizontally: boolean; - /** Prop to represent the size of the float right avatar images to be shown */ - floatRightAvatarSize?: ValueOf; + /** Prop to represent the size of the avatar images to be shown */ + avatarSize?: (typeof CONST.AVATAR_SIZE)[keyof typeof CONST.AVATAR_SIZE]; - /** Affects avatar size */ - viewMode?: ValueOf; + /** Avatars to show on the right of the menu item */ + floatRightAvatars?: IconType[]; - /** Used to truncate the text with an ellipsis after computing the text layout */ - numberOfLinesTitle?: number; + /** Prop to represent the size of the float right avatar images to be shown */ + floatRightAvatarSize?: ValueOf; - /** Whether we should use small avatar subscript sizing the for menu item */ - isSmallAvatarSubscriptMenu?: boolean; + /** Affects avatar size */ + viewMode?: ValueOf; - /** The type of brick road indicator to show. */ - brickRoadIndicator?: ValueOf; + /** Used to truncate the text with an ellipsis after computing the text layout */ + numberOfLinesTitle?: number; - /** Should render the content in HTML format */ - shouldRenderAsHTML?: boolean; + /** Whether we should use small avatar subscript sizing the for menu item */ + isSmallAvatarSubscriptMenu?: boolean; - /** Should we grey out the menu item when it is disabled? */ - shouldGreyOutWhenDisabled?: boolean; + /** The type of brick road indicator to show. */ + brickRoadIndicator?: ValueOf; - /** The action accept for anonymous user or not */ - isAnonymousAction?: boolean; + /** Should render the content in HTML format */ + shouldRenderAsHTML?: boolean; - /** Flag to indicate whether or not text selection should be disabled from long-pressing the menu item. */ - shouldBlockSelection?: boolean; + /** Should we grey out the menu item when it is disabled? */ + shouldGreyOutWhenDisabled?: boolean; - /** Whether should render title as HTML or as Text */ - shouldParseTitle?: false; + /** The action accept for anonymous user or not */ + isAnonymousAction?: boolean; - /** Should check anonymous user in onPress function */ - shouldCheckActionAllowedOnPress?: boolean; + /** Flag to indicate whether or not text selection should be disabled from long-pressing the menu item. */ + shouldBlockSelection?: boolean; - /** Text to display under the main item */ - furtherDetails?: string; + /** Whether should render title as HTML or as Text */ + shouldParseTitle?: false; - /** The function that should be called when this component is LongPressed or right-clicked. */ - onSecondaryInteraction?: (event: GestureResponderEvent | MouseEvent) => void; + /** Should check anonymous user in onPress function */ + shouldCheckActionAllowedOnPress?: boolean; - /** Array of objects that map display names to their corresponding tooltip */ - titleWithTooltips: DisplayNameWithTooltip[]; + /** Text to display under the main item */ + furtherDetails?: string; - /** Icon should be displayed in its own color */ - displayInDefaultIconColor?: boolean; + /** The function that should be called when this component is LongPressed or right-clicked. */ + onSecondaryInteraction?: (event: GestureResponderEvent | MouseEvent) => void; - /** Determines how the icon should be resized to fit its container */ - contentFit?: ImageContentFit; - }; + /** Array of objects that map display names to their corresponding tooltip */ + titleWithTooltips: DisplayNameWithTooltip[]; + + /** Icon should be displayed in its own color */ + displayInDefaultIconColor?: boolean; + + /** Determines how the icon should be resized to fit its container */ + contentFit?: ImageContentFit; +}; function MenuItem( { diff --git a/src/components/MenuItemList.tsx b/src/components/MenuItemList.tsx index 2e1015d092ca..82097309c580 100644 --- a/src/components/MenuItemList.tsx +++ b/src/components/MenuItemList.tsx @@ -5,9 +5,11 @@ import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportA import CONST from '@src/CONST'; import MenuItem, {MenuItemProps} from './MenuItem'; +type MenuItemLink = string | (() => Promise); + type MenuItemWithLink = MenuItemProps & { /** The link to open when the menu item is clicked */ - link: string | (() => Promise); + link: MenuItemLink; }; type MenuItemListProps = { @@ -19,16 +21,16 @@ type MenuItemListProps = { }; function MenuItemList({menuItems = [], shouldUseSingleExecution = false}: MenuItemListProps) { - const popoverAnchor = useRef(null); + const popoverAnchor = useRef(null); const {isExecuting, singleExecution} = useSingleExecution(); /** * Handle the secondary interaction for a menu item. * * @param link the menu item link or function to get the link - * @param e the interaction event + * @param event the interaction event */ - const secondaryInteraction = (link: MenuItemWithLink['link'], event: GestureResponderEvent | MouseEvent) => { + const secondaryInteraction = (link: MenuItemLink, event: GestureResponderEvent | MouseEvent) => { if (typeof link === 'function') { link().then((url) => ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, event, url, popoverAnchor.current)); } else if (link) { @@ -38,23 +40,18 @@ function MenuItemList({menuItems = [], shouldUseSingleExecution = false}: MenuIt return ( <> - {menuItems.map((menuItemProps) => { - const onPress = menuItemProps.onPress ?? (() => {}); - - return ( - secondaryInteraction(menuItemProps.link, e) : undefined} - ref={popoverAnchor} - shouldBlockSelection={!!menuItemProps.link} - // eslint-disable-next-line react/jsx-props-no-spreading - {...menuItemProps} - disabled={menuItemProps.disabled ?? isExecuting} - onPress={shouldUseSingleExecution ? singleExecution(onPress) : onPress} - interactive - /> - ); - })} + {menuItems.map((menuItemProps) => ( + secondaryInteraction(menuItemProps.link, e) : undefined} + ref={popoverAnchor} + shouldBlockSelection={!!menuItemProps.link} + // eslint-disable-next-line react/jsx-props-no-spreading + {...menuItemProps} + disabled={!!menuItemProps.disabled || isExecuting} + onPress={shouldUseSingleExecution ? singleExecution(menuItemProps.onPress) : menuItemProps.onPress} + /> + ))} ); } diff --git a/src/components/Section/index.tsx b/src/components/Section/index.tsx index 7f458d2ed8a5..47f7605d278b 100644 --- a/src/components/Section/index.tsx +++ b/src/components/Section/index.tsx @@ -26,7 +26,7 @@ type SectionProps = ChildrenProps & { /** The icon to display along with the title */ icon?: IconAsset; - /** Card layout that affects icon positioning, margins, sizes. */ + /** Card layout that affects icon positioning, margins, sizes */ cardLayout?: ValueOf; /** Whether the subtitle should have a muted style */ diff --git a/src/hooks/useSingleExecution/index.ts b/src/hooks/useSingleExecution/index.ts index c37087d27c5f..0522303d553a 100644 --- a/src/hooks/useSingleExecution/index.ts +++ b/src/hooks/useSingleExecution/index.ts @@ -9,9 +9,9 @@ type Action = (...params: T) => void | Promise; */ export default function useSingleExecution() { const singleExecution = useCallback( - (action: Action) => + (action?: Action) => (...params: T) => { - action(...params); + action?.(...params); }, [], ); From 2a50e51d96ad58f2894d0a0345c191fe1492d391 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 27 Dec 2023 18:07:54 +0100 Subject: [PATCH 14/69] Fix package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 43b74ab0de55..df30c549c515 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "private": true, "scripts": { "configure-mapbox": "scripts/setup-mapbox-sdk-walkthrough.sh", - "setupNewDotWebForEmulators": "scripts/psetup-newdot-web-emulators.sh", + "setupNewDotWebForEmulators": "scripts/setup-newdot-web-emulators.sh", "startAndroidEmulator": "scripts/start-android.sh", "postinstall": "scripts/postInstall.sh", "clean": "npx react-native clean-project-auto", From 1591bddaf52952f69151cd4b63fae418c05c140f Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Fri, 29 Dec 2023 13:39:53 +0100 Subject: [PATCH 15/69] [TS migration] Migrate 'HeaderPageLayout.js' component to TypeScript --- ...aderPageLayout.js => HeaderPageLayout.tsx} | 77 +++++++++---------- 1 file changed, 36 insertions(+), 41 deletions(-) rename src/components/{HeaderPageLayout.js => HeaderPageLayout.tsx} (61%) diff --git a/src/components/HeaderPageLayout.js b/src/components/HeaderPageLayout.tsx similarity index 61% rename from src/components/HeaderPageLayout.js rename to src/components/HeaderPageLayout.tsx index 9ef5d4f83a06..bdc949275dd3 100644 --- a/src/components/HeaderPageLayout.js +++ b/src/components/HeaderPageLayout.tsx @@ -1,56 +1,51 @@ -import PropTypes from 'prop-types'; import React, {useMemo} from 'react'; -import {ScrollView, View} from 'react-native'; -import _ from 'underscore'; +import {ScrollView, StyleProp, View, ViewStyle} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as Browser from '@libs/Browser'; +import ChildrenProps from '@src/types/utils/ChildrenProps'; import FixedFooter from './FixedFooter'; import HeaderWithBackButton from './HeaderWithBackButton'; -import headerWithBackButtonPropTypes from './HeaderWithBackButton/headerWithBackButtonPropTypes'; import ScreenWrapper from './ScreenWrapper'; -const propTypes = { - ...headerWithBackButtonPropTypes, - - /** Children to display in the lower half of the page (below the header section w/ an animation) */ - children: PropTypes.node.isRequired, - +// TODO: Extend with HeaderWithBackButtonProps once HeaderWithBackButton (https://github.com/Expensify/App/issues/25120) is migrated to TS +type HeaderPageLayoutProps = ChildrenProps & { /** The background color to apply in the upper half of the screen. */ - backgroundColor: PropTypes.string, + backgroundColor?: string; /** A fixed footer to display at the bottom of the page. */ - footer: PropTypes.node, + footer?: React.ReactNode; /** The image to display in the upper half of the screen. */ - header: PropTypes.node, + headerContent?: React.ReactNode; /** Style to apply to the header image container */ - // eslint-disable-next-line react/forbid-prop-types - headerContainerStyles: PropTypes.arrayOf(PropTypes.object), + headerContainerStyles?: Array>; /** Style to apply to the ScrollView container */ - // eslint-disable-next-line react/forbid-prop-types - scrollViewContainerStyles: PropTypes.arrayOf(PropTypes.object), + scrollViewContainerStyles?: Array>; /** Style to apply to the children container */ - // eslint-disable-next-line react/forbid-prop-types - childrenContainerStyles: PropTypes.arrayOf(PropTypes.object), -}; + childrenContainerStyles?: Array>; -const defaultProps = { - backgroundColor: undefined, - header: null, - headerContainerStyles: [], - scrollViewContainerStyles: [], - childrenContainerStyles: [], - footer: null, + /** Style to apply to the whole section container */ + style?: StyleProp; }; -function HeaderPageLayout({backgroundColor, children, footer, headerContainerStyles, scrollViewContainerStyles, childrenContainerStyles, style, headerContent, ...propsToPassToHeader}) { +function HeaderPageLayout({ + backgroundColor, + children, + footer, + headerContainerStyles, + scrollViewContainerStyles, + childrenContainerStyles, + style, + headerContent, + ...propsToPassToHeader +}: HeaderPageLayoutProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -58,7 +53,7 @@ function HeaderPageLayout({backgroundColor, children, footer, headerContainerSty const {isOffline} = useNetwork(); const appBGColor = StyleUtils.getBackgroundColorStyle(theme.appBG); const {titleColor, iconFill} = useMemo(() => { - const isColorfulBackground = (backgroundColor || theme.appBG) !== theme.appBG && (backgroundColor || theme.highlightBG) !== theme.highlightBG; + const isColorfulBackground = (backgroundColor ?? theme.appBG) !== theme.appBG && (backgroundColor ?? theme.highlightBG) !== theme.highlightBG; return { titleColor: isColorfulBackground ? theme.textColorfulBackground : undefined, iconFill: isColorfulBackground ? theme.iconColorfulBackground : undefined, @@ -66,40 +61,41 @@ function HeaderPageLayout({backgroundColor, children, footer, headerContainerSty }, [backgroundColor, theme.appBG, theme.highlightBG, theme.iconColorfulBackground, theme.textColorfulBackground]); return ( + // @ts-expect-error TODO: Remove once ScreenWrapper (https://github.com/Expensify/App/issues/25128) is migrated to TS + {/** @ts-expect-error TODO: Remove once ScreenWrapper (https://github.com/Expensify/App/issues/25128) is migrated to TS */} {({safeAreaPaddingBottomStyle}) => ( <> - + {/** Safari on ios/mac has a bug where overscrolling the page scrollview shows green background color. This is a workaround to fix that. https://github.com/Expensify/App/issues/23422 */} {Browser.isSafari() && ( - + )} - - {!Browser.isSafari() && } - + + {!Browser.isSafari() && } + {headerContent} {children} - {!_.isNull(footer) && {footer}} + {!(footer === null) && {footer}} )} @@ -107,8 +103,7 @@ function HeaderPageLayout({backgroundColor, children, footer, headerContainerSty ); } -HeaderPageLayout.propTypes = propTypes; -HeaderPageLayout.defaultProps = defaultProps; HeaderPageLayout.displayName = 'HeaderPageLayout'; +export type {HeaderPageLayoutProps}; export default HeaderPageLayout; From 4639673314ac8c322f220405a8931f6e11512e05 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Fri, 29 Dec 2023 13:40:17 +0100 Subject: [PATCH 16/69] [TS migration] Migrate 'IllustratedHeaderPageLayout.js' component to TypeScript --- ...out.js => IllustratedHeaderPageLayout.tsx} | 38 ++++++------------- 1 file changed, 12 insertions(+), 26 deletions(-) rename src/components/{IllustratedHeaderPageLayout.js => IllustratedHeaderPageLayout.tsx} (52%) diff --git a/src/components/IllustratedHeaderPageLayout.js b/src/components/IllustratedHeaderPageLayout.tsx similarity index 52% rename from src/components/IllustratedHeaderPageLayout.js rename to src/components/IllustratedHeaderPageLayout.tsx index 9980d8a7879a..ed19d924d501 100644 --- a/src/components/IllustratedHeaderPageLayout.js +++ b/src/components/IllustratedHeaderPageLayout.tsx @@ -1,43 +1,32 @@ -import PropTypes from 'prop-types'; import React from 'react'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import HeaderPageLayout from './HeaderPageLayout'; -import headerWithBackButtonPropTypes from './HeaderWithBackButton/headerWithBackButtonPropTypes'; +import HeaderPageLayout, {HeaderPageLayoutProps} from './HeaderPageLayout'; import Lottie from './Lottie'; +import DotLottieAnimation from './LottieAnimations/types'; -const propTypes = { - ...headerWithBackButtonPropTypes, - +type IllustratedHeaderPageLayoutProps = { /** Children to display in the lower half of the page (below the header section w/ an animation) */ - children: PropTypes.node.isRequired, + children?: React.ReactNode; - /** The illustration to display in the header. Can be either an SVG component or a JSON object representing a Lottie animation. */ - illustration: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).isRequired, + /** The illustration to display in the header. Can be a JSON object representing a Lottie animation. */ + illustration: DotLottieAnimation; /** The background color to apply in the upper half of the screen. */ - backgroundColor: PropTypes.string, - - /** A fixed footer to display at the bottom of the page. */ - footer: PropTypes.node, + backgroundColor?: string; /** Overlay content to display on top of animation */ - overlayContent: PropTypes.func, -}; + overlayContent?: () => React.ReactNode; -const defaultProps = { - backgroundColor: undefined, - footer: null, - overlayContent: null, + propsToPassToHeader: HeaderPageLayoutProps; }; -function IllustratedHeaderPageLayout({backgroundColor, children, illustration, footer, overlayContent, ...propsToPassToHeader}) { +function IllustratedHeaderPageLayout({backgroundColor, children, illustration, overlayContent, propsToPassToHeader}: IllustratedHeaderPageLayoutProps) { const theme = useTheme(); const styles = useThemeStyles(); return ( - {overlayContent && overlayContent()} + {overlayContent?.()} } headerContainerStyles={[styles.justifyContentCenter, styles.w100]} - footer={footer} // eslint-disable-next-line react/jsx-props-no-spreading {...propsToPassToHeader} > @@ -60,8 +48,6 @@ function IllustratedHeaderPageLayout({backgroundColor, children, illustration, f ); } -IllustratedHeaderPageLayout.propTypes = propTypes; -IllustratedHeaderPageLayout.defaultProps = defaultProps; IllustratedHeaderPageLayout.displayName = 'IllustratedHeaderPageLayout'; export default IllustratedHeaderPageLayout; From 1114908acace5d79d3ef731b3c139bdacd726fa8 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Fri, 29 Dec 2023 17:18:10 +0100 Subject: [PATCH 17/69] Adjustments after HeaderWithBackButton migration --- src/components/HeaderPageLayout.tsx | 25 +++++++++---------- .../IllustratedHeaderPageLayout.tsx | 9 ++++--- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/components/HeaderPageLayout.tsx b/src/components/HeaderPageLayout.tsx index bdc949275dd3..003c00326605 100644 --- a/src/components/HeaderPageLayout.tsx +++ b/src/components/HeaderPageLayout.tsx @@ -9,9 +9,9 @@ import * as Browser from '@libs/Browser'; import ChildrenProps from '@src/types/utils/ChildrenProps'; import FixedFooter from './FixedFooter'; import HeaderWithBackButton from './HeaderWithBackButton'; +import HeaderWithBackButtonProps from './HeaderWithBackButton/types'; import ScreenWrapper from './ScreenWrapper'; -// TODO: Extend with HeaderWithBackButtonProps once HeaderWithBackButton (https://github.com/Expensify/App/issues/25120) is migrated to TS type HeaderPageLayoutProps = ChildrenProps & { /** The background color to apply in the upper half of the screen. */ backgroundColor?: string; @@ -33,6 +33,9 @@ type HeaderPageLayoutProps = ChildrenProps & { /** Style to apply to the whole section container */ style?: StyleProp; + + /** Props to pass to HeaderWithackButton */ + propsToPassToHeader?: HeaderWithBackButtonProps; }; function HeaderPageLayout({ @@ -44,7 +47,7 @@ function HeaderPageLayout({ childrenContainerStyles, style, headerContent, - ...propsToPassToHeader + propsToPassToHeader, }: HeaderPageLayoutProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -71,15 +74,12 @@ function HeaderPageLayout({ > {/** @ts-expect-error TODO: Remove once ScreenWrapper (https://github.com/Expensify/App/issues/25128) is migrated to TS */} {({safeAreaPaddingBottomStyle}) => ( - <> - + {/** Safari on ios/mac has a bug where overscrolling the page scrollview shows green background color. This is a workaround to fix that. https://github.com/Expensify/App/issues/23422 */} {Browser.isSafari() && ( @@ -97,7 +97,7 @@ function HeaderPageLayout({ {!(footer === null) && {footer}} - + )} ); @@ -105,5 +105,4 @@ function HeaderPageLayout({ HeaderPageLayout.displayName = 'HeaderPageLayout'; -export type {HeaderPageLayoutProps}; export default HeaderPageLayout; diff --git a/src/components/IllustratedHeaderPageLayout.tsx b/src/components/IllustratedHeaderPageLayout.tsx index ed19d924d501..fe8d37a43a97 100644 --- a/src/components/IllustratedHeaderPageLayout.tsx +++ b/src/components/IllustratedHeaderPageLayout.tsx @@ -1,7 +1,8 @@ import React from 'react'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import HeaderPageLayout, {HeaderPageLayoutProps} from './HeaderPageLayout'; +import HeaderPageLayout from './HeaderPageLayout'; +import HeaderWithBackButtonProps from './HeaderWithBackButton/types'; import Lottie from './Lottie'; import DotLottieAnimation from './LottieAnimations/types'; @@ -18,7 +19,8 @@ type IllustratedHeaderPageLayoutProps = { /** Overlay content to display on top of animation */ overlayContent?: () => React.ReactNode; - propsToPassToHeader: HeaderPageLayoutProps; + /** Props to pass to HeaderWithBackButton */ + propsToPassToHeader: HeaderWithBackButtonProps; }; function IllustratedHeaderPageLayout({backgroundColor, children, illustration, overlayContent, propsToPassToHeader}: IllustratedHeaderPageLayoutProps) { @@ -40,8 +42,7 @@ function IllustratedHeaderPageLayout({backgroundColor, children, illustration, o } headerContainerStyles={[styles.justifyContentCenter, styles.w100]} - // eslint-disable-next-line react/jsx-props-no-spreading - {...propsToPassToHeader} + propsToPassToHeader={propsToPassToHeader} > {children} From d273fc6c2d5e8e3fbc62542935416060e769dfc4 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Fri, 29 Dec 2023 18:21:13 +0100 Subject: [PATCH 18/69] Fix typescript commplaining --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 69db817d44b3..79721d50df35 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1872,7 +1872,7 @@ function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, trans return true; } - const reportID = reportAction?.originalMessage?.IOUReportID ?? 0; + const reportID = (reportAction?.originalMessage as IOUMessage).IOUReportID ?? 0; if (isSettled(String(reportID)) || isReportApproved(String(reportID))) { return false; } From 7ba51461bc06946499545d6ac885e5ceaf82a8fb Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Fri, 29 Dec 2023 18:22:17 +0100 Subject: [PATCH 19/69] Add safety check --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 79721d50df35..5db9da334715 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1872,7 +1872,7 @@ function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, trans return true; } - const reportID = (reportAction?.originalMessage as IOUMessage).IOUReportID ?? 0; + const reportID = (reportAction?.originalMessage as IOUMessage)?.IOUReportID ?? 0; if (isSettled(String(reportID)) || isReportApproved(String(reportID))) { return false; } From d85421dafb19c116d4da1495328ab2511f022a8c Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Fri, 29 Dec 2023 19:02:16 +0100 Subject: [PATCH 20/69] Remove useMemo --- .../ReportActionItem/MoneyRequestView.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 5d4a127900cf..ce4b5d44a801 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -116,9 +116,10 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const isCancelled = moneyRequestReport && moneyRequestReport.isCancelledIOU; const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction); - const canEditAmount = useMemo(() => ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.AMOUNT), [transaction, parentReportAction]); - const canEditMerchant = useMemo(() => ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.MERCHANT), [transaction, parentReportAction]); - const canEditDate = useMemo(() => ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.DATE), [transaction, parentReportAction]); + + // The fields amount, currency, merchant, and date share the same logic, so we'll use Amount here for getting the permission + // and it can be used for the rest of the restricted fields + const canEditRestrictedField = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.AMOUNT); const canEditReceipt = useMemo(() => ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.RECEIPT), [transaction, parentReportAction]); const canEditDistance = useMemo(() => ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.DISTANCE), [transaction, parentReportAction]); @@ -203,8 +204,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate titleIcon={Expensicons.Checkmark} description={amountDescription} titleStyle={styles.newKansasLarge} - interactive={canEditAmount} - shouldShowRightIcon={canEditAmount} + interactive={canEditRestrictedField} + shouldShowRightIcon={canEditRestrictedField} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.AMOUNT))} brickRoadIndicator={hasErrors && transactionAmount === 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} error={hasErrors && transactionAmount === 0 ? translate('common.error.enterAmount') : ''} @@ -239,8 +240,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.MERCHANT))} brickRoadIndicator={hasErrors && isEmptyMerchant ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} @@ -252,8 +253,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DATE))} brickRoadIndicator={hasErrors && transactionDate === '' ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} From 07e2148ad8555a58fe9c87b2d00843fc019eeb7d Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Fri, 29 Dec 2023 19:03:30 +0100 Subject: [PATCH 21/69] Remove useMemo --- src/components/ReportActionItem/MoneyRequestView.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index ce4b5d44a801..3410cceafa0f 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -117,11 +117,10 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction); - // The fields amount, currency, merchant, and date share the same logic, so we'll use Amount here for getting the permission - // and it can be used for the rest of the restricted fields + // The fields amount, currency, merchant, and date share the same logic so we'll use Amount here for getting the permission and it can be used for the rest of the restricted fields const canEditRestrictedField = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.AMOUNT); - const canEditReceipt = useMemo(() => ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.RECEIPT), [transaction, parentReportAction]); - const canEditDistance = useMemo(() => ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.DISTANCE), [transaction, parentReportAction]); + const canEditReceipt = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.RECEIPT); + const canEditDistance = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.DISTANCE); // A flag for verifying that the current report is a sub-report of a workspace chat const isPolicyExpenseChat = policy && policy.type !== CONST.POLICY.TYPE.PERSONAL; From c746ce6c74f0470968da0934447fc7e0ea085c79 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Sat, 30 Dec 2023 15:55:29 +0100 Subject: [PATCH 22/69] parse style as array --- src/components/AmountTextInput.js | 2 +- src/components/TextInput/BaseTextInput/index.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/AmountTextInput.js b/src/components/AmountTextInput.js index e174ad27e7c3..1bf45ffd38db 100644 --- a/src/components/AmountTextInput.js +++ b/src/components/AmountTextInput.js @@ -67,7 +67,7 @@ function AmountTextInput(props) { onSelectionChange={props.onSelectionChange} role={CONST.ROLE.PRESENTATION} onKeyPress={props.onKeyPress} - testInputWrapper={[...StyleUtils.parseStyleAsArray(props.containerStyles)]} + testInputWrapper={props.containerStyles} /> ); } diff --git a/src/components/TextInput/BaseTextInput/index.js b/src/components/TextInput/BaseTextInput/index.js index a9b9876b2f1d..df33b915ef7f 100644 --- a/src/components/TextInput/BaseTextInput/index.js +++ b/src/components/TextInput/BaseTextInput/index.js @@ -263,6 +263,7 @@ function BaseTextInput(props) { props.autoGrowHeight && styles.autoGrowHeightInputContainer(textInputHeight, variables.componentSizeLarge, maxHeight), !isMultiline && styles.componentHeightLarge, props.testInputWrapper, + ...StyleUtils.parseStyleAsArray(props.testInputWrapper) ]} > Date: Sat, 30 Dec 2023 16:00:53 +0100 Subject: [PATCH 23/69] type --- .../TextInput/BaseTextInput/baseTextInputPropTypes.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js b/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js index 5387d1ff81d1..39bb2f1ff123 100644 --- a/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js +++ b/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js @@ -25,6 +25,9 @@ const propTypes = { /** Customize the TextInput container */ textInputContainerStyles: PropTypes.arrayOf(PropTypes.object), + /** Customizes the touchable wrapper of the TextInput component */ + testInputWrapper: PropTypes.arrayOf(PropTypes.object), + /** Customize the main container */ containerStyles: PropTypes.arrayOf(PropTypes.object), From 41b6340d84395a758e585aec1345d5a66d9452cf Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Sat, 30 Dec 2023 16:31:40 +0100 Subject: [PATCH 24/69] remove file after merge --- src/components/AmountTextInput.js | 6 +- .../TextInput/BaseTextInput/index.js | 432 ------------------ .../TextInput/BaseTextInput/index.tsx | 2 +- src/components/TimePicker/TimePicker.js | 4 +- 4 files changed, 6 insertions(+), 438 deletions(-) delete mode 100644 src/components/TextInput/BaseTextInput/index.js diff --git a/src/components/AmountTextInput.js b/src/components/AmountTextInput.js index a080b521c14f..231a99f0e6a6 100644 --- a/src/components/AmountTextInput.js +++ b/src/components/AmountTextInput.js @@ -32,7 +32,7 @@ const propTypes = { style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), /** Style for the container */ - containerStyles: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), + touchableInputWrapperStyle: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), /** Function to call to handle key presses in the text input */ onKeyPress: PropTypes.func, @@ -44,7 +44,7 @@ const defaultProps = { onSelectionChange: () => {}, onKeyPress: () => {}, style: {}, - containerStyles: {}, + touchableInputWrapperStyle: {}, }; function AmountTextInput(props) { @@ -67,7 +67,7 @@ function AmountTextInput(props) { onSelectionChange={props.onSelectionChange} role={CONST.ROLE.PRESENTATION} onKeyPress={props.onKeyPress} - touchableInputWrapperStyle={props.containerStyles} + touchableInputWrapperStyle={props.touchableInputWrapperStyle} /> ); } diff --git a/src/components/TextInput/BaseTextInput/index.js b/src/components/TextInput/BaseTextInput/index.js deleted file mode 100644 index 12136cbde51d..000000000000 --- a/src/components/TextInput/BaseTextInput/index.js +++ /dev/null @@ -1,432 +0,0 @@ -import Str from 'expensify-common/lib/str'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {ActivityIndicator, Animated, StyleSheet, View} from 'react-native'; -import _ from 'underscore'; -import Checkbox from '@components/Checkbox'; -import FormHelpMessage from '@components/FormHelpMessage'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; -import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; -import RNTextInput from '@components/RNTextInput'; -import SwipeInterceptPanResponder from '@components/SwipeInterceptPanResponder'; -import Text from '@components/Text'; -import * as styleConst from '@components/TextInput/styleConst'; -import TextInputLabel from '@components/TextInput/TextInputLabel'; -import withLocalize from '@components/withLocalize'; -import useStyleUtils from '@hooks/useStyleUtils'; -import useTheme from '@hooks/useTheme'; -import useThemeStyles from '@hooks/useThemeStyles'; -import * as Browser from '@libs/Browser'; -import isInputAutoFilled from '@libs/isInputAutoFilled'; -import useNativeDriver from '@libs/useNativeDriver'; -import variables from '@styles/variables'; -import CONST from '@src/CONST'; -import * as baseTextInputPropTypes from './baseTextInputPropTypes'; - -function BaseTextInput(props) { - const theme = useTheme(); - const styles = useThemeStyles(); - const StyleUtils = useStyleUtils(); - const initialValue = props.value || props.defaultValue || ''; - const initialActiveLabel = props.forceActiveLabel || initialValue.length > 0 || Boolean(props.prefixCharacter); - - const [isFocused, setIsFocused] = useState(false); - const [passwordHidden, setPasswordHidden] = useState(props.secureTextEntry); - const [textInputWidth, setTextInputWidth] = useState(0); - const [textInputHeight, setTextInputHeight] = useState(0); - const [height, setHeight] = useState(variables.componentSizeLarge); - const [width, setWidth] = useState(); - const labelScale = useRef(new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_SCALE : styleConst.INACTIVE_LABEL_SCALE)).current; - const labelTranslateY = useRef(new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_TRANSLATE_Y : styleConst.INACTIVE_LABEL_TRANSLATE_Y)).current; - - const input = useRef(null); - const isLabelActive = useRef(initialActiveLabel); - - // AutoFocus which only works on mount: - useEffect(() => { - // We are manually managing focus to prevent this issue: https://github.com/Expensify/App/issues/4514 - if (!props.autoFocus || !input.current) { - return; - } - - if (props.shouldDelayFocus) { - const focusTimeout = setTimeout(() => input.current.focus(), CONST.ANIMATED_TRANSITION); - return () => clearTimeout(focusTimeout); - } - input.current.focus(); - // We only want this to run on mount - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const animateLabel = useCallback( - (translateY, scale) => { - Animated.parallel([ - Animated.spring(labelTranslateY, { - toValue: translateY, - duration: styleConst.LABEL_ANIMATION_DURATION, - useNativeDriver, - }), - Animated.spring(labelScale, { - toValue: scale, - duration: styleConst.LABEL_ANIMATION_DURATION, - useNativeDriver, - }), - ]).start(); - }, - [labelScale, labelTranslateY], - ); - - const activateLabel = useCallback(() => { - const value = props.value || ''; - - if (value.length < 0 || isLabelActive.current) { - return; - } - - animateLabel(styleConst.ACTIVE_LABEL_TRANSLATE_Y, styleConst.ACTIVE_LABEL_SCALE); - isLabelActive.current = true; - }, [animateLabel, props.value]); - - const deactivateLabel = useCallback(() => { - const value = props.value || ''; - - if (props.forceActiveLabel || value.length !== 0 || props.prefixCharacter) { - return; - } - - animateLabel(styleConst.INACTIVE_LABEL_TRANSLATE_Y, styleConst.INACTIVE_LABEL_SCALE); - isLabelActive.current = false; - }, [animateLabel, props.forceActiveLabel, props.prefixCharacter, props.value]); - - const onFocus = (event) => { - if (props.onFocus) { - props.onFocus(event); - } - setIsFocused(true); - }; - - const onBlur = (event) => { - if (props.onBlur) { - props.onBlur(event); - } - setIsFocused(false); - }; - - const onPress = (event) => { - if (props.disabled) { - return; - } - - if (props.onPress) { - props.onPress(event); - } - - if (!event.isDefaultPrevented()) { - input.current.focus(); - } - }; - - const onLayout = useCallback( - (event) => { - if (!props.autoGrowHeight && props.multiline) { - return; - } - - const layout = event.nativeEvent.layout; - - setWidth((prevWidth) => (props.autoGrowHeight ? layout.width : prevWidth)); - setHeight((prevHeight) => (!props.multiline ? layout.height : prevHeight)); - }, - [props.autoGrowHeight, props.multiline], - ); - - // The ref is needed when the component is uncontrolled and we don't have a value prop - const hasValueRef = useRef(initialValue.length > 0); - const inputValue = props.value || ''; - const hasValue = inputValue.length > 0 || hasValueRef.current; - - // Activate or deactivate the label when either focus changes, or for controlled - // components when the value prop changes: - useEffect(() => { - if ( - hasValue || - isFocused || - // If the text has been supplied by Chrome autofill, the value state is not synced with the value - // as Chrome doesn't trigger a change event. When there is autofill text, keep the label activated. - isInputAutoFilled(input.current) - ) { - activateLabel(); - } else { - deactivateLabel(); - } - }, [activateLabel, deactivateLabel, hasValue, isFocused]); - - // When the value prop gets cleared externally, we need to keep the ref in sync: - useEffect(() => { - // Return early when component uncontrolled, or we still have a value - if (props.value === undefined || !_.isEmpty(props.value)) { - return; - } - hasValueRef.current = false; - }, [props.value]); - - /** - * Set Value & activateLabel - * - * @param {String} value - * @memberof BaseTextInput - */ - const setValue = (value) => { - if (props.onInputChange) { - props.onInputChange(value); - } - - Str.result(props.onChangeText, value); - - if (value && value.length > 0) { - hasValueRef.current = true; - // When the componment is uncontrolled, we need to manually activate the label: - if (props.value === undefined) { - activateLabel(); - } - } else { - hasValueRef.current = false; - } - }; - - const togglePasswordVisibility = useCallback(() => { - setPasswordHidden((prevPasswordHidden) => !prevPasswordHidden); - }, []); - - // When adding a new prefix character, adjust this method to add expected character width. - // This is because character width isn't known before it's rendered to the screen, and once it's rendered, - // it's too late to calculate it's width because the change in padding would cause a visible jump. - // Some characters are wider than the others when rendered, e.g. '@' vs '#'. Chosen font-family and font-size - // also have an impact on the width of the character, but as long as there's only one font-family and one font-size, - // this method will produce reliable results. - const getCharacterPadding = (prefix) => { - switch (prefix) { - case CONST.POLICY.ROOM_PREFIX: - return 10; - default: - throw new Error(`Prefix ${prefix} has no padding assigned.`); - } - }; - - // eslint-disable-next-line react/forbid-foreign-prop-types - const inputProps = _.omit(props, _.keys(baseTextInputPropTypes.propTypes)); - const hasLabel = Boolean(props.label.length); - const isReadOnly = _.isUndefined(props.readOnly) ? props.disabled : props.readOnly; - const inputHelpText = props.errorText || props.hint; - const placeholder = props.prefixCharacter || isFocused || !hasLabel || (hasLabel && props.forceActiveLabel) ? props.placeholder : null; - const maxHeight = StyleSheet.flatten(props.containerStyles).maxHeight; - const textInputContainerStyles = StyleSheet.flatten([ - styles.textInputContainer, - ...props.textInputContainerStyles, - props.autoGrow && StyleUtils.getWidthStyle(textInputWidth), - !props.hideFocusedState && isFocused && styles.borderColorFocus, - (props.hasError || props.errorText) && styles.borderColorDanger, - props.autoGrowHeight && {scrollPaddingTop: 2 * maxHeight}, - ]); - const isMultiline = props.multiline || props.autoGrowHeight; - - /* To prevent text jumping caused by virtual DOM calculations on Safari and mobile Chrome, - make sure to include the `lineHeight`. - Reference: https://github.com/Expensify/App/issues/26735 - For other platforms, explicitly remove `lineHeight` from single-line inputs - to prevent long text from disappearing once it exceeds the input space. - See https://github.com/Expensify/App/issues/13802 */ - - const lineHeight = useMemo(() => { - if ((Browser.isSafari() || Browser.isMobileChrome()) && _.isArray(props.inputStyle)) { - const lineHeightValue = _.find(props.inputStyle, (f) => f.lineHeight !== undefined); - if (lineHeightValue) { - return lineHeightValue.lineHeight; - } - } - - return undefined; - }, [props.inputStyle]); - - return ( - <> - - - - {hasLabel ? ( - <> - {/* Adding this background to the label only for multiline text input, - to prevent text overlapping with label when scrolling */} - {isMultiline && } - - - ) : null} - - {Boolean(props.prefixCharacter) && ( - - - {props.prefixCharacter} - - - )} - { - if (typeof props.innerRef === 'function') { - props.innerRef(ref); - } else if (props.innerRef && _.has(props.innerRef, 'current')) { - // eslint-disable-next-line no-param-reassign - props.innerRef.current = ref; - } - input.current = ref; - }} - // eslint-disable-next-line - {...inputProps} - autoCorrect={props.secureTextEntry ? false : props.autoCorrect} - placeholder={placeholder} - placeholderTextColor={theme.placeholderText} - underlineColorAndroid="transparent" - style={[ - styles.flex1, - styles.w100, - props.inputStyle, - (!hasLabel || isMultiline) && styles.pv0, - props.prefixCharacter && StyleUtils.getPaddingLeft(getCharacterPadding(props.prefixCharacter) + styles.pl1.paddingLeft), - props.secureTextEntry && styles.secureInput, - - // Explicitly remove `lineHeight` from single line inputs so that long text doesn't disappear - // once it exceeds the input space (See https://github.com/Expensify/App/issues/13802) - !isMultiline && {height, lineHeight}, - - // Explicitly change boxSizing attribute for mobile chrome in order to apply line-height - // for the issue mentioned here https://github.com/Expensify/App/issues/26735 - !isMultiline && Browser.isMobileChrome() && {boxSizing: 'content-box', height: undefined}, - - // Stop scrollbar flashing when breaking lines with autoGrowHeight enabled. - ...(props.autoGrowHeight ? [StyleUtils.getAutoGrowHeightInputStyle(textInputHeight, maxHeight), styles.verticalAlignTop] : []), - - // Add disabled color theme when field is not editable. - props.disabled && styles.textInputDisabled, - styles.pointerEventsAuto, - ]} - multiline={isMultiline} - maxLength={props.maxLength} - onFocus={onFocus} - onBlur={onBlur} - onChangeText={setValue} - secureTextEntry={passwordHidden} - onPressOut={props.onPress} - showSoftInputOnFocus={!props.disableKeyboard} - inputMode={props.inputMode} - value={props.value} - selection={props.selection} - readOnly={isReadOnly} - defaultValue={props.defaultValue} - // FormSubmit Enter key handler does not have access to direct props. - // `dataset.submitOnEnter` is used to indicate that pressing Enter on this input should call the submit callback. - dataSet={{submitOnEnter: isMultiline && props.submitOnEnter}} - /> - {props.isLoading && ( - - )} - {Boolean(props.secureTextEntry) && ( - e.preventDefault()} - accessibilityLabel={props.translate('common.visible')} - > - - - )} - {!props.secureTextEntry && Boolean(props.icon) && ( - - - - )} - - - - {!_.isEmpty(inputHelpText) && ( - - )} - - {/* - Text input component doesn't support auto grow by default. - We're using a hidden text input to achieve that. - This text view is used to calculate width or height of the input value given textStyle in this component. - This Text component is intentionally positioned out of the screen. - */} - {(props.autoGrow || props.autoGrowHeight) && ( - // Add +2 to width on Safari browsers so that text is not cut off due to the cursor or when changing the value - // https://github.com/Expensify/App/issues/8158 - // https://github.com/Expensify/App/issues/26628 - { - let additionalWidth = 0; - if (Browser.isMobileSafari() || Browser.isSafari()) { - additionalWidth = 2; - } - setTextInputWidth(e.nativeEvent.layout.width + additionalWidth); - setTextInputHeight(e.nativeEvent.layout.height); - }} - > - {/* \u200B added to solve the issue of not expanding the text input enough when the value ends with '\n' (https://github.com/Expensify/App/issues/21271) */} - {props.value ? `${props.value}${props.value.endsWith('\n') ? '\u200B' : ''}` : props.placeholder} - - )} - - ); -} - -BaseTextInput.displayName = 'BaseTextInput'; -BaseTextInput.propTypes = baseTextInputPropTypes.propTypes; -BaseTextInput.defaultProps = baseTextInputPropTypes.defaultProps; - -export default withLocalize(BaseTextInput); diff --git a/src/components/TextInput/BaseTextInput/index.tsx b/src/components/TextInput/BaseTextInput/index.tsx index 0c872d240623..8e1605944993 100644 --- a/src/components/TextInput/BaseTextInput/index.tsx +++ b/src/components/TextInput/BaseTextInput/index.tsx @@ -287,7 +287,7 @@ function BaseTextInput( style={[ autoGrowHeight && styles.autoGrowHeightInputContainer(textInputHeight, variables.componentSizeLarge, typeof maxHeight === 'number' ? maxHeight : 0), !isMultiline && styles.componentHeightLarge, - ...StyleUtils.parseStyleAsArray(touchableInputWrapperStyle) + touchableInputWrapperStyle, ]} > @@ -411,7 +411,7 @@ function TimePicker({forwardedRef, defaultValue, onSubmit, onInputChange}) { setSelectionMinute(e.nativeEvent.selection); }} style={styles.timePickerInput} - containerStyles={[styles.timePickerHeight100]} + touchableInputWrapperStyle={styles.timePickerHeight100} selection={selectionMinute} showSoftInputOnFocus={false} /> From 9a2ef5c8dee0245143b45b217d8aa93634baed3c Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Sun, 31 Dec 2023 14:04:46 +0100 Subject: [PATCH 25/69] Remove unnecessary call to canEditFieldOfMoneyRequest --- src/components/ReportActionItem/MoneyRequestView.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 3410cceafa0f..2c5221f3268d 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -120,7 +120,6 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate // The fields amount, currency, merchant, and date share the same logic so we'll use Amount here for getting the permission and it can be used for the rest of the restricted fields const canEditRestrictedField = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.AMOUNT); const canEditReceipt = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.RECEIPT); - const canEditDistance = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.DISTANCE); // A flag for verifying that the current report is a sub-report of a workspace chat const isPolicyExpenseChat = policy && policy.type !== CONST.POLICY.TYPE.PERSONAL; @@ -228,8 +227,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} /> From bdfa31931328954c2edaaca2c47ba64847573262 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Sun, 31 Dec 2023 14:05:00 +0100 Subject: [PATCH 26/69] Address comments --- src/pages/EditRequestPage.js | 2 +- src/pages/iou/IOUCurrencySelection.js | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index b7f8c7af0d1e..ce9f0e0f5830 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -90,7 +90,7 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep // A flag for verifying that the current report is a sub-report of a workspace chat const policy = ReportUtils.getPolicy(report.policyID || ''); - const isPolicyExpenseChat = policy && policy.type !== CONST.POLICY.TYPE.PERSONAL; + const isPolicyExpenseChat = policy.type && policy.type !== CONST.POLICY.TYPE.PERSONAL; // A flag for showing the categories page const shouldShowCategories = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index eab9ab5a7510..b3180bbd5964 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -86,7 +86,7 @@ function IOUCurrencySelection(props) { const parentReportAction = ReportActionsUtils.getReportAction(report.parentReportID, report.parentReportActionID); // Do not dismiss the modal, when a current user can edit this currency of this money request. - if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, report.parentReportID, CONST.EDIT_REQUEST_FIELD.CURRENCY)) { + if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, props.transaction, CONST.EDIT_REQUEST_FIELD.CURRENCY)) { return; } @@ -94,7 +94,7 @@ function IOUCurrencySelection(props) { Navigation.isNavigationReady().then(() => { Navigation.dismissModal(); }); - }, [threadReportID]); + }, [threadReportID, props.transaction]); const confirmCurrencySelection = useCallback( (option) => { @@ -193,6 +193,24 @@ export default compose( withOnyx({ currencyList: {key: ONYXKEYS.CURRENCY_LIST}, iou: {key: ONYXKEYS.IOU}, + report: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`, + }, + }), + withOnyx({ + parentReportActions: { + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`, + canEvict: false, + }, + }), + withOnyx({ + transaction: { + key: ({report, parentReportActions}) => { + const parentReportActionID = lodashGet(report, 'parentReportActionID', '0'); + const parentReportAction = lodashGet(parentReportActions, parentReportActionID); + return `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(parentReportAction, 'originalMessage.IOUTransactionID', 0)}`; + }, + }, }), withNetwork(), )(IOUCurrencySelection); From de8ea549fd682dbbe290a6b4e942df938a3d3588 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Sun, 31 Dec 2023 14:35:59 +0100 Subject: [PATCH 27/69] Remove unneeded check --- src/libs/ReportUtils.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 5db9da334715..1a39504b3b0a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1845,11 +1845,6 @@ function canEditMoneyRequest(reportAction: OnyxEntry): boolean { return true; } - // We allow editing of PROCESSING reports in Free policies, but not in paid policie (yet) - if (policy.type === CONST.POLICY.TYPE.FREE) { - return isRequestor && isProcessingReport(moneyRequestReport); - } - return !isReportApproved(moneyRequestReport) && !isSettled(moneyRequestReport?.reportID) && isRequestor; } From 982ba480178f3d07a1d63ed2eeee6c08fe3923c9 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Sun, 31 Dec 2023 15:07:26 +0100 Subject: [PATCH 28/69] Remove code causing regression --- .../ReportActionItem/MoneyRequestView.js | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 2c5221f3268d..013cc7e7bf55 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -115,11 +115,13 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const isCancelled = moneyRequestReport && moneyRequestReport.isCancelledIOU; + // Use for non-restricted fields: description, category, tag, billable, etc. const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction); - - // The fields amount, currency, merchant, and date share the same logic so we'll use Amount here for getting the permission and it can be used for the rest of the restricted fields - const canEditRestrictedField = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.AMOUNT); + const canEditAmount = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.AMOUNT); + const canEditMerchant = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.MERCHANT); + const canEditDate = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.DATE); const canEditReceipt = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.RECEIPT); + const canEditDistance = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.DISTANCE); // A flag for verifying that the current report is a sub-report of a workspace chat const isPolicyExpenseChat = policy && policy.type !== CONST.POLICY.TYPE.PERSONAL; @@ -202,8 +204,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate titleIcon={Expensicons.Checkmark} description={amountDescription} titleStyle={styles.newKansasLarge} - interactive={canEditRestrictedField} - shouldShowRightIcon={canEditRestrictedField} + interactive={canEditAmount} + shouldShowRightIcon={canEditAmount} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.AMOUNT))} brickRoadIndicator={hasErrors && transactionAmount === 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} error={hasErrors && transactionAmount === 0 ? translate('common.error.enterAmount') : ''} @@ -227,8 +229,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} /> @@ -238,8 +240,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.MERCHANT))} brickRoadIndicator={hasErrors && isEmptyMerchant ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} @@ -251,8 +253,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DATE))} brickRoadIndicator={hasErrors && transactionDate === '' ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} From cfaac6f93da3a625efd8c7fc3c920da28ce76f5a Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Sun, 31 Dec 2023 15:09:29 +0100 Subject: [PATCH 29/69] Add comment --- src/libs/ReportUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1a39504b3b0a..2bfb310cc206 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1863,6 +1863,7 @@ function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, trans return false; } + // If we're editing fields such as category, tag, description, etc. the check above should be enough for handling the permission if (!restrictedFields.includes(fieldToEdit)) { return true; } From beb66011470d5d47db7471be9fbf94a7e426857b Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Sun, 31 Dec 2023 19:03:54 -0500 Subject: [PATCH 30/69] parent report action id --- src/pages/home/HeaderView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 5c0be79d1d85..a3dcb41d1bfc 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -147,7 +147,7 @@ function HeaderView(props) { icon: Expensicons.ChatBubbles, text: translate('common.join'), onSelected: Session.checkIfActionIsAllowed(() => - Report.updateNotificationPreference(props.report.reportID, props.report.notificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false, props.report.parentReportID, parentReportAction), + Report.updateNotificationPreference(props.report.reportID, props.report.notificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false, props.report.parentReportID, props.report.parentReportActionID), ), }); } else if ((isChatThread && props.report.notificationPreference.length) || isUserCreatedPolicyRoom || canLeaveRoom) { From e76dd0187889a2188d02574a74fd498ad866442d Mon Sep 17 00:00:00 2001 From: Srikar Parsi Date: Sun, 31 Dec 2023 19:08:37 -0500 Subject: [PATCH 31/69] prettier --- src/pages/home/HeaderView.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index f79cac315d71..e50f0e81b9de 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -147,7 +147,14 @@ function HeaderView(props) { icon: Expensicons.ChatBubbles, text: translate('common.join'), onSelected: Session.checkIfActionIsAllowed(() => - Report.updateNotificationPreference(props.report.reportID, props.report.notificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false, props.report.parentReportID, props.report.parentReportActionID), + Report.updateNotificationPreference( + props.report.reportID, + props.report.notificationPreference, + CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + false, + props.report.parentReportID, + props.report.parentReportActionID, + ), ), }); } else if ((isChatThread && props.report.notificationPreference.length) || isUserCreatedPolicyRoom || canLeaveRoom) { From f672b91a0ab81bc34dd41fdfdda64018ebd0be7c Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Mon, 1 Jan 2024 15:03:25 +0100 Subject: [PATCH 32/69] Update src/components/ReportActionItem/MoneyRequestView.js Co-authored-by: c3024 <102477862+c3024@users.noreply.github.com> --- src/components/ReportActionItem/MoneyRequestView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 013cc7e7bf55..bae1c90a1682 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -124,7 +124,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const canEditDistance = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.DISTANCE); // A flag for verifying that the current report is a sub-report of a workspace chat - const isPolicyExpenseChat = policy && policy.type !== CONST.POLICY.TYPE.PERSONAL; + const isPolicyExpenseChat = policy.type && policy.type !== CONST.POLICY.TYPE.PERSONAL; // Fetches only the first tag, for now const policyTag = PolicyUtils.getTag(policyTags); From 31e6a15f1fedc071dee237cdf1683bb8d55c061c Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Mon, 1 Jan 2024 15:05:52 +0100 Subject: [PATCH 33/69] Update comment --- src/libs/ReportUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 2bfb310cc206..890cdf2ad4d1 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1807,7 +1807,8 @@ function getTransactionDetails(transaction: OnyxEntry, createdDateF * - the current user is the requestor and is not settled yet * - in case of expense report * - the current user is the requestor and is not settled yet - * - or the user is an admin on the policy the expense report is tied to + * - the current user is the manager of the report + * - or the current user is an admin on the policy the expense report is tied to * * This is used in conjuction with canEditRestrictedField to control editing of specific fields like amount, currency, created, and receipt. * On its own, it only controls allowing/disallowing navigating to the editing pages or showing/hiding the 'Edit' icon on report actions From ba8506d00b78fc88e85ba811f931ebf62b726dad Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Mon, 1 Jan 2024 15:22:25 +0100 Subject: [PATCH 34/69] Remove transaction dependency from canEditFieldOfMoneyRequest --- .../ReportActionItem/MoneyRequestView.js | 10 +++++----- src/libs/ReportUtils.ts | 7 ++++--- src/pages/EditRequestPage.js | 2 +- src/pages/iou/IOUCurrencySelection.js | 20 +------------------ 4 files changed, 11 insertions(+), 28 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index bae1c90a1682..8c0dd159d3f4 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -117,11 +117,11 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate // Use for non-restricted fields: description, category, tag, billable, etc. const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction); - const canEditAmount = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.AMOUNT); - const canEditMerchant = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.MERCHANT); - const canEditDate = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.DATE); - const canEditReceipt = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.RECEIPT); - const canEditDistance = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, CONST.EDIT_REQUEST_FIELD.DISTANCE); + const canEditAmount = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.AMOUNT); + const canEditMerchant = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.MERCHANT); + const canEditDate = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DATE); + const canEditReceipt = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.RECEIPT); + const canEditDistance = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE); // A flag for verifying that the current report is a sub-report of a workspace chat const isPolicyExpenseChat = policy.type && policy.type !== CONST.POLICY.TYPE.PERSONAL; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 890cdf2ad4d1..33a5f2f5bb1e 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1849,7 +1849,7 @@ function canEditMoneyRequest(reportAction: OnyxEntry): boolean { return !isReportApproved(moneyRequestReport) && !isSettled(moneyRequestReport?.reportID) && isRequestor; } -function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, transaction: Transaction, fieldToEdit: ValueOf): boolean { +function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, fieldToEdit: ValueOf): boolean { // A list of fields that cannot be edited by anyone, once a money request has been settled const restrictedFields: string[] = [ CONST.EDIT_REQUEST_FIELD.AMOUNT, @@ -1869,11 +1869,12 @@ function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, trans return true; } - const reportID = (reportAction?.originalMessage as IOUMessage)?.IOUReportID ?? 0; - if (isSettled(String(reportID)) || isReportApproved(String(reportID))) { + const iouMessage: IOUMessage = reportAction?.originalMessage; + if (isSettled(String(iouMessage.IOUReportID)) || isReportApproved(String(iouMessage.IOUReportID))) { return false; } + const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${iouMessage?.IOUTransactionID}`] ?? {}; if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.AMOUNT || fieldToEdit === CONST.EDIT_REQUEST_FIELD.CURRENCY) { return !TransactionUtils.isCardTransaction(transaction); } diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index ce9f0e0f5830..7cc1bde4e91f 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -101,7 +101,7 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep // Decides whether to allow or disallow editing a money request useEffect(() => { // Do not dismiss the modal, when a current user can edit this property of the money request. - if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, transaction, fieldToEdit)) { + if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, fieldToEdit)) { return; } diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index b3180bbd5964..29580dec4bef 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -86,7 +86,7 @@ function IOUCurrencySelection(props) { const parentReportAction = ReportActionsUtils.getReportAction(report.parentReportID, report.parentReportActionID); // Do not dismiss the modal, when a current user can edit this currency of this money request. - if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, props.transaction, CONST.EDIT_REQUEST_FIELD.CURRENCY)) { + if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.CURRENCY)) { return; } @@ -193,24 +193,6 @@ export default compose( withOnyx({ currencyList: {key: ONYXKEYS.CURRENCY_LIST}, iou: {key: ONYXKEYS.IOU}, - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`, - }, - }), - withOnyx({ - parentReportActions: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`, - canEvict: false, - }, - }), - withOnyx({ - transaction: { - key: ({report, parentReportActions}) => { - const parentReportActionID = lodashGet(report, 'parentReportActionID', '0'); - const parentReportAction = lodashGet(parentReportActions, parentReportActionID); - return `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(parentReportAction, 'originalMessage.IOUTransactionID', 0)}`; - }, - }, }), withNetwork(), )(IOUCurrencySelection); From c51f2507081a87e60c2bfddca9336d96e42a3b2f Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Mon, 1 Jan 2024 16:47:20 +0100 Subject: [PATCH 35/69] Update comment --- src/components/ReportActionItem/MoneyRequestView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 8c0dd159d3f4..5b4c98ec0682 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -115,7 +115,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const isCancelled = moneyRequestReport && moneyRequestReport.isCancelledIOU; - // Use for non-restricted fields: description, category, tag, billable, etc. + // Used for non-restricted fields such as: description, category, tag, billable, etc. const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction); const canEditAmount = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.AMOUNT); const canEditMerchant = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.MERCHANT); From 3b7c4fb031350136c3041637b59ed92a529c9fbd Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Tue, 2 Jan 2024 11:07:46 +0100 Subject: [PATCH 36/69] CR fixes --- src/components/HeaderPageLayout.tsx | 48 +++++++++---------- .../IllustratedHeaderPageLayout.tsx | 31 ++++++------ 2 files changed, 37 insertions(+), 42 deletions(-) diff --git a/src/components/HeaderPageLayout.tsx b/src/components/HeaderPageLayout.tsx index 003c00326605..6701d78af901 100644 --- a/src/components/HeaderPageLayout.tsx +++ b/src/components/HeaderPageLayout.tsx @@ -1,4 +1,4 @@ -import React, {useMemo} from 'react'; +import React, {ReactNode, useMemo} from 'react'; import {ScrollView, StyleProp, View, ViewStyle} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -12,31 +12,29 @@ import HeaderWithBackButton from './HeaderWithBackButton'; import HeaderWithBackButtonProps from './HeaderWithBackButton/types'; import ScreenWrapper from './ScreenWrapper'; -type HeaderPageLayoutProps = ChildrenProps & { - /** The background color to apply in the upper half of the screen. */ - backgroundColor?: string; +type HeaderPageLayoutProps = ChildrenProps & + HeaderWithBackButtonProps & { + /** The background color to apply in the upper half of the screen. */ + backgroundColor?: string; - /** A fixed footer to display at the bottom of the page. */ - footer?: React.ReactNode; + /** A fixed footer to display at the bottom of the page. */ + footer?: ReactNode; - /** The image to display in the upper half of the screen. */ - headerContent?: React.ReactNode; + /** The image to display in the upper half of the screen. */ + headerContent?: React.ReactNode; - /** Style to apply to the header image container */ - headerContainerStyles?: Array>; + /** Style to apply to the header image container */ + headerContainerStyles?: StyleProp; - /** Style to apply to the ScrollView container */ - scrollViewContainerStyles?: Array>; + /** Style to apply to the ScrollView container */ + scrollViewContainerStyles?: StyleProp; - /** Style to apply to the children container */ - childrenContainerStyles?: Array>; + /** Style to apply to the children container */ + childrenContainerStyles?: StyleProp; - /** Style to apply to the whole section container */ - style?: StyleProp; - - /** Props to pass to HeaderWithackButton */ - propsToPassToHeader?: HeaderWithBackButtonProps; -}; + /** Style to apply to the whole section container */ + style?: StyleProp; + }; function HeaderPageLayout({ backgroundColor, @@ -47,7 +45,7 @@ function HeaderPageLayout({ childrenContainerStyles, style, headerContent, - propsToPassToHeader, + ...rest }: HeaderPageLayoutProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -76,11 +74,11 @@ function HeaderPageLayout({ {({safeAreaPaddingBottomStyle}) => ( - + {/** Safari on ios/mac has a bug where overscrolling the page scrollview shows green background color. This is a workaround to fix that. https://github.com/Expensify/App/issues/23422 */} {Browser.isSafari() && ( @@ -88,14 +86,14 @@ function HeaderPageLayout({ )} - + {!Browser.isSafari() && } {headerContent} {children} - {!(footer === null) && {footer}} + {footer !== null && {footer}} )} diff --git a/src/components/IllustratedHeaderPageLayout.tsx b/src/components/IllustratedHeaderPageLayout.tsx index fe8d37a43a97..6ca7d5c0715b 100644 --- a/src/components/IllustratedHeaderPageLayout.tsx +++ b/src/components/IllustratedHeaderPageLayout.tsx @@ -1,29 +1,25 @@ -import React from 'react'; +import React, {ReactNode} from 'react'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import ChildrenProps from '@src/types/utils/ChildrenProps'; import HeaderPageLayout from './HeaderPageLayout'; import HeaderWithBackButtonProps from './HeaderWithBackButton/types'; import Lottie from './Lottie'; import DotLottieAnimation from './LottieAnimations/types'; -type IllustratedHeaderPageLayoutProps = { - /** Children to display in the lower half of the page (below the header section w/ an animation) */ - children?: React.ReactNode; +type IllustratedHeaderPageLayoutProps = ChildrenProps & + HeaderWithBackButtonProps & { + /** The illustration to display in the header. Can be a JSON object representing a Lottie animation. */ + illustration: DotLottieAnimation; - /** The illustration to display in the header. Can be a JSON object representing a Lottie animation. */ - illustration: DotLottieAnimation; + /** The background color to apply in the upper half of the screen. */ + backgroundColor?: string; - /** The background color to apply in the upper half of the screen. */ - backgroundColor?: string; + /** Overlay content to display on top of animation */ + overlayContent?: () => ReactNode; + }; - /** Overlay content to display on top of animation */ - overlayContent?: () => React.ReactNode; - - /** Props to pass to HeaderWithBackButton */ - propsToPassToHeader: HeaderWithBackButtonProps; -}; - -function IllustratedHeaderPageLayout({backgroundColor, children, illustration, overlayContent, propsToPassToHeader}: IllustratedHeaderPageLayoutProps) { +function IllustratedHeaderPageLayout({backgroundColor, children, illustration, overlayContent, ...rest}: IllustratedHeaderPageLayoutProps) { const theme = useTheme(); const styles = useThemeStyles(); return ( @@ -42,7 +38,8 @@ function IllustratedHeaderPageLayout({backgroundColor, children, illustration, o } headerContainerStyles={[styles.justifyContentCenter, styles.w100]} - propsToPassToHeader={propsToPassToHeader} + // eslint-disable-next-line react/jsx-props-no-spreading + {...rest} > {children} From bec22a9cfbe9a4b80bfe20ed5439d6f60314f5c4 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Tue, 2 Jan 2024 11:25:03 +0100 Subject: [PATCH 37/69] Fix HeaderPageLayout component --- src/components/HeaderPageLayout.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/HeaderPageLayout.tsx b/src/components/HeaderPageLayout.tsx index 6701d78af901..c4a26c85813a 100644 --- a/src/components/HeaderPageLayout.tsx +++ b/src/components/HeaderPageLayout.tsx @@ -72,12 +72,13 @@ function HeaderPageLayout({ > {/** @ts-expect-error TODO: Remove once ScreenWrapper (https://github.com/Expensify/App/issues/25128) is migrated to TS */} {({safeAreaPaddingBottomStyle}) => ( - + <> + {/** Safari on ios/mac has a bug where overscrolling the page scrollview shows green background color. This is a workaround to fix that. https://github.com/Expensify/App/issues/23422 */} {Browser.isSafari() && ( @@ -95,7 +96,7 @@ function HeaderPageLayout({ {footer !== null && {footer}} - + )} ); From 6ebe31564e9f7f055d27a2f3ea48b21fc3b5bfab Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Tue, 2 Jan 2024 11:31:42 +0100 Subject: [PATCH 38/69] Fix typescript errors --- src/libs/ReportUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 33a5f2f5bb1e..c3acd4f94fb0 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1869,12 +1869,12 @@ function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, field return true; } - const iouMessage: IOUMessage = reportAction?.originalMessage; + const iouMessage = reportAction?.originalMessage as IOUMessage; if (isSettled(String(iouMessage.IOUReportID)) || isReportApproved(String(iouMessage.IOUReportID))) { return false; } - const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${iouMessage?.IOUTransactionID}`] ?? {}; + const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${iouMessage?.IOUTransactionID}`] ?? ({} as Transaction); if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.AMOUNT || fieldToEdit === CONST.EDIT_REQUEST_FIELD.CURRENCY) { return !TransactionUtils.isCardTransaction(transaction); } From 9ba6d1929d601d0e17b1d561f8bfe76d623865a2 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Tue, 2 Jan 2024 11:55:53 +0100 Subject: [PATCH 39/69] Change type for IllustratedHeaderPageLayout --- src/components/HeaderPageLayout.tsx | 3 ++- .../IllustratedHeaderPageLayout.tsx | 21 ++++++++----------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/components/HeaderPageLayout.tsx b/src/components/HeaderPageLayout.tsx index c4a26c85813a..129f93e2b7e1 100644 --- a/src/components/HeaderPageLayout.tsx +++ b/src/components/HeaderPageLayout.tsx @@ -21,7 +21,7 @@ type HeaderPageLayoutProps = ChildrenProps & footer?: ReactNode; /** The image to display in the upper half of the screen. */ - headerContent?: React.ReactNode; + headerContent?: ReactNode; /** Style to apply to the header image container */ headerContainerStyles?: StyleProp; @@ -104,4 +104,5 @@ function HeaderPageLayout({ HeaderPageLayout.displayName = 'HeaderPageLayout'; +export type {HeaderPageLayoutProps}; export default HeaderPageLayout; diff --git a/src/components/IllustratedHeaderPageLayout.tsx b/src/components/IllustratedHeaderPageLayout.tsx index 6ca7d5c0715b..e3f3468ff1ee 100644 --- a/src/components/IllustratedHeaderPageLayout.tsx +++ b/src/components/IllustratedHeaderPageLayout.tsx @@ -1,23 +1,20 @@ import React, {ReactNode} from 'react'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import ChildrenProps from '@src/types/utils/ChildrenProps'; -import HeaderPageLayout from './HeaderPageLayout'; -import HeaderWithBackButtonProps from './HeaderWithBackButton/types'; +import HeaderPageLayout, {HeaderPageLayoutProps} from './HeaderPageLayout'; import Lottie from './Lottie'; import DotLottieAnimation from './LottieAnimations/types'; -type IllustratedHeaderPageLayoutProps = ChildrenProps & - HeaderWithBackButtonProps & { - /** The illustration to display in the header. Can be a JSON object representing a Lottie animation. */ - illustration: DotLottieAnimation; +type IllustratedHeaderPageLayoutProps = HeaderPageLayoutProps & { + /** The illustration to display in the header. Can be a JSON object representing a Lottie animation. */ + illustration: DotLottieAnimation; - /** The background color to apply in the upper half of the screen. */ - backgroundColor?: string; + /** The background color to apply in the upper half of the screen. */ + backgroundColor?: string; - /** Overlay content to display on top of animation */ - overlayContent?: () => ReactNode; - }; + /** Overlay content to display on top of animation */ + overlayContent?: () => ReactNode; +}; function IllustratedHeaderPageLayout({backgroundColor, children, illustration, overlayContent, ...rest}: IllustratedHeaderPageLayoutProps) { const theme = useTheme(); From 60e0c990e48bf3fa9ebf1804557f329047332c87 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 2 Jan 2024 14:52:59 +0100 Subject: [PATCH 40/69] Rerun checks From c5e9c58db6a4bc69edaf1de6bdff555c997a60cd Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Tue, 2 Jan 2024 23:32:18 +0100 Subject: [PATCH 41/69] Fix condition --- src/libs/ReportUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1c86b1715d3d..3ef13015aac3 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1895,9 +1895,9 @@ function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, field return !TransactionUtils.isCardTransaction(transaction); } - if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.RECEIPT) { + if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.RECEIPT && TransactionUtils.hasReceipt(transaction)) { const isRequestor = currentUserAccountID === reportAction?.actorAccountID; - return TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction) && !TransactionUtils.isDistanceRequest(transaction) && isRequestor; + return !TransactionUtils.isReceiptBeingScanned(transaction) && !TransactionUtils.isDistanceRequest(transaction) && isRequestor; } return true; From 145fc6676626d981ddc1ad9d445345fe2a5da884 Mon Sep 17 00:00:00 2001 From: filip-solecki <153545991+filip-solecki@users.noreply.github.com> Date: Wed, 3 Jan 2024 08:26:48 +0100 Subject: [PATCH 42/69] Update src/components/HeaderPageLayout.tsx Co-authored-by: VickyStash --- src/components/HeaderPageLayout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/HeaderPageLayout.tsx b/src/components/HeaderPageLayout.tsx index 129f93e2b7e1..e5eec1c06097 100644 --- a/src/components/HeaderPageLayout.tsx +++ b/src/components/HeaderPageLayout.tsx @@ -79,7 +79,7 @@ function HeaderPageLayout({ titleColor={titleColor} iconFill={iconFill} /> - + {/** Safari on ios/mac has a bug where overscrolling the page scrollview shows green background color. This is a workaround to fix that. https://github.com/Expensify/App/issues/23422 */} {Browser.isSafari() && ( From 8b994d6c532ba172c2ddfbd2e3f557fb1fc6a57a Mon Sep 17 00:00:00 2001 From: filip-solecki <153545991+filip-solecki@users.noreply.github.com> Date: Wed, 3 Jan 2024 08:27:00 +0100 Subject: [PATCH 43/69] Update src/components/HeaderPageLayout.tsx Co-authored-by: VickyStash --- src/components/HeaderPageLayout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/HeaderPageLayout.tsx b/src/components/HeaderPageLayout.tsx index e5eec1c06097..095d39c28f8a 100644 --- a/src/components/HeaderPageLayout.tsx +++ b/src/components/HeaderPageLayout.tsx @@ -94,7 +94,7 @@ function HeaderPageLayout({ {children} - {footer !== null && {footer}} + {!!footer && {footer}} )} From 1bea72cd98d5ca78c6a80e98b2c9c93c4651dd98 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Wed, 3 Jan 2024 14:39:32 +0100 Subject: [PATCH 44/69] Update comment --- src/components/ReportActionItem/MoneyRequestView.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 5b4c98ec0682..b1143d561d29 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -124,6 +124,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const canEditDistance = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE); // A flag for verifying that the current report is a sub-report of a workspace chat + // if the policy of the report is not of type Personal, then this report must be tied to workspace chat const isPolicyExpenseChat = policy.type && policy.type !== CONST.POLICY.TYPE.PERSONAL; // Fetches only the first tag, for now From 22ec1dd3e16c1f6599a6ecb1cf7488812bbba72a Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Wed, 3 Jan 2024 17:04:59 +0100 Subject: [PATCH 45/69] Fi comment --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index bb77466d92d2..bac6d3019ed4 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1888,7 +1888,7 @@ function canEditMoneyRequest(reportAction: OnyxEntry): boolean { const isManager = isExpenseReport(moneyRequestReport) && currentUserAccountID === moneyRequestReport?.managerID; const isRequestor = currentUserAccountID === reportAction?.actorAccountID; - // Admin & managers can always allow coding fields such as tag, category, billable, etc. As long as the report has a state higher than OPEN. + // Admin & managers can always edit coding fields such as tag, category, billable, etc. As long as the report has a state higher than OPEN. if ((isAdmin || isManager) && !isOpenReport(moneyRequestReport)) { return true; } From 7021d27460d72a7e466ed41adc2852d9e6ae2f63 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Wed, 3 Jan 2024 21:47:57 +0100 Subject: [PATCH 46/69] Add policy to proptypes with default --- src/components/ReportActionItem/MoneyRequestView.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index b1143d561d29..6d302ce14eef 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -34,6 +34,7 @@ import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateB 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'; @@ -54,6 +55,9 @@ const propTypes = { /** 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), @@ -69,12 +73,13 @@ const propTypes = { const defaultProps = { parentReport: {}, parentReportActions: {}, - policyCategories: {}, transaction: { amount: 0, currency: CONST.CURRENCY.USD, comment: {comment: ''}, }, + ...policyDefaultProps, + policyCategories: {}, policyTags: {}, }; From a5cdf152aa52062fad53e9b1828c29da83dbb762 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Wed, 3 Jan 2024 21:56:25 +0100 Subject: [PATCH 47/69] Use isGroupPolicy --- src/components/ReportActionItem/MoneyRequestView.js | 4 ++-- src/libs/ReportUtils.ts | 1 + src/pages/EditRequestPage.js | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 6d302ce14eef..595fab237efb 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -129,8 +129,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const canEditDistance = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE); // A flag for verifying that the current report is a sub-report of a workspace chat - // if the policy of the report is not of type Personal, then this report must be tied to workspace chat - const isPolicyExpenseChat = policy.type && policy.type !== CONST.POLICY.TYPE.PERSONAL; + // if the policy of the report is either Collect or Control, then this report must be tied to workspace chat + const isPolicyExpenseChat = ReportUtils.isGroupPolicy(report); // Fetches only the first tag, for now const policyTag = PolicyUtils.getTag(policyTags); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index bac6d3019ed4..1b369e721c0a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4327,6 +4327,7 @@ export { formatReportLastMessageText, chatIncludesConcierge, isPolicyExpenseChat, + isGroupPolicy, isControlPolicyExpenseChat, isControlPolicyExpenseReport, isGroupPolicyExpenseChat, diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index c110fd346ff6..d84477aa8b9d 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -89,8 +89,7 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep const tagListName = PolicyUtils.getTagListName(policyTags); // A flag for verifying that the current report is a sub-report of a workspace chat - const policy = ReportUtils.getPolicy(report.policyID || ''); - const isPolicyExpenseChat = policy.type && policy.type !== CONST.POLICY.TYPE.PERSONAL; + const isPolicyExpenseChat = ReportUtils.isGroupPolicy(report); // A flag for showing the categories page const shouldShowCategories = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); From 4cebe6a365e8791f435a17f06c6aee651a693fff Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 4 Jan 2024 09:47:42 +0100 Subject: [PATCH 48/69] Fix typecheck --- src/components/MenuItem.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index db3a77ccee35..368a3148d0b4 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -78,7 +78,7 @@ type MenuItemProps = (IconProps | AvatarProps | NoIcon) & { titleStyle?: ViewStyle; /** Any adjustments to style when menu item is hovered or pressed */ - hoverAndPressStyle: StyleProp>; + hoverAndPressStyle?: StyleProp>; /** Additional styles to style the description text below the title */ descriptionTextStyle?: StyleProp; @@ -168,7 +168,7 @@ type MenuItemProps = (IconProps | AvatarProps | NoIcon) & { isSelected?: boolean; /** Prop to identify if we should load avatars vertically instead of diagonally */ - shouldStackHorizontally: boolean; + shouldStackHorizontally?: boolean; /** Prop to represent the size of the avatar images to be shown */ avatarSize?: (typeof CONST.AVATAR_SIZE)[keyof typeof CONST.AVATAR_SIZE]; @@ -216,7 +216,7 @@ type MenuItemProps = (IconProps | AvatarProps | NoIcon) & { onSecondaryInteraction?: (event: GestureResponderEvent | MouseEvent) => void; /** Array of objects that map display names to their corresponding tooltip */ - titleWithTooltips: DisplayNameWithTooltip[]; + titleWithTooltips?: DisplayNameWithTooltip[]; /** Icon should be displayed in its own color */ displayInDefaultIconColor?: boolean; From 09a65e6b1e8416d7e5c764ac4615d9109ad120cd Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 4 Jan 2024 09:59:35 +0100 Subject: [PATCH 49/69] Fix type imports --- src/components/MenuItemList.tsx | 5 +++-- src/components/Section/IconSection.tsx | 5 +++-- src/components/Section/index.tsx | 12 +++++++----- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/components/MenuItemList.tsx b/src/components/MenuItemList.tsx index 82097309c580..f83f173a644f 100644 --- a/src/components/MenuItemList.tsx +++ b/src/components/MenuItemList.tsx @@ -1,9 +1,10 @@ import React, {useRef} from 'react'; -import {GestureResponderEvent, View} from 'react-native'; +import type {GestureResponderEvent, View} from 'react-native'; import useSingleExecution from '@hooks/useSingleExecution'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; -import MenuItem, {MenuItemProps} from './MenuItem'; +import type {MenuItemProps} from './MenuItem'; +import MenuItem from './MenuItem'; type MenuItemLink = string | (() => Promise); diff --git a/src/components/Section/IconSection.tsx b/src/components/Section/IconSection.tsx index ddadbcdb1d77..cc42c6b7ace5 100644 --- a/src/components/Section/IconSection.tsx +++ b/src/components/Section/IconSection.tsx @@ -1,8 +1,9 @@ import React from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import Icon from '@components/Icon'; import useThemeStyles from '@hooks/useThemeStyles'; -import IconAsset from '@src/types/utils/IconAsset'; +import type IconAsset from '@src/types/utils/IconAsset'; type IconSectionProps = { icon?: IconAsset; diff --git a/src/components/Section/index.tsx b/src/components/Section/index.tsx index 47f7605d278b..f24316a5f1bb 100644 --- a/src/components/Section/index.tsx +++ b/src/components/Section/index.tsx @@ -1,11 +1,13 @@ import React from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; -import {ValueOf} from 'type-fest'; -import MenuItemList, {MenuItemWithLink} from '@components/MenuItemList'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; +import type {ValueOf} from 'type-fest'; +import type {MenuItemWithLink} from '@components/MenuItemList'; +import MenuItemList from '@components/MenuItemList'; import Text from '@components/Text'; import useThemeStyles from '@hooks/useThemeStyles'; -import ChildrenProps from '@src/types/utils/ChildrenProps'; -import IconAsset from '@src/types/utils/IconAsset'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; +import type IconAsset from '@src/types/utils/IconAsset'; import IconSection from './IconSection'; const CARD_LAYOUT = { From 5ac97d039516c45764e57586e1138f9466cdd021 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Thu, 4 Jan 2024 10:00:01 +0100 Subject: [PATCH 50/69] Remove ts-expect-error --- src/components/HeaderPageLayout.tsx | 12 ++++++------ src/components/IllustratedHeaderPageLayout.tsx | 8 +++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/HeaderPageLayout.tsx b/src/components/HeaderPageLayout.tsx index 095d39c28f8a..304bb2ce49b1 100644 --- a/src/components/HeaderPageLayout.tsx +++ b/src/components/HeaderPageLayout.tsx @@ -1,15 +1,17 @@ -import React, {ReactNode, useMemo} from 'react'; -import {ScrollView, StyleProp, View, ViewStyle} from 'react-native'; +import React, {useMemo} from 'react'; +import type {ReactNode} from 'react'; +import {ScrollView, View} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as Browser from '@libs/Browser'; -import ChildrenProps from '@src/types/utils/ChildrenProps'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; import FixedFooter from './FixedFooter'; import HeaderWithBackButton from './HeaderWithBackButton'; -import HeaderWithBackButtonProps from './HeaderWithBackButton/types'; +import type HeaderWithBackButtonProps from './HeaderWithBackButton/types'; import ScreenWrapper from './ScreenWrapper'; type HeaderPageLayoutProps = ChildrenProps & @@ -62,7 +64,6 @@ function HeaderPageLayout({ }, [backgroundColor, theme.appBG, theme.highlightBG, theme.iconColorfulBackground, theme.textColorfulBackground]); return ( - // @ts-expect-error TODO: Remove once ScreenWrapper (https://github.com/Expensify/App/issues/25128) is migrated to TS - {/** @ts-expect-error TODO: Remove once ScreenWrapper (https://github.com/Expensify/App/issues/25128) is migrated to TS */} {({safeAreaPaddingBottomStyle}) => ( <> Date: Thu, 4 Jan 2024 23:41:14 +0100 Subject: [PATCH 51/69] Cleanup --- src/components/ReportActionItem/MoneyRequestView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 5324fe5c6cdf..a63a9c33a046 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -123,7 +123,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); const {canUseViolations} = usePermissions(); - const parentReportAction = useMemo(() => parentReportActions[report.parentReportActionID] || {}, [parentReportActions, report.parentReportActionID]); + const parentReportAction = parentReportActions[report.parentReportActionID] || {}; const moneyRequestReport = parentReport; const { created: transactionDate, From 7de81d4cab0cd3292812a911be6e868fc676f3d1 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Thu, 4 Jan 2024 23:51:41 +0100 Subject: [PATCH 52/69] Cleanup --- src/components/ReportActionItem/MoneyRequestView.js | 2 +- src/libs/ReportUtils.ts | 2 +- src/pages/iou/IOUCurrencySelection.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index a63a9c33a046..37ff163f23c8 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -1,7 +1,7 @@ import lodashGet from 'lodash/get'; import lodashValues from 'lodash/values'; import PropTypes from 'prop-types'; -import React, {useCallback, useMemo} from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import categoryPropTypes from '@components/categoryPropTypes'; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b11ba5c7f9c3..8fe4f729e607 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1923,7 +1923,7 @@ function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, field } const iouMessage = reportAction?.originalMessage as IOUMessage; - const moneyRequestReport = (allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouMessage?.IOUReportID}`] ?? {}) as Report; + const moneyRequestReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouMessage?.IOUReportID}`] ?? ({} as Report); const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${iouMessage?.IOUTransactionID}`] ?? ({} as Transaction); if (isSettled(String(moneyRequestReport.reportID)) || isReportApproved(String(moneyRequestReport.reportID))) { diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index 29580dec4bef..24833fc96fdc 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -94,7 +94,7 @@ function IOUCurrencySelection(props) { Navigation.isNavigationReady().then(() => { Navigation.dismissModal(); }); - }, [threadReportID, props.transaction]); + }, [threadReportID]); const confirmCurrencySelection = useCallback( (option) => { From 4123a3106f41fc0576f75bb8050f6577fb879481 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Fri, 5 Jan 2024 00:06:07 +0100 Subject: [PATCH 53/69] Remove unneeded check --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8fe4f729e607..7df2e9c0c2ee 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1944,7 +1944,7 @@ function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, field } } - if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.RECEIPT && TransactionUtils.hasReceipt(transaction)) { + if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.RECEIPT) { const isRequestor = currentUserAccountID === reportAction?.actorAccountID; return !TransactionUtils.isReceiptBeingScanned(transaction) && !TransactionUtils.isDistanceRequest(transaction) && isRequestor; } From c23db7c4d56952913f7e4e035ecd850b55240624 Mon Sep 17 00:00:00 2001 From: Aswin S Date: Fri, 5 Jan 2024 08:20:19 +0530 Subject: [PATCH 54/69] fix: tab header labels getting clipped on font scaling --- src/components/TabSelector/TabLabel.tsx | 3 ++- src/styles/index.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/TabSelector/TabLabel.tsx b/src/components/TabSelector/TabLabel.tsx index 40f4dc30bb97..548b4ebccbc8 100644 --- a/src/components/TabSelector/TabLabel.tsx +++ b/src/components/TabSelector/TabLabel.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import {Animated, StyleSheet, Text, View} from 'react-native'; +import {Animated, StyleSheet, View} from 'react-native'; +import Text from '@components/Text'; import useThemeStyles from '@hooks/useThemeStyles'; type TabLabelProps = { diff --git a/src/styles/index.ts b/src/styles/index.ts index 260f6498e8bb..a74d431f59d8 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3709,7 +3709,8 @@ const styles = (theme: ThemeColors) => fontFamily: isSelected ? fontFamily.EXP_NEUE_BOLD : fontFamily.EXP_NEUE, fontWeight: isSelected ? fontWeightBold : '400', color: isSelected ? theme.text : theme.textSupporting, - lineHeight: 14, + lineHeight: variables.lineHeightNormal, + fontSize: variables.fontSizeNormal, } satisfies TextStyle), tabBackground: (hovered: boolean, isFocused: boolean, background: string | Animated.AnimatedInterpolation) => ({ From e261836b80eb8970d597861948af75648edf5939 Mon Sep 17 00:00:00 2001 From: Aswin S Date: Fri, 5 Jan 2024 08:57:26 +0530 Subject: [PATCH 55/69] fix: tab button height --- src/styles/variables.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/variables.ts b/src/styles/variables.ts index 4d717389cdb6..83096eb5a25e 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -142,7 +142,7 @@ export default { signInLogoWidthLargeScreen: 144, signInLogoHeightLargeScreen: 108, signInLogoWidthPill: 132, - tabSelectorButtonHeight: 40, + tabSelectorButtonHeight: 42, tabSelectorButtonPadding: 12, lhnLogoWidth: 95.09, lhnLogoHeight: 22.33, From a70f818a5b85ca4cbf5351313c527f10b394d1de Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 4 Jan 2024 22:38:23 -0800 Subject: [PATCH 56/69] Don't skip successData or failureData --- src/libs/actions/OnyxUpdates.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index 2291e6d0af4a..a0772db49585 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -7,6 +7,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {OnyxUpdateEvent, OnyxUpdatesFromServer, Request} from '@src/types/onyx'; import type Response from '@src/types/onyx/Response'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import * as QueuedOnyxUpdates from './QueuedOnyxUpdates'; // This key needs to be separate from ONYXKEYS.ONYX_UPDATES_FROM_SERVER so that it can be updated without triggering the callback when the server IDs are updated. If that @@ -74,7 +75,18 @@ function apply({lastUpdateID, type, request, response, updates}: OnyxUpdatesFrom Log.info(`[OnyxUpdateManager] Applying update type: ${type} with lastUpdateID: ${lastUpdateID}`, false, {command: request?.command}); if (lastUpdateID && lastUpdateIDAppliedToClient && Number(lastUpdateID) <= lastUpdateIDAppliedToClient) { - Log.info('[OnyxUpdateManager] Update received was older or the same than current state, returning without applying the updates', false); + Log.info('[OnyxUpdateManager] Update received was older than or the same as current state, returning without applying the updates other than successData and failureData'); + + // In this case, we're already received the OnyxUpdate included in the response, so we don't need to apply it again. + // However, we do need to apply the successData and failureData from the request + if (type === CONST.ONYX_UPDATE_TYPES.HTTPS && request && response && (!isEmptyObject(request.successData) || !isEmptyObject(request.failureData))) { + Log.info('[OnyxUpdateManager] Applying success or failure data from request without onyxData from response'); + + // We use a spread here instead of delete because we don't want to change the response for other middlewares + const {onyxData, ...responseWithoutOnyxData} = response; + return applyHTTPSOnyxUpdates(request, responseWithoutOnyxData); + } + return Promise.resolve(); } if (lastUpdateID && (lastUpdateIDAppliedToClient === null || Number(lastUpdateID) > lastUpdateIDAppliedToClient)) { From 85ba4aef2b54cf7152f31cd82a84e48b4fea1f1a Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Fri, 5 Jan 2024 14:31:26 +0100 Subject: [PATCH 57/69] fix: lint issues with types import --- src/components/HoldMenuSectionList.tsx | 6 +++--- src/components/ProcessMoneyRequestHoldMenu.tsx | 4 ++-- src/components/TextPill.tsx | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/HoldMenuSectionList.tsx b/src/components/HoldMenuSectionList.tsx index 9a9857f037f2..145de4eb392d 100644 --- a/src/components/HoldMenuSectionList.tsx +++ b/src/components/HoldMenuSectionList.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import {ImageSourcePropType, View} from 'react-native'; -import {SvgProps} from 'react-native-svg'; +import {type ImageSourcePropType, View} from 'react-native'; +import type {SvgProps} from 'react-native-svg'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; -import {TranslationPaths} from '@src/languages/types'; +import type {TranslationPaths} from '@src/languages/types'; import Icon from './Icon'; import * as Illustrations from './Icon/Illustrations'; import Text from './Text'; diff --git a/src/components/ProcessMoneyRequestHoldMenu.tsx b/src/components/ProcessMoneyRequestHoldMenu.tsx index be72fdb98a8b..1b711633ed3b 100644 --- a/src/components/ProcessMoneyRequestHoldMenu.tsx +++ b/src/components/ProcessMoneyRequestHoldMenu.tsx @@ -4,9 +4,9 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Button from './Button'; import HoldMenuSectionList from './HoldMenuSectionList'; -import {PopoverAnchorPosition} from './Modal/types'; +import type {PopoverAnchorPosition} from './Modal/types'; import Popover from './Popover'; -import {AnchorAlignment} from './Popover/types'; +import type {AnchorAlignment} from './Popover/types'; import Text from './Text'; import TextPill from './TextPill'; diff --git a/src/components/TextPill.tsx b/src/components/TextPill.tsx index 035ae1dd42d8..6d473b189534 100644 --- a/src/components/TextPill.tsx +++ b/src/components/TextPill.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {StyleProp, TextStyle} from 'react-native'; +import type {StyleProp, TextStyle} from 'react-native'; // eslint-disable-next-line no-restricted-imports import useThemeStyles from '@hooks/useThemeStyles'; import colors from '@styles/theme/colors'; From 3d77a96fe12ec4579790ca264ee9093bebd72d76 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Fri, 5 Jan 2024 14:39:27 +0100 Subject: [PATCH 58/69] fix: remove inline type specifier --- src/components/HoldMenuSectionList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/HoldMenuSectionList.tsx b/src/components/HoldMenuSectionList.tsx index 145de4eb392d..f5c7e8cf3b94 100644 --- a/src/components/HoldMenuSectionList.tsx +++ b/src/components/HoldMenuSectionList.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {type ImageSourcePropType, View} from 'react-native'; +import {ImageSourcePropType, View} from 'react-native'; import type {SvgProps} from 'react-native-svg'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; From 02f7caa997507227c0494c8a9decb4ba3ecefd8d Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Fri, 5 Jan 2024 14:46:07 +0100 Subject: [PATCH 59/69] fix: split imports for components & types --- src/components/HoldMenuSectionList.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/HoldMenuSectionList.tsx b/src/components/HoldMenuSectionList.tsx index f5c7e8cf3b94..aa5dd75ce159 100644 --- a/src/components/HoldMenuSectionList.tsx +++ b/src/components/HoldMenuSectionList.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import {ImageSourcePropType, View} from 'react-native'; +import {View} from 'react-native'; +import type {ImageSourcePropType} from 'react-native'; import type {SvgProps} from 'react-native-svg'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; From a130cb381a61b785556841f58c41a52961cf82b0 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Fri, 5 Jan 2024 16:15:29 +0100 Subject: [PATCH 60/69] Update src/libs/ReportUtils.ts Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 7df2e9c0c2ee..0117694addb3 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1859,7 +1859,7 @@ function getTransactionDetails(transaction: OnyxEntry, createdDateF * - the current user is the manager of the report * - or the current user is an admin on the policy the expense report is tied to * - * This is used in conjuction with canEditRestrictedField to control editing of specific fields like amount, currency, created, receipt, and distance. + * This is used in conjunction with canEditRestrictedField to control editing of specific fields like amount, currency, created, receipt, and distance. * On its own, it only controls allowing/disallowing navigating to the editing pages or showing/hiding the 'Edit' icon on report actions */ function canEditMoneyRequest(reportAction: OnyxEntry): boolean { From b6ba955c90b210f78120fe94f326ae3d686de265 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Fri, 5 Jan 2024 16:17:54 +0100 Subject: [PATCH 61/69] use isDraftExpenseReport --- src/libs/ReportUtils.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 0117694addb3..2956f16da697 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -608,7 +608,7 @@ function isReportApproved(reportOrID: OnyxEntry | string | EmptyObject): /** * Checks if the supplied report is an expense report in Open state and status. */ -function isDraftExpenseReport(report: OnyxEntry): boolean { +function isDraftExpenseReport(report: OnyxEntry | EmptyObject): boolean { return isExpenseReport(report) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS.OPEN; } @@ -822,13 +822,6 @@ function isProcessingReport(report: OnyxEntry | EmptyObject): boolean { return report?.stateNum === CONST.REPORT.STATE_NUM.PROCESSING && report?.statusNum === CONST.REPORT.STATUS.SUBMITTED; } -/** - * Returns true if report is still open - */ -function isOpenReport(report: OnyxEntry | EmptyObject): boolean { - return report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS.OPEN; -} - /** * Check if the report is a single chat report that isn't a thread * and personal detail of participant is optimistic data @@ -1891,7 +1884,7 @@ function canEditMoneyRequest(reportAction: OnyxEntry): boolean { const isRequestor = currentUserAccountID === reportAction?.actorAccountID; // Admin & managers can always edit coding fields such as tag, category, billable, etc. As long as the report has a state higher than OPEN. - if ((isAdmin || isManager) && !isOpenReport(moneyRequestReport)) { + if ((isAdmin || isManager) && !isDraftExpenseReport(moneyRequestReport)) { return true; } From 1707e031ec7f54ea8fac491b8fc96c2288e891ba Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Fri, 5 Jan 2024 16:19:37 +0100 Subject: [PATCH 62/69] Move variables into the scope where they're needed --- src/libs/ReportUtils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 2956f16da697..a3aedf0e57a9 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1928,11 +1928,11 @@ function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, field return false; } - const policy = getPolicy(moneyRequestReport?.reportID ?? ''); - const isAdmin = isExpenseReport(moneyRequestReport) && policy.role === CONST.POLICY.ROLE.ADMIN; - const isManager = isExpenseReport(moneyRequestReport) && currentUserAccountID === moneyRequestReport?.managerID; - if (TransactionUtils.isDistanceRequest(transaction)) { + const policy = getPolicy(moneyRequestReport?.reportID ?? ''); + const isAdmin = isExpenseReport(moneyRequestReport) && policy.role === CONST.POLICY.ROLE.ADMIN; + const isManager = isExpenseReport(moneyRequestReport) && currentUserAccountID === moneyRequestReport?.managerID; + return isAdmin || isManager; } } From 8491e868729b4cb1a3c9c4bd7eee74213d066c0f Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Fri, 5 Jan 2024 17:23:47 +0100 Subject: [PATCH 63/69] Rename methods --- src/components/MoneyReportHeader.js | 8 +++--- .../ReportActionItem/MoneyRequestPreview.js | 2 +- .../ReportActionItem/ReportPreview.js | 10 +++---- src/libs/ReportUtils.ts | 27 ++++++++++++------- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/components/MoneyReportHeader.js b/src/components/MoneyReportHeader.js index 8ed6d0746438..5b59fca6cdae 100644 --- a/src/components/MoneyReportHeader.js +++ b/src/components/MoneyReportHeader.js @@ -87,9 +87,9 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const policyType = lodashGet(policy, 'type'); const isPolicyAdmin = policyType !== CONST.POLICY.TYPE.PERSONAL && lodashGet(policy, 'role') === CONST.POLICY.ROLE.ADMIN; - const isGroupPolicy = _.contains([CONST.POLICY.TYPE.CORPORATE, CONST.POLICY.TYPE.TEAM], policyType); + const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicy(moneyRequestReport); const isManager = ReportUtils.isMoneyRequestReport(moneyRequestReport) && lodashGet(session, 'accountID', null) === moneyRequestReport.managerID; - const isPayer = isGroupPolicy + const isPayer = isPaidGroupPolicy ? // In a group policy, the admin approver can pay the report directly by skipping the approval step isPolicyAdmin && (isApproved || isManager) : isPolicyAdmin || (ReportUtils.isMoneyRequestReport(moneyRequestReport) && isManager); @@ -99,11 +99,11 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt [isPayer, isDraft, isSettled, moneyRequestReport, reimbursableTotal, chatReport], ); const shouldShowApproveButton = useMemo(() => { - if (!isGroupPolicy) { + if (!isPaidGroupPolicy) { return false; } return isManager && !isDraft && !isApproved && !isSettled; - }, [isGroupPolicy, isManager, isDraft, isApproved, isSettled]); + }, [isPaidGroupPolicy, isManager, isDraft, isApproved, isSettled]); const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton; const shouldShowSubmitButton = isDraft && reimbursableTotal !== 0; const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index d425e236431b..c052a885245f 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -210,7 +210,7 @@ function MoneyRequestPreview(props) { } let message = translate('iou.cash'); - if (ReportUtils.isGroupPolicyExpenseReport(props.iouReport) && ReportUtils.isReportApproved(props.iouReport) && !ReportUtils.isSettled(props.iouReport)) { + if (ReportUtils.isPaidGroupPolicyExpenseReport(props.iouReport) && ReportUtils.isReportApproved(props.iouReport) && !ReportUtils.isSettled(props.iouReport)) { message += ` • ${translate('iou.approved')}`; } else if (props.iouReport.isWaitingOnBankAccount) { message += ` • ${translate('iou.pending')}`; diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index e88c057a615d..27447a10a32b 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -243,10 +243,10 @@ function ReportPreview(props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const isGroupPolicy = ReportUtils.isGroupPolicyExpenseChat(props.chatReport); + const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(props.chatReport); const isPolicyAdmin = policyType !== CONST.POLICY.TYPE.PERSONAL && lodashGet(props.policy, 'role') === CONST.POLICY.ROLE.ADMIN; - const isPayer = isGroupPolicy - ? // In a group policy, the admin approver can pay the report directly by skipping the approval step + const isPayer = isPaidGroupPolicy + ? // In a paid group policy, the admin approver can pay the report directly by skipping the approval step isPolicyAdmin && (isApproved || isCurrentUserManager) : isPolicyAdmin || (isMoneyRequestReport && isCurrentUserManager); const shouldShowPayButton = useMemo( @@ -254,11 +254,11 @@ function ReportPreview(props) { [isPayer, isDraftExpenseReport, iouSettled, reimbursableSpend, iouCanceled, props.iouReport], ); const shouldShowApproveButton = useMemo(() => { - if (!isGroupPolicy) { + if (!isPaidGroupPolicy) { return false; } return isCurrentUserManager && !isDraftExpenseReport && !isApproved && !iouSettled; - }, [isGroupPolicy, isCurrentUserManager, isDraftExpenseReport, isApproved, iouSettled]); + }, [isPaidGroupPolicy, isCurrentUserManager, isDraftExpenseReport, isApproved, iouSettled]); const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton; return ( diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a3aedf0e57a9..165fd611da44 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -609,7 +609,7 @@ function isReportApproved(reportOrID: OnyxEntry | string | EmptyObject): * Checks if the supplied report is an expense report in Open state and status. */ function isDraftExpenseReport(report: OnyxEntry | EmptyObject): boolean { - return isExpenseReport(report) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS.OPEN; + return report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS.OPEN; } /** @@ -715,9 +715,17 @@ function isControlPolicyExpenseChat(report: OnyxEntry): boolean { } /** - * Whether the provided report belongs to a Control or Collect policy + * Whether the provided report belongs to a Free, Collect or Control policy */ function isGroupPolicy(report: OnyxEntry): boolean { + const policyType = getPolicyType(report, allPolicies); + return policyType === CONST.POLICY.TYPE.CORPORATE || policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.FREE; +} + +/** + * Whether the provided report belongs to a Control or Collect policy + */ +function isPaidGroupPolicy(report: OnyxEntry): boolean { const policyType = getPolicyType(report, allPolicies); return policyType === CONST.POLICY.TYPE.CORPORATE || policyType === CONST.POLICY.TYPE.TEAM; } @@ -725,8 +733,8 @@ function isGroupPolicy(report: OnyxEntry): boolean { /** * Whether the provided report belongs to a Control or Collect policy and is an expense chat */ -function isGroupPolicyExpenseChat(report: OnyxEntry): boolean { - return isPolicyExpenseChat(report) && isGroupPolicy(report); +function isPaidGroupPolicyExpenseChat(report: OnyxEntry): boolean { + return isPolicyExpenseChat(report) && isPaidGroupPolicy(report); } /** @@ -739,8 +747,8 @@ function isControlPolicyExpenseReport(report: OnyxEntry): boolean { /** * Whether the provided report belongs to a Control or Collect policy and is an expense report */ -function isGroupPolicyExpenseReport(report: OnyxEntry): boolean { - return isExpenseReport(report) && isGroupPolicy(report); +function isPaidGroupPolicyExpenseReport(report: OnyxEntry): boolean { + return isExpenseReport(report) && isPaidGroupPolicy(report); } /** @@ -2082,7 +2090,7 @@ function getReportPreviewMessage( const formattedAmount = CurrencyUtils.convertToDisplayString(totalAmount, report.currency); - if (isReportApproved(report) && isGroupPolicy(report)) { + if (isReportApproved(report) && isPaidGroupPolicy(report)) { return Localize.translateLocal('iou.managerApprovedAmount', { manager: payerName ?? '', amount: formattedAmount, @@ -4335,10 +4343,11 @@ export { chatIncludesConcierge, isPolicyExpenseChat, isGroupPolicy, + isPaidGroupPolicy, isControlPolicyExpenseChat, isControlPolicyExpenseReport, - isGroupPolicyExpenseChat, - isGroupPolicyExpenseReport, + isPaidGroupPolicyExpenseChat, + isPaidGroupPolicyExpenseReport, getIconsForParticipants, getIcons, getRoomWelcomeMessage, From 3525c5e6f0e7a6e8ae335c44ea3e5569d7ed7313 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Fri, 5 Jan 2024 18:18:35 +0100 Subject: [PATCH 64/69] Add code removed by mistake --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 165fd611da44..cee7a56d520b 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -609,7 +609,7 @@ function isReportApproved(reportOrID: OnyxEntry | string | EmptyObject): * Checks if the supplied report is an expense report in Open state and status. */ function isDraftExpenseReport(report: OnyxEntry | EmptyObject): boolean { - return report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS.OPEN; + return isExpenseReport(report) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS.OPEN; } /** From e40dcd6ebb7c1d054d69eeb01a3428f29ebe4625 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Fri, 5 Jan 2024 18:49:22 +0100 Subject: [PATCH 65/69] Skip the two report screen reassure tests --- tests/perf-test/ReportScreen.perf-test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/perf-test/ReportScreen.perf-test.js b/tests/perf-test/ReportScreen.perf-test.js index a82903762631..88f3fe99b347 100644 --- a/tests/perf-test/ReportScreen.perf-test.js +++ b/tests/perf-test/ReportScreen.perf-test.js @@ -153,7 +153,7 @@ function ReportScreenWrapper(args) { const runs = CONST.PERFORMANCE_TESTS.RUNS; -test('[ReportScreen] should render ReportScreen with composer interactions', () => { +test.skip('[ReportScreen] should render ReportScreen with composer interactions', () => { const {triggerTransitionEnd, addListener} = createAddListenerMock(); const scenario = async () => { /** @@ -226,7 +226,7 @@ test('[ReportScreen] should render ReportScreen with composer interactions', () ); }); -test('[ReportScreen] should press of the report item', () => { +test.skip('[ReportScreen] should press of the report item', () => { const {triggerTransitionEnd, addListener} = createAddListenerMock(); const scenario = async () => { /** From 316b71832ac00b310034775e96f067ee95fd891f Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Fri, 5 Jan 2024 18:50:10 +0100 Subject: [PATCH 66/69] Make code clearer --- src/libs/ReportUtils.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index cee7a56d520b..baf8c460fd2a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1886,11 +1886,16 @@ function canEditMoneyRequest(reportAction: OnyxEntry): boolean { } const moneyRequestReport = getReport(String(moneyRequestReportID)); - const policy = getPolicy(moneyRequestReport?.policyID ?? ''); - const isAdmin = isExpenseReport(moneyRequestReport) && policy.role === CONST.POLICY.ROLE.ADMIN; - const isManager = isExpenseReport(moneyRequestReport) && currentUserAccountID === moneyRequestReport?.managerID; const isRequestor = currentUserAccountID === reportAction?.actorAccountID; + if (isIOUReport(moneyRequestReport)) { + return isProcessingReport(moneyRequestReport) && isRequestor; + } + + const policy = getPolicy(moneyRequestReport?.policyID ?? ''); + const isAdmin = policy.role === CONST.POLICY.ROLE.ADMIN; + const isManager = currentUserAccountID === moneyRequestReport?.managerID; + // Admin & managers can always edit coding fields such as tag, category, billable, etc. As long as the report has a state higher than OPEN. if ((isAdmin || isManager) && !isDraftExpenseReport(moneyRequestReport)) { return true; From cb007479ec523ded9c751797fefa7848e8bf5849 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Fri, 5 Jan 2024 21:20:58 +0000 Subject: [PATCH 67/69] Update version to 1.4.22-1 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index aaa6aaeb7a78..13f9dba2bbe6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -96,8 +96,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001042200 - versionName "1.4.22-0" + versionCode 1001042201 + versionName "1.4.22-1" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index ecf3b8c9cad9..dd86b6fd7bd5 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.22.0 + 1.4.22.1 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 1ec1aeb6ce14..392403d19961 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.22.0 + 1.4.22.1 diff --git a/package-lock.json b/package-lock.json index 2c1da1670e19..f30d9bc84ec0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.22-0", + "version": "1.4.22-1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.22-0", + "version": "1.4.22-1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 118f63e45879..308e5808dd05 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.22-0", + "version": "1.4.22-1", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 548830a07e57985e6cbf86938fafe974c171e885 Mon Sep 17 00:00:00 2001 From: Pujan Date: Sat, 6 Jan 2024 05:03:25 +0530 Subject: [PATCH 68/69] safe check for isTaxTrackingEnabled --- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 7ec95aec951f..2fee67a3d632 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -277,7 +277,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const shouldShowTags = isPolicyExpenseChat && OptionsListUtils.hasEnabledOptions(_.values(policyTagList)); // A flag for showing tax rate - const shouldShowTax = isPolicyExpenseChat && policy.isTaxTrackingEnabled; + const shouldShowTax = isPolicyExpenseChat && policy && policy.isTaxTrackingEnabled; // A flag for showing the billable field const shouldShowBillable = !lodashGet(policy, 'disabledFields.defaultBillable', true); From 50f20108cc02a89932df7a96056f0aef07558c1b Mon Sep 17 00:00:00 2001 From: OSBotify Date: Sat, 6 Jan 2024 00:31:15 +0000 Subject: [PATCH 69/69] Update version to 1.4.22-2 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 13f9dba2bbe6..ddef060e3a96 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -96,8 +96,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001042201 - versionName "1.4.22-1" + versionCode 1001042202 + versionName "1.4.22-2" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index dd86b6fd7bd5..1c4a33d37b91 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.22.1 + 1.4.22.2 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 392403d19961..31fdafe32980 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.22.1 + 1.4.22.2 diff --git a/package-lock.json b/package-lock.json index f30d9bc84ec0..065fc0055291 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.22-1", + "version": "1.4.22-2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.22-1", + "version": "1.4.22-2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 308e5808dd05..f6d401a8266b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.22-1", + "version": "1.4.22-2", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",