From 99027f8ae0e81c3ef79ac7dd7945bf5631a32b97 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 12 Apr 2023 13:51:32 +0200 Subject: [PATCH 001/329] add delete to iou context menu --- src/libs/ReportUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 4e7305a6d2f7..300a8ca7fcaa 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -158,7 +158,7 @@ function canEditReportAction(reportAction) { */ function canDeleteReportAction(reportAction) { return reportAction.actorEmail === sessionEmail - && reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT + && _.contains([CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, CONST.REPORT.ACTIONS.TYPE.IOU], reportAction.actionName) && reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; } From 9edaa871b8c894fa1d52002ea70e926c54156f63 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 12 Apr 2023 14:07:41 +0200 Subject: [PATCH 002/329] update canDeleteReportAction --- src/libs/ReportUtils.js | 7 ++++--- src/pages/home/report/ContextMenu/ContextMenuActions.js | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 300a8ca7fcaa..cd9792dacc72 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -151,14 +151,15 @@ function canEditReportAction(reportAction) { } /** - * Can only delete if it's an ADDCOMMENT, the author is this user. + * Can only delete if the author is this user and the action is an ADDCOMMENT action or an IOU action in an unsettled report * * @param {Object} reportAction + * @param {Boolean} isReportSettled * @returns {Boolean} */ -function canDeleteReportAction(reportAction) { +function canDeleteReportAction(reportAction, isReportSettled) { return reportAction.actorEmail === sessionEmail - && _.contains([CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, CONST.REPORT.ACTIONS.TYPE.IOU], reportAction.actionName) + && (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT || (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && !isReportSettled)) && reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; } diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 385fee477f7b..f2a3e25eede6 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -237,8 +237,8 @@ export default [ { textTranslateKey: 'reportActionContextMenu.deleteComment', icon: Expensicons.Trashcan, - shouldShow: (type, reportAction, isArchivedRoom, betas, menuTarget, isChronosReport) => type === CONTEXT_MENU_TYPES.REPORT_ACTION - && ReportUtils.canDeleteReportAction(reportAction) && !isArchivedRoom && !isChronosReport, + shouldShow: (type, reportAction, isArchivedRoom, betas, menuTarget, isChronosReport, isReportSettled) => type === CONTEXT_MENU_TYPES.REPORT_ACTION + && ReportUtils.canDeleteReportAction(reportAction, isReportSettled) && !isArchivedRoom && !isChronosReport, onPress: (closePopover, {reportID, reportAction}) => { if (closePopover) { // Hide popover, then call showDeleteConfirmModal From e6948b0505dee06dde2dc38c949b6248dfdf6744 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 12 Apr 2023 14:10:17 +0200 Subject: [PATCH 003/329] add isReportSettled prop --- .../home/report/ContextMenu/BaseReportActionContextMenu.js | 5 +++++ src/pages/home/report/ReportActionItem.js | 1 + 2 files changed, 6 insertions(+) diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js index e562a417d423..6f802c229fd4 100755 --- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js @@ -27,6 +27,9 @@ const propTypes = { /** Whether the provided report is an archived room */ isArchivedRoom: PropTypes.bool, + /** Whether the provided report has been reimbursed */ + isReportSettled: PropTypes.bool, + contentRef: PropTypes.oneOfType([PropTypes.node, PropTypes.object, PropTypes.func]), ...genericReportActionContextMenuPropTypes, @@ -40,6 +43,7 @@ const defaultProps = { contentRef: null, isChronosReport: false, isArchivedRoom: false, + isReportSettled: false, ...GenericReportActionContextMenuDefaultProps, }; class BaseReportActionContextMenu extends React.Component { @@ -60,6 +64,7 @@ class BaseReportActionContextMenu extends React.Component { this.props.betas, this.props.anchor, this.props.isChronosReport, + this.props.isReportSettled, ); return (this.props.isVisible || this.state.shouldKeepOpen) && ( diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 8cb3bd38e12d..48b5bca48d74 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -298,6 +298,7 @@ class ReportActionItem extends Component { } draftMessage={this.props.draftMessage} isChronosReport={ReportUtils.chatIncludesChronos(this.props.report)} + isReportSettled={this.props.report.status === CONST.REPORT.REPORT.STATUS.REIMBURSED} /> )} From b12bf1d7b0a0821639e2ace1092d83b543197792 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 12 Apr 2023 14:15:19 +0200 Subject: [PATCH 004/329] add isReportSettled param to context menu --- .../report/ContextMenu/PopoverReportActionContextMenu.js | 7 +++++++ .../home/report/ContextMenu/ReportActionContextMenu.js | 3 +++ 2 files changed, 10 insertions(+) diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 4883d2f37aa7..154a96013ecf 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -39,6 +39,7 @@ class PopoverReportActionContextMenu extends React.Component { }, isArchivedRoom: false, isChronosReport: false, + isReportSettled: false, }; this.onPopoverShow = () => {}; this.onPopoverHide = () => {}; @@ -125,6 +126,7 @@ class PopoverReportActionContextMenu extends React.Component { * @param {Function} [onHide] - Run a callback when Menu is hidden * @param {Boolean} isArchivedRoom - Whether the provided report is an archived room * @param {Boolean} isChronosReport - Flag to check if the chat participant is Chronos + * @param {Boolean} isReportSettled - Whether the provided report has been reimbursed */ showContextMenu( type, @@ -138,6 +140,7 @@ class PopoverReportActionContextMenu extends React.Component { onHide = () => {}, isArchivedRoom, isChronosReport, + isReportSettled, ) { const nativeEvent = event.nativeEvent || {}; this.contextMenuAnchor = contextMenuAnchor; @@ -168,6 +171,7 @@ class PopoverReportActionContextMenu extends React.Component { reportActionDraftMessage: draftMessage, isArchivedRoom, isChronosReport, + isReportSettled, }); }); } @@ -242,6 +246,7 @@ class PopoverReportActionContextMenu extends React.Component { reportAction={this.state.reportAction} isArchivedRoom={this.state.isArchivedRoom} isChronosReport={this.state.isChronosReport} + isReportSettled={this.state.isReportSettled} anchor={this.contextMenuTargetNode} contentRef={this.setContentRef} /> @@ -273,6 +278,7 @@ class PopoverReportActionContextMenu extends React.Component { shouldSetModalVisibilityForDeleteConfirmation: true, isArchivedRoom: false, isChronosReport: false, + isReportSettled: false, }); } @@ -319,6 +325,7 @@ class PopoverReportActionContextMenu extends React.Component { draftMessage={this.state.reportActionDraftMessage} isArchivedRoom={this.state.isArchivedRoom} isChronosReport={this.state.isChronosReport} + isReportSettled={this.state.isReportSettled} anchor={this.contextMenuTargetNode} contentRef={this.contentRef} /> diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js index df544f2e7202..c83549cabb81 100644 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js @@ -16,6 +16,7 @@ const contextMenuRef = React.createRef(); * @param {Function} [onHide=() => {}] - Run a callback when Menu is hidden * @param {Boolean} isArchivedRoom - Whether the provided report is an archived room * @param {Boolean} isChronosReport - Flag to check if the chat participant is Chronos + * @param {Boolean} isReportSettled - Whether the provided report has been reimbursed */ function showContextMenu( type, @@ -29,6 +30,7 @@ function showContextMenu( onHide = () => {}, isArchivedRoom = false, isChronosReport = false, + isReportSettled = false, ) { if (!contextMenuRef.current) { return; @@ -45,6 +47,7 @@ function showContextMenu( onHide, isArchivedRoom, isChronosReport, + isReportSettled, ); } From 012f2e5fa6b84323b9dc2c52bbca7c824c0c1b01 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 12 Apr 2023 14:25:23 +0200 Subject: [PATCH 005/329] fix const typo --- src/pages/home/report/ReportActionItem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 48b5bca48d74..3453b735f33b 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -298,7 +298,7 @@ class ReportActionItem extends Component { } draftMessage={this.props.draftMessage} isChronosReport={ReportUtils.chatIncludesChronos(this.props.report)} - isReportSettled={this.props.report.status === CONST.REPORT.REPORT.STATUS.REIMBURSED} + isReportSettled={this.props.report.status === CONST.REPORT.STATUS.REIMBURSED} /> )} From dc0cb475417b30377251c3bc53372efe95880a61 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 12 Apr 2023 14:45:26 +0200 Subject: [PATCH 006/329] create isReportSettled --- src/libs/ReportUtils.js | 5 +++++ src/pages/home/report/ReportActionItem.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index cd9792dacc72..b33abfbe885b 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1703,6 +1703,10 @@ function canLeaveRoom(report, isPolicyMember) { return true; } +function isReportSettled(report) { + return report.status === CONST.REPORT.STATUS.REIMBURSED; +} + export { getReportParticipantsTitle, isReportMessageAttachment, @@ -1771,4 +1775,5 @@ export { getSmallSizeAvatar, getMoneyRequestOptions, canRequestMoney, + isReportSettled, }; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 3453b735f33b..2fd07de1d8d6 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -298,7 +298,7 @@ class ReportActionItem extends Component { } draftMessage={this.props.draftMessage} isChronosReport={ReportUtils.chatIncludesChronos(this.props.report)} - isReportSettled={this.props.report.status === CONST.REPORT.STATUS.REIMBURSED} + isReportSettled={this.props.iouReport.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED} /> )} From e86c484116b63bde273f1ddaec8c19f4ab2c8d01 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 12 Apr 2023 14:50:47 +0200 Subject: [PATCH 007/329] rm isReportSettled props and params --- src/libs/ReportUtils.js | 20 ++++++++++++------- .../BaseReportActionContextMenu.js | 5 ----- .../report/ContextMenu/ContextMenuActions.js | 4 ++-- .../PopoverReportActionContextMenu.js | 7 ------- .../ContextMenu/ReportActionContextMenu.js | 3 --- src/pages/home/report/ReportActionItem.js | 1 - 6 files changed, 15 insertions(+), 25 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index b33abfbe885b..e070cac6e76e 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -150,16 +150,26 @@ function canEditReportAction(reportAction) { && reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; } +/** + * Returns whether the Money Request report has been reimbursed + * + * @param {String} reportID + * @returns {Boolean} + */ +function isReportSettled(reportID) { + return allReports[reportID].status === CONST.REPORT.STATUS.REIMBURSED; +} + /** * Can only delete if the author is this user and the action is an ADDCOMMENT action or an IOU action in an unsettled report * * @param {Object} reportAction - * @param {Boolean} isReportSettled * @returns {Boolean} */ -function canDeleteReportAction(reportAction, isReportSettled) { +function canDeleteReportAction(reportAction) { return reportAction.actorEmail === sessionEmail - && (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT || (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && !isReportSettled)) + && (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT + || (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && !isReportSettled(reportAction.originalMessage.IOUReportID))) && reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; } @@ -1703,10 +1713,6 @@ function canLeaveRoom(report, isPolicyMember) { return true; } -function isReportSettled(report) { - return report.status === CONST.REPORT.STATUS.REIMBURSED; -} - export { getReportParticipantsTitle, isReportMessageAttachment, diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js index 6f802c229fd4..e562a417d423 100755 --- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js @@ -27,9 +27,6 @@ const propTypes = { /** Whether the provided report is an archived room */ isArchivedRoom: PropTypes.bool, - /** Whether the provided report has been reimbursed */ - isReportSettled: PropTypes.bool, - contentRef: PropTypes.oneOfType([PropTypes.node, PropTypes.object, PropTypes.func]), ...genericReportActionContextMenuPropTypes, @@ -43,7 +40,6 @@ const defaultProps = { contentRef: null, isChronosReport: false, isArchivedRoom: false, - isReportSettled: false, ...GenericReportActionContextMenuDefaultProps, }; class BaseReportActionContextMenu extends React.Component { @@ -64,7 +60,6 @@ class BaseReportActionContextMenu extends React.Component { this.props.betas, this.props.anchor, this.props.isChronosReport, - this.props.isReportSettled, ); return (this.props.isVisible || this.state.shouldKeepOpen) && ( diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index f2a3e25eede6..385fee477f7b 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -237,8 +237,8 @@ export default [ { textTranslateKey: 'reportActionContextMenu.deleteComment', icon: Expensicons.Trashcan, - shouldShow: (type, reportAction, isArchivedRoom, betas, menuTarget, isChronosReport, isReportSettled) => type === CONTEXT_MENU_TYPES.REPORT_ACTION - && ReportUtils.canDeleteReportAction(reportAction, isReportSettled) && !isArchivedRoom && !isChronosReport, + shouldShow: (type, reportAction, isArchivedRoom, betas, menuTarget, isChronosReport) => type === CONTEXT_MENU_TYPES.REPORT_ACTION + && ReportUtils.canDeleteReportAction(reportAction) && !isArchivedRoom && !isChronosReport, onPress: (closePopover, {reportID, reportAction}) => { if (closePopover) { // Hide popover, then call showDeleteConfirmModal diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 154a96013ecf..4883d2f37aa7 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -39,7 +39,6 @@ class PopoverReportActionContextMenu extends React.Component { }, isArchivedRoom: false, isChronosReport: false, - isReportSettled: false, }; this.onPopoverShow = () => {}; this.onPopoverHide = () => {}; @@ -126,7 +125,6 @@ class PopoverReportActionContextMenu extends React.Component { * @param {Function} [onHide] - Run a callback when Menu is hidden * @param {Boolean} isArchivedRoom - Whether the provided report is an archived room * @param {Boolean} isChronosReport - Flag to check if the chat participant is Chronos - * @param {Boolean} isReportSettled - Whether the provided report has been reimbursed */ showContextMenu( type, @@ -140,7 +138,6 @@ class PopoverReportActionContextMenu extends React.Component { onHide = () => {}, isArchivedRoom, isChronosReport, - isReportSettled, ) { const nativeEvent = event.nativeEvent || {}; this.contextMenuAnchor = contextMenuAnchor; @@ -171,7 +168,6 @@ class PopoverReportActionContextMenu extends React.Component { reportActionDraftMessage: draftMessage, isArchivedRoom, isChronosReport, - isReportSettled, }); }); } @@ -246,7 +242,6 @@ class PopoverReportActionContextMenu extends React.Component { reportAction={this.state.reportAction} isArchivedRoom={this.state.isArchivedRoom} isChronosReport={this.state.isChronosReport} - isReportSettled={this.state.isReportSettled} anchor={this.contextMenuTargetNode} contentRef={this.setContentRef} /> @@ -278,7 +273,6 @@ class PopoverReportActionContextMenu extends React.Component { shouldSetModalVisibilityForDeleteConfirmation: true, isArchivedRoom: false, isChronosReport: false, - isReportSettled: false, }); } @@ -325,7 +319,6 @@ class PopoverReportActionContextMenu extends React.Component { draftMessage={this.state.reportActionDraftMessage} isArchivedRoom={this.state.isArchivedRoom} isChronosReport={this.state.isChronosReport} - isReportSettled={this.state.isReportSettled} anchor={this.contextMenuTargetNode} contentRef={this.contentRef} /> diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js index c83549cabb81..df544f2e7202 100644 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js @@ -16,7 +16,6 @@ const contextMenuRef = React.createRef(); * @param {Function} [onHide=() => {}] - Run a callback when Menu is hidden * @param {Boolean} isArchivedRoom - Whether the provided report is an archived room * @param {Boolean} isChronosReport - Flag to check if the chat participant is Chronos - * @param {Boolean} isReportSettled - Whether the provided report has been reimbursed */ function showContextMenu( type, @@ -30,7 +29,6 @@ function showContextMenu( onHide = () => {}, isArchivedRoom = false, isChronosReport = false, - isReportSettled = false, ) { if (!contextMenuRef.current) { return; @@ -47,7 +45,6 @@ function showContextMenu( onHide, isArchivedRoom, isChronosReport, - isReportSettled, ); } diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 2fd07de1d8d6..8cb3bd38e12d 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -298,7 +298,6 @@ class ReportActionItem extends Component { } draftMessage={this.props.draftMessage} isChronosReport={ReportUtils.chatIncludesChronos(this.props.report)} - isReportSettled={this.props.iouReport.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED} /> )} From 64de99fdd85801adce7267f567fa60fa9bd994e6 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 12 Apr 2023 15:00:49 +0200 Subject: [PATCH 008/329] get iouReport in isReimbursed --- src/libs/ReportUtils.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index e070cac6e76e..fd598580121a 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -156,8 +156,8 @@ function canEditReportAction(reportAction) { * @param {String} reportID * @returns {Boolean} */ -function isReportSettled(reportID) { - return allReports[reportID].status === CONST.REPORT.STATUS.REIMBURSED; +function isReimbursed(reportID) { + return allReports[`report_${reportID}`].status === CONST.REPORT.STATUS.REIMBURSED; } /** @@ -169,7 +169,7 @@ function isReportSettled(reportID) { function canDeleteReportAction(reportAction) { return reportAction.actorEmail === sessionEmail && (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT - || (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && !isReportSettled(reportAction.originalMessage.IOUReportID))) + || (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && !isReimbursed(reportAction.originalMessage.IOUReportID))) && reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; } @@ -1781,5 +1781,5 @@ export { getSmallSizeAvatar, getMoneyRequestOptions, canRequestMoney, - isReportSettled, + isReimbursed, }; From 1e45e0ff95c2c8653e3bc25650e2430a6c497e54 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 12 Apr 2023 15:16:31 +0200 Subject: [PATCH 009/329] use lodash --- src/libs/ReportUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index fd598580121a..d4e75bd91015 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -157,7 +157,7 @@ function canEditReportAction(reportAction) { * @returns {Boolean} */ function isReimbursed(reportID) { - return allReports[`report_${reportID}`].status === CONST.REPORT.STATUS.REIMBURSED; + return lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, 'status'], '') === CONST.REPORT.STATUS.REIMBURSED; } /** From 8899cc8197060fc5bdff09c124223ebc1606d7b7 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 12 Apr 2023 19:13:38 +0200 Subject: [PATCH 010/329] rename method to isSettled, fix bug --- src/libs/ReportUtils.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index d4e75bd91015..e714cf7029f8 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -151,13 +151,13 @@ function canEditReportAction(reportAction) { } /** - * Returns whether the Money Request report has been reimbursed + * Whether the Money Request report has been reimbursed * * @param {String} reportID * @returns {Boolean} */ -function isReimbursed(reportID) { - return lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, 'status'], '') === CONST.REPORT.STATUS.REIMBURSED; +function isSettled(reportID) { + return !lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, 'hasOutstandingIOU']); } /** @@ -169,7 +169,7 @@ function isReimbursed(reportID) { function canDeleteReportAction(reportAction) { return reportAction.actorEmail === sessionEmail && (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT - || (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && !isReimbursed(reportAction.originalMessage.IOUReportID))) + || (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && !isSettled(reportAction.originalMessage.IOUReportID))) && reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; } @@ -1781,5 +1781,5 @@ export { getSmallSizeAvatar, getMoneyRequestOptions, canRequestMoney, - isReimbursed, + isSettled, }; From 7f1af583974d8a06ebeed04b460e11ae9140a503 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 12 Apr 2023 20:13:06 +0200 Subject: [PATCH 011/329] update confirmation modal --- src/languages/en.js | 4 ++-- src/languages/es.js | 4 ++-- src/pages/home/report/ContextMenu/ContextMenuActions.js | 2 +- .../home/report/ContextMenu/PopoverReportActionContextMenu.js | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index 1c83afdb79b7..b28020bb1b1f 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -239,8 +239,8 @@ export default { copyEmailToClipboard: 'Copy email to clipboard', markAsUnread: 'Mark as unread', editComment: 'Edit comment', - deleteComment: 'Delete comment', - deleteConfirmation: 'Are you sure you want to delete this comment?', + deleteAction: ({actionName}) => `Delete ${actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? 'request' : 'comment'}`, + deleteConfirmation: ({actionName}) => `Are you sure you want to delete this ${actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? 'request' : 'comment'}?`, }, emojiReactions: { addReactionTooltip: 'Add reaction', diff --git a/src/languages/es.js b/src/languages/es.js index 74dbcb762d50..c4a89223b350 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -238,8 +238,8 @@ export default { copyEmailToClipboard: 'Copiar email al portapapeles', markAsUnread: 'Marcar como no leído', editComment: 'Editar comentario', - deleteComment: 'Eliminar comentario', - deleteConfirmation: '¿Estás seguro de que quieres eliminar este comentario?', + deleteAction: ({actionName}) => `Eliminar ${actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? 'pedido' : 'comentario'}`, + deleteConfirmation: ({actionName}) => `¿Estás seguro de que quieres eliminar este ${actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? 'pedido' : 'comentario'}`, }, emojiReactions: { addReactionTooltip: 'Añadir una reacción', diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 385fee477f7b..50b4ec5e7082 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -235,7 +235,7 @@ export default [ getDescription: () => {}, }, { - textTranslateKey: 'reportActionContextMenu.deleteComment', + textTranslateKey: 'reportActionContextMenu.deleteAction', icon: Expensicons.Trashcan, shouldShow: (type, reportAction, isArchivedRoom, betas, menuTarget, isChronosReport) => type === CONTEXT_MENU_TYPES.REPORT_ACTION && ReportUtils.canDeleteReportAction(reportAction) && !isArchivedRoom && !isChronosReport, diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 4883d2f37aa7..4553b6b5fb8e 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -324,13 +324,13 @@ class PopoverReportActionContextMenu extends React.Component { /> Date: Wed, 12 Apr 2023 20:16:07 +0200 Subject: [PATCH 012/329] rm reportAction reset on modal hide --- .../home/report/ContextMenu/PopoverReportActionContextMenu.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 4553b6b5fb8e..02f8582cab4d 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -268,7 +268,6 @@ class PopoverReportActionContextMenu extends React.Component { this.callbackWhenDeleteModalHide = () => this.onCancelDeleteModal = this.runAndResetCallback(this.onCancelDeleteModal); this.setState({ reportID: '0', - reportAction: {}, isDeleteCommentConfirmModalVisible: false, shouldSetModalVisibilityForDeleteConfirmation: true, isArchivedRoom: false, From bdf32e68eb1434fc29698f364ac8157ebd0cf930 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 12 Apr 2023 20:19:24 +0200 Subject: [PATCH 013/329] add api callback conditional --- .../report/ContextMenu/PopoverReportActionContextMenu.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 02f8582cab4d..8ba79910cad7 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -260,7 +260,12 @@ class PopoverReportActionContextMenu extends React.Component { confirmDeleteAndHideModal() { this.callbackWhenDeleteModalHide = () => this.onComfirmDeleteModal = this.runAndResetCallback(this.onComfirmDeleteModal); - Report.deleteReportComment(this.state.reportID, this.state.reportAction); + + if (this.state.reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { + // @TODO: Implement new DeleteMoneyRequest API command from issue https://github.com/Expensify/Expensify/issues/270502 + } else { + Report.deleteReportComment(this.state.reportID, this.state.reportAction); + } this.setState({isDeleteCommentConfirmModalVisible: false}); } From 3324de7c9816c50fcf20f0a29248fc7fc96cf53d Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 12 Apr 2023 20:28:49 +0200 Subject: [PATCH 014/329] update tooltip --- .../home/report/ContextMenu/BaseReportActionContextMenu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js index e562a417d423..6771ac18409c 100755 --- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js @@ -90,7 +90,7 @@ class BaseReportActionContextMenu extends React.Component { return ( Date: Sat, 15 Apr 2023 00:51:14 +0200 Subject: [PATCH 015/329] create isMoneyRequestAction and replace usages of const --- src/components/ReportTransaction.js | 3 ++- src/libs/IOUUtils.js | 3 ++- src/libs/ReportActionsUtils.js | 9 +++++++++ src/libs/ReportUtils.js | 2 +- .../report/ContextMenu/PopoverReportActionContextMenu.js | 3 ++- src/pages/home/report/ReportActionItem.js | 5 +++-- 6 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/components/ReportTransaction.js b/src/components/ReportTransaction.js index be53b11c742e..ce8079dbe35f 100644 --- a/src/components/ReportTransaction.js +++ b/src/components/ReportTransaction.js @@ -11,6 +11,7 @@ import withLocalize, {withLocalizePropTypes} from './withLocalize'; import OfflineWithFeedback from './OfflineWithFeedback'; import Text from './Text'; import Button from './Button'; +import * as ReportActionsUtils from '../libs/ReportActionsUtils'; const propTypes = { /** The chatReport which the transaction is associated with */ @@ -57,7 +58,7 @@ class ReportTransaction extends Component { return ( { - if (this.props.action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { + if (ReportActionsUtils.isMoneyRequestAction(this.props.action)) { ReportActions.clearSendMoneyErrors(this.props.chatReportID, this.props.action.reportActionID); } if (this.props.action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { diff --git a/src/libs/IOUUtils.js b/src/libs/IOUUtils.js index b4e8f1ff46ce..1fa76ef798c5 100644 --- a/src/libs/IOUUtils.js +++ b/src/libs/IOUUtils.js @@ -1,5 +1,6 @@ import _ from 'underscore'; import CONST from '../CONST'; +import * as ReportActionsUtils from './ReportActionsUtils'; /** * Calculates the amount per user given a list of participants @@ -81,7 +82,7 @@ function updateIOUOwnerAndTotal(iouReport, actorEmail, amount, currency, type = function getIOUReportActions(reportActions, iouReport, type = '', pendingAction = '', filterRequestsInDifferentCurrency = false) { return _.chain(reportActions) .filter(action => action.originalMessage - && action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU + && ReportActionsUtils.isMoneyRequestAction(action) && (!_.isEmpty(type) ? action.originalMessage.type === type : true)) .filter(action => action.originalMessage.IOUReportID.toString() === iouReport.reportID.toString()) .filter(action => (!_.isEmpty(pendingAction) ? action.pendingAction === pendingAction : true)) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index b4c4009d6650..c6b049aac5fa 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -40,6 +40,14 @@ function isDeletedAction(reportAction) { return message.length === 0 || lodashGet(message, [0, 'html']) === ''; } +/** + * @param {Object} reportAction + * @returns {Boolean} + */ +function isMoneyRequestAction(reportAction) { + return reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU; +} + /** * Sort an array of reportActions by their created timestamp first, and reportActionID second * This gives us a stable order even in the case of multiple reportActions created on the same millisecond @@ -254,4 +262,5 @@ export { getSortedReportActionsForDisplay, getLastClosedReportAction, getLatestReportActionFromOnyxData, + isMoneyRequestAction, }; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index e714cf7029f8..9157cfde25c2 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -169,7 +169,7 @@ function isSettled(reportID) { function canDeleteReportAction(reportAction) { return reportAction.actorEmail === sessionEmail && (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT - || (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && !isSettled(reportAction.originalMessage.IOUReportID))) + || (ReportActionsUtils.isMoneyRequestAction(reportAction) && !isSettled(reportAction.originalMessage.IOUReportID))) && reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; } diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 8ba79910cad7..4412c151a8bd 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -10,6 +10,7 @@ import PopoverWithMeasuredContent from '../../../../components/PopoverWithMeasur import BaseReportActionContextMenu from './BaseReportActionContextMenu'; import ConfirmModal from '../../../../components/ConfirmModal'; import CONST from '../../../../CONST'; +import * as ReportActionsUtils from '../../../../libs/ReportActionsUtils'; const propTypes = { ...withLocalizePropTypes, @@ -261,7 +262,7 @@ class PopoverReportActionContextMenu extends React.Component { confirmDeleteAndHideModal() { this.callbackWhenDeleteModalHide = () => this.onComfirmDeleteModal = this.runAndResetCallback(this.onComfirmDeleteModal); - if (this.state.reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { + if (ReportActionsUtils.isMoneyRequestAction(this.state.reportAction)) { // @TODO: Implement new DeleteMoneyRequest API command from issue https://github.com/Expensify/Expensify/issues/270502 } else { Report.deleteReportComment(this.state.reportID, this.state.reportAction); diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 8cb3bd38e12d..a2ec77930c2f 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -33,6 +33,7 @@ import * as User from '../../../libs/actions/User'; import * as ReportUtils from '../../../libs/ReportUtils'; import OfflineWithFeedback from '../../../components/OfflineWithFeedback'; import * as ReportActions from '../../../libs/actions/ReportActions'; +import * as ReportActionsUtils from '../../../libs/ReportActionsUtils'; import reportPropTypes from '../../reportPropTypes'; import {ShowContextMenuContext} from '../../../components/ShowContextMenuContext'; import focusTextInputAfterAnimation from '../../../libs/focusTextInputAfterAnimation'; @@ -156,7 +157,7 @@ class ReportActionItem extends Component { */ renderItemContent(hovered = false) { let children; - if (this.props.action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { + if (ReportActionsUtils.isMoneyRequestAction(this.props.action)) { children = ( {!this.props.displayAsGroup ? ( From 556c961525f61d53356319b6238df11d6800d901 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Sat, 15 Apr 2023 00:54:05 +0200 Subject: [PATCH 016/329] use isMoneyRequestAction in other places --- src/languages/en.js | 5 +++-- src/languages/es.js | 5 +++-- .../report/ContextMenu/PopoverReportActionContextMenu.js | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index b28020bb1b1f..e9615f5ea077 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -1,5 +1,6 @@ import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; import CONST from '../CONST'; +import * as ReportActionsUtils from '../libs/ReportActionsUtils'; /* eslint-disable max-len */ export default { @@ -239,8 +240,8 @@ export default { copyEmailToClipboard: 'Copy email to clipboard', markAsUnread: 'Mark as unread', editComment: 'Edit comment', - deleteAction: ({actionName}) => `Delete ${actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? 'request' : 'comment'}`, - deleteConfirmation: ({actionName}) => `Are you sure you want to delete this ${actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? 'request' : 'comment'}?`, + deleteAction: ({action}) => `Delete ${ReportActionsUtils.isMoneyRequestAction(action) ? 'request' : 'comment'}`, + deleteConfirmation: ({action}) => `Are you sure you want to delete this ${ReportActionsUtils.isMoneyRequestAction(action) ? 'request' : 'comment'}?`, }, emojiReactions: { addReactionTooltip: 'Add reaction', diff --git a/src/languages/es.js b/src/languages/es.js index c4a89223b350..d43bd8ad69db 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -1,4 +1,5 @@ import CONST from '../CONST'; +import * as ReportActionsUtils from '../libs/ReportActionsUtils'; /* eslint-disable max-len */ export default { @@ -238,8 +239,8 @@ export default { copyEmailToClipboard: 'Copiar email al portapapeles', markAsUnread: 'Marcar como no leído', editComment: 'Editar comentario', - deleteAction: ({actionName}) => `Eliminar ${actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? 'pedido' : 'comentario'}`, - deleteConfirmation: ({actionName}) => `¿Estás seguro de que quieres eliminar este ${actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? 'pedido' : 'comentario'}`, + deleteAction: ({action}) => `Eliminar ${ReportActionsUtils.isMoneyRequestAction(action) ? 'pedido' : 'comentario'}`, + deleteConfirmation: ({action}) => `¿Estás seguro de que quieres eliminar este ${ReportActionsUtils.isMoneyRequestAction(action) ? 'pedido' : 'comentario'}`, }, emojiReactions: { addReactionTooltip: 'Añadir una reacción', diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 4412c151a8bd..80a3bedf4706 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -329,13 +329,13 @@ class PopoverReportActionContextMenu extends React.Component { /> Date: Sat, 15 Apr 2023 01:00:37 +0200 Subject: [PATCH 017/329] fix undefined error --- src/libs/ReportActionsUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index c6b049aac5fa..e395ac33bd17 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -45,7 +45,7 @@ function isDeletedAction(reportAction) { * @returns {Boolean} */ function isMoneyRequestAction(reportAction) { - return reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU; + return lodashGet(reportAction, 'actionName', '') === CONST.REPORT.ACTIONS.TYPE.IOU; } /** From fe4e9156c0fc9b94a0be03f922d32c298aeb70be Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 4 May 2023 12:59:18 +0200 Subject: [PATCH 018/329] migrate switch to PressableWithFeedback, add accessibility props --- src/components/Switch.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/Switch.js b/src/components/Switch.js index e4de23650556..4496bfa4266b 100644 --- a/src/components/Switch.js +++ b/src/components/Switch.js @@ -1,7 +1,8 @@ import React, {Component} from 'react'; -import {TouchableOpacity, Animated} from 'react-native'; +import {Animated} from 'react-native'; import PropTypes from 'prop-types'; import styles from '../styles/styles'; +import * as Pressables from './Pressable'; const propTypes = { /** Whether the switch is toggled to the on position */ @@ -11,6 +12,7 @@ const propTypes = { onToggle: PropTypes.func.isRequired, }; +const PressableWithFeedback = Pressables.PressableWithFeedback; class Switch extends Component { constructor(props) { super(props); @@ -40,13 +42,15 @@ class Switch extends Component { render() { const switchTransform = {transform: [{translateX: this.offsetX}]}; return ( - this.props.onToggle(!this.props.isOn)} + accessibilityRole="switch" + accessibilityState={{checked: this.props.isOn}} + aria-checked={this.props.isOn} > - + ); } } From b548a3e50923e0eac84df0735176be7c91c8d78c Mon Sep 17 00:00:00 2001 From: David Bondy Date: Thu, 4 May 2023 15:41:56 -0600 Subject: [PATCH 019/329] change LHN header to Expensify --- src/languages/en.js | 2 +- src/languages/es.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index 7b45cca7cce8..948779a63942 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -295,7 +295,7 @@ export default { newChat: 'New chat', newGroup: 'New group', newRoom: 'New room', - headerChat: 'Chats', + headerChat: 'Expensify', buttonSearch: 'Search', buttonMySettings: 'My settings', fabNewChat: 'New chat (Floating action)', diff --git a/src/languages/es.js b/src/languages/es.js index 001687c1df06..6daf7cbbc901 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -294,7 +294,7 @@ export default { newChat: 'Nuevo chat', newGroup: 'Nuevo grupo', newRoom: 'Nueva sala de chat', - headerChat: 'Chats', + headerChat: 'Expensify', buttonSearch: 'Buscar', buttonMySettings: 'Mi configuración', fabNewChat: 'Nuevo chat', From 3b461e23899c6ee5b1cefc5caab4d51ab0218f87 Mon Sep 17 00:00:00 2001 From: David Bondy Date: Fri, 5 May 2023 15:30:58 -0600 Subject: [PATCH 020/329] update the header component so it can accept react components (or anything really) to be rendered inside the header --- src/components/Header.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/Header.js b/src/components/Header.js index 01752e8e0740..c97a948ca0ce 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -8,7 +8,10 @@ import EnvironmentBadge from './EnvironmentBadge'; const propTypes = { /** Title of the Header */ - title: PropTypes.string.isRequired, + title: PropTypes.string, + + /** Things to be rendered inside the header*/ + children: PropTypes.node, /** Subtitle of the header */ subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), @@ -22,6 +25,8 @@ const propTypes = { }; const defaultProps = { + title: '', + children: '', shouldShowEnvironmentBadge: false, subtitle: '', textStyles: [], @@ -29,9 +34,9 @@ const defaultProps = { const Header = props => ( - - {props.title} - + {!_.isEmpty(props.children) + ? props.children + : {props.title}} {/* If there's no subtitle then display a fragment to avoid an empty space which moves the main title */} {_.isString(props.subtitle) ? Boolean(props.subtitle) && {props.subtitle} From 735725c87039a142577550ea64788fdd9c93a586 Mon Sep 17 00:00:00 2001 From: David Bondy Date: Fri, 5 May 2023 15:31:28 -0600 Subject: [PATCH 021/329] import the wordmark logo and render it in the header --- src/pages/home/sidebar/SidebarLinks.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 65329cebc1f4..77493c5151e1 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -31,6 +31,8 @@ import SidebarUtils from '../../../libs/SidebarUtils'; import reportPropTypes from '../../reportPropTypes'; import OfflineWithFeedback from '../../../components/OfflineWithFeedback'; import LHNSkeletonView from '../../../components/LHNSkeletonView'; +import LogoWordmark from '../../../../assets/images/expensify-wordmark.svg'; +import defaultTheme from '../../../styles/themes/default'; const propTypes = { /** Toggles the navigation menu open and closed */ @@ -159,6 +161,7 @@ class SidebarLinks extends React.Component { accessibilityRole="text" shouldShowEnvironmentBadge textStyles={[styles.textHeadline]} + children={} /> Date: Fri, 5 May 2023 16:13:54 -0600 Subject: [PATCH 022/329] fix style --- src/components/Header.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Header.js b/src/components/Header.js index c97a948ca0ce..faff46ae1aa7 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -10,7 +10,7 @@ const propTypes = { /** Title of the Header */ title: PropTypes.string, - /** Things to be rendered inside the header*/ + /** Things to be rendered inside the header */ children: PropTypes.node, /** Subtitle of the header */ From 1fa9eedebf003bec3fe95b134e4fc887609c4dfc Mon Sep 17 00:00:00 2001 From: David Bondy Date: Fri, 5 May 2023 16:38:46 -0600 Subject: [PATCH 023/329] create new component for imageheader to properly use children to render content --- src/components/Header.js | 13 +++----- src/components/ImageHeader.js | 45 ++++++++++++++++++++++++++ src/pages/home/sidebar/SidebarLinks.js | 11 +++---- 3 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 src/components/ImageHeader.js diff --git a/src/components/Header.js b/src/components/Header.js index faff46ae1aa7..01752e8e0740 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -8,10 +8,7 @@ import EnvironmentBadge from './EnvironmentBadge'; const propTypes = { /** Title of the Header */ - title: PropTypes.string, - - /** Things to be rendered inside the header */ - children: PropTypes.node, + title: PropTypes.string.isRequired, /** Subtitle of the header */ subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), @@ -25,8 +22,6 @@ const propTypes = { }; const defaultProps = { - title: '', - children: '', shouldShowEnvironmentBadge: false, subtitle: '', textStyles: [], @@ -34,9 +29,9 @@ const defaultProps = { const Header = props => ( - {!_.isEmpty(props.children) - ? props.children - : {props.title}} + + {props.title} + {/* If there's no subtitle then display a fragment to avoid an empty space which moves the main title */} {_.isString(props.subtitle) ? Boolean(props.subtitle) && {props.subtitle} diff --git a/src/components/ImageHeader.js b/src/components/ImageHeader.js new file mode 100644 index 000000000000..8f086e1fecf5 --- /dev/null +++ b/src/components/ImageHeader.js @@ -0,0 +1,45 @@ +import React from 'react'; +import {View} from 'react-native'; +import PropTypes from 'prop-types'; +import _ from 'underscore'; +import styles from '../styles/styles'; +import EnvironmentBadge from './EnvironmentBadge'; + +const propTypes = { + children: PropTypes.node.isRequired, + + /** Subtitle of the header */ + subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), + + /** Should we show the environment badge (dev/stg)? */ + shouldShowEnvironmentBadge: PropTypes.bool, + + /** Additional text styles */ + // eslint-disable-next-line react/forbid-prop-types + textStyles: PropTypes.arrayOf(PropTypes.object), +}; + +const defaultProps = { + shouldShowEnvironmentBadge: false, + subtitle: '', + textStyles: [], +}; +const ImageHeader = props => ( + + + {props.children} + {/* If there's no subtitle then display a fragment to avoid an empty space which moves the main title */} + {_.isString(props.subtitle) + ? Boolean(props.subtitle) && {props.subtitle} + : props.subtitle} + + {props.shouldShowEnvironmentBadge && ( + + )} + +); + +ImageHeader.displayName = 'ImageHeader'; +ImageHeader.propTypes = propTypes; +ImageHeader.defaultProps = defaultProps; +export default ImageHeader; diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 77493c5151e1..a7ec9afa6f1c 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -14,7 +14,7 @@ import compose from '../../../libs/compose'; import Navigation from '../../../libs/Navigation/Navigation'; import ROUTES from '../../../ROUTES'; import Icon from '../../../components/Icon'; -import Header from '../../../components/Header'; +import ImageHeader from '../../../components/ImageHeader'; import * as Expensicons from '../../../components/Icon/Expensicons'; import AvatarWithIndicator from '../../../components/AvatarWithIndicator'; import Tooltip from '../../../components/Tooltip'; @@ -155,14 +155,13 @@ class SidebarLinks extends React.Component { ]} nativeID="drag-area" > -
} - /> + > + + Date: Fri, 5 May 2023 16:41:58 -0600 Subject: [PATCH 024/329] remove unused props --- src/components/ImageHeader.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/components/ImageHeader.js b/src/components/ImageHeader.js index 8f086e1fecf5..5c82006e392a 100644 --- a/src/components/ImageHeader.js +++ b/src/components/ImageHeader.js @@ -13,16 +13,11 @@ const propTypes = { /** Should we show the environment badge (dev/stg)? */ shouldShowEnvironmentBadge: PropTypes.bool, - - /** Additional text styles */ - // eslint-disable-next-line react/forbid-prop-types - textStyles: PropTypes.arrayOf(PropTypes.object), }; const defaultProps = { shouldShowEnvironmentBadge: false, subtitle: '', - textStyles: [], }; const ImageHeader = props => ( From a2f35e1b26f2ea834dc85bbd93d034d8167dfebc Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Sat, 6 May 2023 00:42:11 +0200 Subject: [PATCH 025/329] fix warnings which come from OpacityView --- src/components/OpacityView.js | 2 +- src/components/Pressable/PressableWithFeedback.js | 2 +- src/components/Switch.js | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/OpacityView.js b/src/components/OpacityView.js index f9deb1184d72..17d05e42dbb6 100644 --- a/src/components/OpacityView.js +++ b/src/components/OpacityView.js @@ -21,7 +21,7 @@ const propTypes = { * @default [] */ // eslint-disable-next-line react/forbid-prop-types - style: PropTypes.arrayOf(PropTypes.object), + style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), /** * The value to use for the opacity when the view is dimmed diff --git a/src/components/Pressable/PressableWithFeedback.js b/src/components/Pressable/PressableWithFeedback.js index 66401fc1d974..341755c5aaf7 100644 --- a/src/components/Pressable/PressableWithFeedback.js +++ b/src/components/Pressable/PressableWithFeedback.js @@ -29,7 +29,7 @@ const PressableWithFeedback = forwardRef((props, ref) => { {state => ( From 0bc7607bf37e760b83eaf954f3b149d272dfb6c6 Mon Sep 17 00:00:00 2001 From: David Bondy Date: Fri, 5 May 2023 16:53:19 -0600 Subject: [PATCH 026/329] removing unneeded string --- src/languages/en.js | 1 - src/languages/es.js | 1 - src/pages/home/sidebar/SidebarLinks.js | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index 948779a63942..9cbf6954b15e 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -295,7 +295,6 @@ export default { newChat: 'New chat', newGroup: 'New group', newRoom: 'New room', - headerChat: 'Expensify', buttonSearch: 'Search', buttonMySettings: 'My settings', fabNewChat: 'New chat (Floating action)', diff --git a/src/languages/es.js b/src/languages/es.js index 6daf7cbbc901..508555cba1a7 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -294,7 +294,6 @@ export default { newChat: 'Nuevo chat', newGroup: 'Nuevo grupo', newRoom: 'Nueva sala de chat', - headerChat: 'Expensify', buttonSearch: 'Buscar', buttonMySettings: 'Mi configuración', fabNewChat: 'Nuevo chat', diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index a7ec9afa6f1c..57f2df7b2c18 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -156,7 +156,7 @@ class SidebarLinks extends React.Component { nativeID="drag-area" > From 8914ea0c8cdaca60be669a49b74e27736a5713cb Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Sat, 6 May 2023 07:38:00 +0100 Subject: [PATCH 027/329] Allow all filetypes to be uploaded --- src/CONST.js | 3 --- src/components/AttachmentModal.js | 11 ----------- 2 files changed, 14 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index a45c6c91240e..fb7e8ffe0c12 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -31,9 +31,6 @@ const CONST = { ARROW_HIDE_DELAY: 3000, API_ATTACHMENT_VALIDATIONS: { - // Same as the PHP layer allows - ALLOWED_EXTENSIONS: ['webp', 'jpg', 'jpeg', 'png', 'gif', 'pdf', 'html', 'txt', 'rtf', 'doc', 'docx', 'htm', 'tiff', 'tif', 'xml', 'mp3', 'mp4', 'mov'], - // 24 megabytes in bytes, this is limit set on servers, do not update without wider internal discussion MAX_SIZE: 25165824, diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 2dfdb991f9f2..3cf15dacb5fa 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -169,17 +169,6 @@ class AttachmentModal extends PureComponent { * @returns {Boolean} */ isValidFile(file) { - const {fileExtension} = FileUtils.splitExtensionFromFileName(lodashGet(file, 'name', '')); - if (!_.contains(CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_EXTENSIONS, fileExtension.toLowerCase())) { - const invalidReason = `${this.props.translate('attachmentPicker.notAllowedExtension')} ${CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_EXTENSIONS.join(', ')}`; - this.setState({ - isAttachmentInvalid: true, - attachmentInvalidReasonTitle: this.props.translate('attachmentPicker.wrongFileType'), - attachmentInvalidReason: invalidReason, - }); - return false; - } - if (lodashGet(file, 'size', 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { this.setState({ isAttachmentInvalid: true, From 654574d2236f4258c13ffd6103f16abe2bfc4023 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 8 May 2023 22:27:36 +0300 Subject: [PATCH 028/329] update delete and split requests --- src/libs/actions/IOU.js | 68 ++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 8d592526b0c5..fbf4f72a7ce0 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -423,7 +423,8 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment ); // Note: The created action must be optimistically generated before the IOU action so there's no chance that the created action appears after the IOU action in the chat - const oneOnOneCreatedReportAction = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmail); + const oneOnOneCreatedChatReportAction = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmail); + const oneOnOneCreatedIOUReportAction = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmail); const oneOnOneIOUReportAction = ReportUtils.buildOptimisticIOUReportAction( CONST.IOU.REPORT_ACTION_TYPE.CREATE, splitAmount, @@ -435,8 +436,8 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment oneOnOneIOUReport.reportID, ); - oneOnOneChatReport.lastMessageText = oneOnOneIOUReportAction.message[0].text; - oneOnOneChatReport.lastMessageHtml = oneOnOneIOUReportAction.message[0].html; + oneOnOneIOUReport.lastMessageText = oneOnOneIOUReportAction.message[0].text; + oneOnOneIOUReport.lastMessageHtml = oneOnOneIOUReportAction.message[0].html; if (!existingOneOnOneChatReport) { oneOnOneChatReport.pendingFields = { @@ -456,7 +457,17 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment value: { ...(existingOneOnOneChatReport ? {} - : {[oneOnOneCreatedReportAction.reportActionID]: oneOnOneCreatedReportAction} + : {[oneOnOneCreatedChatReportAction.reportActionID]: oneOnOneCreatedChatReportAction} + ), + }, + }, + { + onyxMethod: existingIOUReport ? Onyx.METHOD.MERGE : Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneIOUReport.reportID}`, + value: { + ...(existingIOUReport + ? {} + : {[oneOnOneCreatedIOUReportAction.reportActionID]: oneOnOneCreatedIOUReportAction} ), [oneOnOneIOUReportAction.reportActionID]: oneOnOneIOUReportAction, }, @@ -475,7 +486,17 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment value: { ...(existingOneOnOneChatReport ? {} - : {[oneOnOneCreatedReportAction.reportActionID]: {pendingAction: null}} + : {[oneOnOneCreatedChatReportAction.reportActionID]: {pendingAction: null}} + ), + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneIOUReport.reportID}`, + value: { + ...(existingIOUReport + ? {} + : {[oneOnOneCreatedIOUReportAction.reportActionID]: {pendingAction: null}} ), [oneOnOneIOUReportAction.reportActionID]: {pendingAction: null}, }, @@ -498,7 +519,7 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment failureData.push( { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneChatReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneIOUReport.reportID}`, value: { [oneOnOneIOUReportAction.reportActionID]: { errors: { @@ -559,8 +580,8 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment reportActionID: oneOnOneIOUReportAction.reportActionID, }; - if (!_.isEmpty(oneOnOneCreatedReportAction)) { - splitData.createdReportActionID = oneOnOneCreatedReportAction.reportActionID; + if (!_.isEmpty(oneOnOneCreatedChatReportAction)) { + splitData.createdReportActionID = oneOnOneCreatedChatReportAction.reportActionID; } splits.push(splitData); @@ -644,9 +665,6 @@ function deleteMoneyRequest(chatReportID, iouReportID, moneyRequestAction, shoul const iouReport = iouReports[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`]; const transactionID = moneyRequestAction.originalMessage.IOUTransactionID; - // Make a copy of the chat report so we don't mutate the original one when updating its values - const chatReport = {...chatReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`]}; - // Get the amount we are deleting const amount = moneyRequestAction.originalMessage.amount; const optimisticReportAction = ReportUtils.buildOptimisticIOUReportAction( @@ -662,14 +680,14 @@ function deleteMoneyRequest(chatReportID, iouReportID, moneyRequestAction, shoul const currentUserEmail = optimisticReportAction.actorEmail; const updatedIOUReport = IOUUtils.updateIOUOwnerAndTotal(iouReport, currentUserEmail, amount, moneyRequestAction.originalMessage.currency, CONST.IOU.REPORT_ACTION_TYPE.DELETE); - chatReport.lastMessageText = optimisticReportAction.message[0].text; - chatReport.lastMessageHtml = optimisticReportAction.message[0].html; - chatReport.hasOutstandingIOU = updatedIOUReport.total !== 0; + updatedIOUReport.lastMessageText = optimisticReportAction.message[0].text; + updatedIOUReport.lastMessageHtml = optimisticReportAction.message[0].html; + updatedIOUReport.hasOutstandingIOU = updatedIOUReport.total !== 0; const optimisticData = [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`, value: { [optimisticReportAction.reportActionID]: { ...optimisticReportAction, @@ -677,11 +695,6 @@ function deleteMoneyRequest(chatReportID, iouReportID, moneyRequestAction, shoul }, }, }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, - value: chatReport, - }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, @@ -696,7 +709,7 @@ function deleteMoneyRequest(chatReportID, iouReportID, moneyRequestAction, shoul const successData = [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`, value: { [optimisticReportAction.reportActionID]: { pendingAction: null, @@ -707,7 +720,7 @@ function deleteMoneyRequest(chatReportID, iouReportID, moneyRequestAction, shoul const failureData = [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`, value: { [optimisticReportAction.reportActionID]: { errors: { @@ -716,15 +729,6 @@ function deleteMoneyRequest(chatReportID, iouReportID, moneyRequestAction, shoul }, }, }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, - value: { - lastMessageText: chatReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`].lastMessageText, - lastMessageHtml: chatReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`].lastMessageHtml, - hasOutstandingIOU: iouReport.total !== 0, - }, - }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, @@ -745,7 +749,7 @@ function deleteMoneyRequest(chatReportID, iouReportID, moneyRequestAction, shoul }, {optimisticData, successData, failureData}); if (shouldCloseOnDelete) { - Navigation.navigate(ROUTES.getReportRoute(chatReportID)); + Navigation.navigate(ROUTES.getReportRoute(iouReportID)); } } From d834c6e3fac8fc6c2124cc16d9794b2ebc4c71bb Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 8 May 2023 22:38:53 +0300 Subject: [PATCH 029/329] update remaining commands --- src/libs/actions/IOU.js | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index fbf4f72a7ce0..2b64de80e56f 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -119,8 +119,6 @@ function requestMoney(report, amount, currency, recipientEmail, participant, com value: { ...chatReport, lastReadTime: DateUtils.getDBTime(), - lastMessageText: optimisticReportAction.message[0].text, - lastMessageHtml: optimisticReportAction.message[0].html, hasOutstandingIOU: iouReport.total !== 0, iouReportID: iouReport.reportID, }, @@ -129,12 +127,16 @@ function requestMoney(report, amount, currency, recipientEmail, participant, com const optimisticIOUReportData = { onyxMethod: chatReport.hasOutstandingIOU ? Onyx.METHOD.MERGE : Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, - value: iouReport, + value: { + ...iouReport, + lastMessageText: optimisticReportAction.message[0].text, + lastMessageHtml: optimisticReportAction.message[0].html, + }, }; const optimisticReportActionsData = { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, value: { [optimisticReportAction.reportActionID]: optimisticReportAction, }, @@ -143,7 +145,7 @@ function requestMoney(report, amount, currency, recipientEmail, participant, com let chatReportSuccessData = {}; const reportActionsSuccessData = { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, value: { [optimisticReportAction.reportActionID]: { pendingAction: null, @@ -161,7 +163,7 @@ function requestMoney(report, amount, currency, recipientEmail, participant, com const reportActionsFailureData = { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, value: { [optimisticReportAction.reportActionID]: { errors: { @@ -841,18 +843,20 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType ...chatReport, lastReadTime: DateUtils.getDBTime(), lastVisibleActionCreated: optimisticIOUReportAction.created, - lastMessageText: optimisticIOUReportAction.message[0].text, - lastMessageHtml: optimisticIOUReportAction.message[0].html, }, }; const optimisticIOUReportData = { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticIOUReport.reportID}`, - value: optimisticIOUReport, + value: { + ...optimisticIOUReport, + lastMessageText: optimisticIOUReportAction.message[0].text, + lastMessageHtml: optimisticIOUReportAction.message[0].html, + }, }; const optimisticReportActionsData = { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticIOUReport.reportID}`, value: { [optimisticIOUReportAction.reportActionID]: { ...optimisticIOUReportAction, @@ -864,7 +868,7 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType const successData = [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticIOUReport.reportID}`, value: { [optimisticIOUReportAction.reportActionID]: { pendingAction: null, @@ -881,7 +885,7 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType const failureData = [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticIOUReport.reportID}`, value: { [optimisticIOUReportAction.reportActionID]: { errors: { @@ -985,15 +989,13 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho ...chatReport, lastReadTime: DateUtils.getDBTime(), lastVisibleActionCreated: optimisticIOUReportAction.created, - lastMessageText: optimisticIOUReportAction.message[0].text, - lastMessageHtml: optimisticIOUReportAction.message[0].html, hasOutstandingIOU: false, iouReportID: null, }, }, { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, value: { [optimisticIOUReportAction.reportActionID]: { ...optimisticIOUReportAction, @@ -1006,6 +1008,8 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, value: { ...iouReport, + lastMessageText: optimisticIOUReportAction.message[0].text, + lastMessageHtml: optimisticIOUReportAction.message[0].html, hasOutstandingIOU: false, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, }, @@ -1020,7 +1024,7 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho const successData = [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, value: { [optimisticIOUReportAction.reportActionID]: { pendingAction: null, @@ -1046,7 +1050,7 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho const failureData = [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, value: { [optimisticIOUReportAction.reportActionID]: { pendingAction: null, From 398546cb2709445448e9ac08915011c569a6ce64 Mon Sep 17 00:00:00 2001 From: David Bondy Date: Mon, 8 May 2023 15:43:56 -0600 Subject: [PATCH 030/329] remove unnecessary new component since we have a wordmark that we can update and use --- src/components/ImageHeader.js | 40 -------------------------- src/pages/home/sidebar/SidebarLinks.js | 15 ++++------ 2 files changed, 6 insertions(+), 49 deletions(-) delete mode 100644 src/components/ImageHeader.js diff --git a/src/components/ImageHeader.js b/src/components/ImageHeader.js deleted file mode 100644 index 5c82006e392a..000000000000 --- a/src/components/ImageHeader.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import {View} from 'react-native'; -import PropTypes from 'prop-types'; -import _ from 'underscore'; -import styles from '../styles/styles'; -import EnvironmentBadge from './EnvironmentBadge'; - -const propTypes = { - children: PropTypes.node.isRequired, - - /** Subtitle of the header */ - subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - - /** Should we show the environment badge (dev/stg)? */ - shouldShowEnvironmentBadge: PropTypes.bool, -}; - -const defaultProps = { - shouldShowEnvironmentBadge: false, - subtitle: '', -}; -const ImageHeader = props => ( - - - {props.children} - {/* If there's no subtitle then display a fragment to avoid an empty space which moves the main title */} - {_.isString(props.subtitle) - ? Boolean(props.subtitle) && {props.subtitle} - : props.subtitle} - - {props.shouldShowEnvironmentBadge && ( - - )} - -); - -ImageHeader.displayName = 'ImageHeader'; -ImageHeader.propTypes = propTypes; -ImageHeader.defaultProps = defaultProps; -export default ImageHeader; diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 57f2df7b2c18..6630aacd52c2 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -14,7 +14,6 @@ import compose from '../../../libs/compose'; import Navigation from '../../../libs/Navigation/Navigation'; import ROUTES from '../../../ROUTES'; import Icon from '../../../components/Icon'; -import ImageHeader from '../../../components/ImageHeader'; import * as Expensicons from '../../../components/Icon/Expensicons'; import AvatarWithIndicator from '../../../components/AvatarWithIndicator'; import Tooltip from '../../../components/Tooltip'; @@ -31,7 +30,7 @@ import SidebarUtils from '../../../libs/SidebarUtils'; import reportPropTypes from '../../reportPropTypes'; import OfflineWithFeedback from '../../../components/OfflineWithFeedback'; import LHNSkeletonView from '../../../components/LHNSkeletonView'; -import LogoWordmark from '../../../../assets/images/expensify-wordmark.svg'; +import ExpensifyWordmark from '../../../components/ExpensifyWordmark'; import defaultTheme from '../../../styles/themes/default'; const propTypes = { @@ -155,13 +154,11 @@ class SidebarLinks extends React.Component { ]} nativeID="drag-area" > - - - + + + Date: Mon, 8 May 2023 15:47:54 -0600 Subject: [PATCH 031/329] remove in-svg styles so we can modify them via props --- assets/images/expensify-logo--dev.svg | 19 +++++++++---------- assets/images/expensify-logo--staging.svg | 19 +++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/assets/images/expensify-logo--dev.svg b/assets/images/expensify-logo--dev.svg index 42a0f1d8e952..f68fafbad806 100644 --- a/assets/images/expensify-logo--dev.svg +++ b/assets/images/expensify-logo--dev.svg @@ -4,7 +4,6 @@ viewBox="0 0 162 34" style="enable-background:new 0 0 162 34;" xml:space="preserve"> @@ -16,22 +15,22 @@ - - - + + - - - - - + + - diff --git a/assets/images/expensify-logo--staging.svg b/assets/images/expensify-logo--staging.svg index 335c41a294e3..12b0b9bf6e79 100644 --- a/assets/images/expensify-logo--staging.svg +++ b/assets/images/expensify-logo--staging.svg @@ -4,7 +4,6 @@ viewBox="0 0 162 34" style="enable-background:new 0 0 162 34;" xml:space="preserve"> @@ -16,22 +15,22 @@ - - - + + - - - - - + + - From 9968ab733c64201273cc96e67a271930f3136d7b Mon Sep 17 00:00:00 2001 From: David Bondy Date: Mon, 8 May 2023 15:48:31 -0600 Subject: [PATCH 032/329] update the wordmark component to accept more customization so it can be re-used in various places --- src/components/ExpensifyWordmark.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/components/ExpensifyWordmark.js b/src/components/ExpensifyWordmark.js index 11999dad22d2..489675cd8125 100644 --- a/src/components/ExpensifyWordmark.js +++ b/src/components/ExpensifyWordmark.js @@ -1,5 +1,6 @@ import React from 'react'; import {View} from 'react-native'; +import PropTypes from 'prop-types'; import ProductionLogo from '../../assets/images/expensify-wordmark.svg'; import DevLogo from '../../assets/images/expensify-logo--dev.svg'; import StagingLogo from '../../assets/images/expensify-logo--staging.svg'; @@ -16,6 +17,18 @@ import variables from '../styles/variables'; const propTypes = { ...environmentPropTypes, ...windowDimensionsPropTypes, + + /** The styles to apply for the View wrapping the svg */ + containerStyles: PropTypes.array, + + /** Fill color of the svg */ + color: PropTypes.string, +}; + + +const defaultProps = { + containerStyles: [], + color: themeColors.success, }; const logoComponents = { @@ -32,10 +45,10 @@ const ExpensifyWordmark = (props) => { - + ); From 8a14e2e514e63f6fff39adf0b63f60e5867764bc Mon Sep 17 00:00:00 2001 From: David Bondy Date: Mon, 8 May 2023 15:49:10 -0600 Subject: [PATCH 033/329] fix typo --- src/components/ExpensifyWordmark.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ExpensifyWordmark.js b/src/components/ExpensifyWordmark.js index 489675cd8125..0187d0905189 100644 --- a/src/components/ExpensifyWordmark.js +++ b/src/components/ExpensifyWordmark.js @@ -56,6 +56,7 @@ const ExpensifyWordmark = (props) => { ExpensifyWordmark.displayName = 'ExpensifyWordmark'; ExpensifyWordmark.propTypes = propTypes; +ExpensifyWordmark.defaultProps = defaultProps; export default compose( withEnvironment, withWindowDimensions, From d5d38f594ae66d2f3583d338cd5efc625ebd03d9 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Mon, 8 May 2023 23:49:15 +0200 Subject: [PATCH 034/329] remove dimming from switch component --- src/components/Switch.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/Switch.js b/src/components/Switch.js index febd42ddfeba..ef070768fe7a 100644 --- a/src/components/Switch.js +++ b/src/components/Switch.js @@ -52,6 +52,9 @@ class Switch extends Component { accessibilityState={{checked: this.props.isOn}} aria-checked={this.props.isOn} accessibilityLabel={this.props.accessibilityLabel} + + // disable hover dim for switch + hoverDimmingValue={1} > From d6238f2fc889b51c07548d51e3e51b695245e051 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Mon, 8 May 2023 23:49:41 +0200 Subject: [PATCH 035/329] add required accessibility labels for all switches --- src/components/TestToolMenu.js | 3 +++ src/pages/settings/Preferences/PreferencesPage.js | 1 + 2 files changed, 4 insertions(+) diff --git a/src/components/TestToolMenu.js b/src/components/TestToolMenu.js index 7a056ac48565..1d432eaec1b5 100644 --- a/src/components/TestToolMenu.js +++ b/src/components/TestToolMenu.js @@ -48,6 +48,7 @@ const TestToolMenu = props => ( {!CONFIG.IS_USING_LOCAL_WEB && ( User.setShouldUseStagingServer( !lodashGet(props, 'user.shouldUseStagingServer', ApiUtils.isUsingStagingApi()), @@ -59,6 +60,7 @@ const TestToolMenu = props => ( {/* When toggled the app will be forced offline. */} Network.setShouldForceOffline(!props.network.shouldForceOffline)} /> @@ -67,6 +69,7 @@ const TestToolMenu = props => ( {/* When toggled all network requests will fail. */} Network.setShouldFailAllRequests(!props.network.shouldFailAllRequests)} /> diff --git a/src/pages/settings/Preferences/PreferencesPage.js b/src/pages/settings/Preferences/PreferencesPage.js index 3594e37d6deb..904593d0bdb4 100755 --- a/src/pages/settings/Preferences/PreferencesPage.js +++ b/src/pages/settings/Preferences/PreferencesPage.js @@ -70,6 +70,7 @@ const PreferencesPage = (props) => { From 4843a61b63e1870fead3959ce42db561f46446f6 Mon Sep 17 00:00:00 2001 From: David Bondy Date: Mon, 8 May 2023 15:49:58 -0600 Subject: [PATCH 036/329] update usage of wordmark component based on new api --- src/pages/signin/SignInPageLayout/SignInPageContent.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/signin/SignInPageLayout/SignInPageContent.js b/src/pages/signin/SignInPageLayout/SignInPageContent.js index 9991cac126c2..3e0bdb6d3421 100755 --- a/src/pages/signin/SignInPageLayout/SignInPageContent.js +++ b/src/pages/signin/SignInPageLayout/SignInPageContent.js @@ -13,6 +13,7 @@ import OfflineIndicator from '../../../components/OfflineIndicator'; import SignInHeroImage from '../SignInHeroImage'; import * as StyleUtils from '../../../styles/StyleUtils'; import variables from '../../../styles/variables'; +import withEnvironment, {environmentPropTypes} from '../../../components/withEnvironment'; const propTypes = { /** The children to show inside the layout */ @@ -34,6 +35,7 @@ const propTypes = { ...withLocalizePropTypes, ...windowDimensionsPropTypes, + ...environmentPropTypes, }; const SignInPageContent = props => ( @@ -49,7 +51,9 @@ const SignInPageContent = props => ( - + {(props.shouldShowWelcomeHeader && props.welcomeHeader) ? ( @@ -92,4 +96,5 @@ export default compose( withWindowDimensions, withLocalize, withSafeAreaInsets, + withEnvironment, )(SignInPageContent); From 8920c49e498f90dfa8db1173bc98c397ec4d6015 Mon Sep 17 00:00:00 2001 From: David Bondy Date: Mon, 8 May 2023 15:55:45 -0600 Subject: [PATCH 037/329] linter fixes --- src/components/ExpensifyWordmark.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/ExpensifyWordmark.js b/src/components/ExpensifyWordmark.js index 0187d0905189..109af767341b 100644 --- a/src/components/ExpensifyWordmark.js +++ b/src/components/ExpensifyWordmark.js @@ -10,7 +10,6 @@ import withEnvironment, {environmentPropTypes} from './withEnvironment'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import compose from '../libs/compose'; import themeColors from '../styles/themes/default'; -import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; import variables from '../styles/variables'; @@ -19,13 +18,12 @@ const propTypes = { ...windowDimensionsPropTypes, /** The styles to apply for the View wrapping the svg */ - containerStyles: PropTypes.array, + containerStyles: PropTypes.arrayOf(PropTypes.object), /** Fill color of the svg */ color: PropTypes.string, }; - const defaultProps = { containerStyles: [], color: themeColors.success, From 64de69bae63e91dff1ccc3dab8ff8e96daacd85e Mon Sep 17 00:00:00 2001 From: David Bondy Date: Mon, 8 May 2023 15:59:16 -0600 Subject: [PATCH 038/329] fixing proptypes b/c linter complains --- src/components/ExpensifyWordmark.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ExpensifyWordmark.js b/src/components/ExpensifyWordmark.js index 109af767341b..1bcc02e1a82d 100644 --- a/src/components/ExpensifyWordmark.js +++ b/src/components/ExpensifyWordmark.js @@ -18,7 +18,7 @@ const propTypes = { ...windowDimensionsPropTypes, /** The styles to apply for the View wrapping the svg */ - containerStyles: PropTypes.arrayOf(PropTypes.object), + containerStyles: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.string)), /** Fill color of the svg */ color: PropTypes.string, From 2d1eb9b90a6b8b120a91af484c79902bd9d98d13 Mon Sep 17 00:00:00 2001 From: David Bondy Date: Mon, 8 May 2023 16:07:17 -0600 Subject: [PATCH 039/329] remove the unneeded badge component from header component --- src/components/Header.js | 7 ------- src/stories/Header.stories.js | 1 - 2 files changed, 8 deletions(-) diff --git a/src/components/Header.js b/src/components/Header.js index 01752e8e0740..7016631b95a8 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -13,16 +13,12 @@ const propTypes = { /** Subtitle of the header */ subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - /** Should we show the environment badge (dev/stg)? */ - shouldShowEnvironmentBadge: PropTypes.bool, - /** Additional text styles */ // eslint-disable-next-line react/forbid-prop-types textStyles: PropTypes.arrayOf(PropTypes.object), }; const defaultProps = { - shouldShowEnvironmentBadge: false, subtitle: '', textStyles: [], }; @@ -37,9 +33,6 @@ const Header = props => ( ? Boolean(props.subtitle) && {props.subtitle} : props.subtitle} - {props.shouldShowEnvironmentBadge && ( - - )} ); diff --git a/src/stories/Header.stories.js b/src/stories/Header.stories.js index 09c13b54d7d6..1cdc1d7c9c37 100644 --- a/src/stories/Header.stories.js +++ b/src/stories/Header.stories.js @@ -19,7 +19,6 @@ const Template = args =>
; const Default = Template.bind({}); Default.args = { title: 'Chats', - shouldShowEnvironmentBadge: true, }; export default story; From 8d25d70a66956699794d3345d7d548c5d1fd85e1 Mon Sep 17 00:00:00 2001 From: David Bondy Date: Mon, 8 May 2023 16:07:28 -0600 Subject: [PATCH 040/329] include const lib --- src/pages/signin/SignInPageLayout/SignInPageContent.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/signin/SignInPageLayout/SignInPageContent.js b/src/pages/signin/SignInPageLayout/SignInPageContent.js index 3e0bdb6d3421..9904e478f4b0 100755 --- a/src/pages/signin/SignInPageLayout/SignInPageContent.js +++ b/src/pages/signin/SignInPageLayout/SignInPageContent.js @@ -14,6 +14,7 @@ import SignInHeroImage from '../SignInHeroImage'; import * as StyleUtils from '../../../styles/StyleUtils'; import variables from '../../../styles/variables'; import withEnvironment, {environmentPropTypes} from '../../../components/withEnvironment'; +import CONST from '../../../CONST'; const propTypes = { /** The children to show inside the layout */ From b4aee41a456f53fa701982a7423cfcb2daf88eb0 Mon Sep 17 00:00:00 2001 From: David Bondy Date: Mon, 8 May 2023 16:11:07 -0600 Subject: [PATCH 041/329] remove unneeded import --- src/components/Header.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Header.js b/src/components/Header.js index 7016631b95a8..77dd4e60f5a1 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -4,7 +4,6 @@ import PropTypes from 'prop-types'; import _ from 'underscore'; import styles from '../styles/styles'; import Text from './Text'; -import EnvironmentBadge from './EnvironmentBadge'; const propTypes = { /** Title of the Header */ From 24c325926bed24491cf50f59e2aa2cbb4ef297af Mon Sep 17 00:00:00 2001 From: David Bondy Date: Mon, 8 May 2023 16:21:06 -0600 Subject: [PATCH 042/329] linter be damned this is what works --- src/components/ExpensifyWordmark.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ExpensifyWordmark.js b/src/components/ExpensifyWordmark.js index 1bcc02e1a82d..109af767341b 100644 --- a/src/components/ExpensifyWordmark.js +++ b/src/components/ExpensifyWordmark.js @@ -18,7 +18,7 @@ const propTypes = { ...windowDimensionsPropTypes, /** The styles to apply for the View wrapping the svg */ - containerStyles: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.string)), + containerStyles: PropTypes.arrayOf(PropTypes.object), /** Fill color of the svg */ color: PropTypes.string, From 43d95dfe10813a623642a5fa53382391332e5979 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Mon, 8 May 2023 23:45:22 +0100 Subject: [PATCH 043/329] Fix linting --- src/components/AttachmentModal.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 3cf15dacb5fa..69344fb39079 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -4,14 +4,12 @@ import {View, Animated, Keyboard} from 'react-native'; import Str from 'expensify-common/lib/str'; import lodashGet from 'lodash/get'; import lodashExtend from 'lodash/extend'; -import _ from 'underscore'; import CONST from '../CONST'; import Modal from './Modal'; import AttachmentView from './AttachmentView'; import AttachmentCarousel from './AttachmentCarousel'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; -import * as FileUtils from '../libs/fileDownload/FileUtils'; import themeColors from '../styles/themes/default'; import compose from '../libs/compose'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; From cb6ce870799cd32dc54f3c899a2caae249f32a27 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Tue, 9 May 2023 11:36:04 +0300 Subject: [PATCH 044/329] added a checkbox to bankAccountPlaidStep --- .../BankAccountPlaidStep.js | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/BankAccountPlaidStep.js b/src/pages/ReimbursementAccount/BankAccountPlaidStep.js index 65b7dea3e395..364300c90d0d 100644 --- a/src/pages/ReimbursementAccount/BankAccountPlaidStep.js +++ b/src/pages/ReimbursementAccount/BankAccountPlaidStep.js @@ -11,6 +11,9 @@ import withLocalize from '../../components/withLocalize'; import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; import AddPlaidBankAccount from '../../components/AddPlaidBankAccount'; +import CheckboxWithLabel from '../../components/CheckboxWithLabel'; +import TextLink from '../../components/TextLink'; +import Text from '../../components/Text'; import * as ReimbursementAccount from '../../libs/actions/ReimbursementAccount'; import Form from '../../components/Form'; import styles from '../../styles/styles'; @@ -40,9 +43,19 @@ const defaultProps = { class BankAccountPlaidStep extends React.Component { constructor(props) { super(props); + this.validate = this.validate.bind(this); this.submit = this.submit.bind(this); } + validate(values) { + const errorFields = {}; + if (!values.acceptTerms) { + errorFields.acceptTerms = this.props.translate('common.error.acceptTerms'); + } + + return errorFields; + } + submit() { const selectedPlaidBankAccount = _.findWhere(lodashGet(this.props.plaidData, 'bankAccounts', []), { plaidAccountID: lodashGet(this.props.reimbursementAccountDraft, 'plaidAccountID', ''), @@ -83,7 +96,7 @@ class BankAccountPlaidStep extends React.Component { />
({})} + validate={this.validate} onSubmit={this.submit} scrollContextEnabled submitButtonText={this.props.translate('common.saveAndContinue')} @@ -103,6 +116,26 @@ class BankAccountPlaidStep extends React.Component { bankAccountID={bankAccountID} selectedPlaidAccountID={selectedPlaidAccountID} /> + {Boolean(selectedPlaidAccountID) && !_.isEmpty(lodashGet(this.props.plaidData, 'bankAccounts')) && + ( + + {this.props.translate('common.iAcceptThe')} + e.preventDefault()} + > + {this.props.translate('common.expensifyTermsOfService')} + + + )} + defaultValue={this.props.getDefaultStateForField('acceptTerms', false)} + shouldSaveDraft + />} ); From f40e88b44ddea8c45c642c141c19b41ac366dba6 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Tue, 9 May 2023 13:45:34 +0300 Subject: [PATCH 045/329] Indentations added --- .../BankAccountPlaidStep.js | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/pages/ReimbursementAccount/BankAccountPlaidStep.js b/src/pages/ReimbursementAccount/BankAccountPlaidStep.js index 364300c90d0d..7b9f9fbdcd6d 100644 --- a/src/pages/ReimbursementAccount/BankAccountPlaidStep.js +++ b/src/pages/ReimbursementAccount/BankAccountPlaidStep.js @@ -118,24 +118,24 @@ class BankAccountPlaidStep extends React.Component { /> {Boolean(selectedPlaidAccountID) && !_.isEmpty(lodashGet(this.props.plaidData, 'bankAccounts')) && ( - - {this.props.translate('common.iAcceptThe')} - ( + + {this.props.translate('common.iAcceptThe')} + e.preventDefault()} - > - {this.props.translate('common.expensifyTermsOfService')} - - - )} - defaultValue={this.props.getDefaultStateForField('acceptTerms', false)} - shouldSaveDraft - />} + // to call the onPress in the TextLink before the input blur is fired and shift the link element + onMouseDown={e => e.preventDefault()} + > + {this.props.translate('common.expensifyTermsOfService')} + + + )} + defaultValue={this.props.getDefaultStateForField('acceptTerms', false)} + shouldSaveDraft + />} ); From fd4e931b1bc624bb1c57f23180bef6e6f9158baa Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Tue, 9 May 2023 13:06:11 +0200 Subject: [PATCH 046/329] Add remaining accessibilityLabel for switch in TimezoneInitialPage.js --- src/pages/settings/Profile/TimezoneInitialPage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/settings/Profile/TimezoneInitialPage.js b/src/pages/settings/Profile/TimezoneInitialPage.js index c2162af65118..ba7a3491387a 100644 --- a/src/pages/settings/Profile/TimezoneInitialPage.js +++ b/src/pages/settings/Profile/TimezoneInitialPage.js @@ -59,6 +59,7 @@ const TimezoneInitialPage = (props) => { {props.translate('timezonePage.getLocationAutomatically')} From 7f516e4bcfee018ccf1ac54d37471a14b593572c Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Tue, 9 May 2023 14:13:57 +0300 Subject: [PATCH 047/329] Indentation fixed --- .../BankAccountPlaidStep.js | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/pages/ReimbursementAccount/BankAccountPlaidStep.js b/src/pages/ReimbursementAccount/BankAccountPlaidStep.js index 7b9f9fbdcd6d..e8071d2e078f 100644 --- a/src/pages/ReimbursementAccount/BankAccountPlaidStep.js +++ b/src/pages/ReimbursementAccount/BankAccountPlaidStep.js @@ -117,25 +117,25 @@ class BankAccountPlaidStep extends React.Component { selectedPlaidAccountID={selectedPlaidAccountID} /> {Boolean(selectedPlaidAccountID) && !_.isEmpty(lodashGet(this.props.plaidData, 'bankAccounts')) && - ( - - {this.props.translate('common.iAcceptThe')} - ( + + {this.props.translate('common.iAcceptThe')} + e.preventDefault()} - > - {this.props.translate('common.expensifyTermsOfService')} - - - )} - defaultValue={this.props.getDefaultStateForField('acceptTerms', false)} - shouldSaveDraft - />} + // to call the onPress in the TextLink before the input blur is fired and shift the link element + onMouseDown={e => e.preventDefault()} + > + {this.props.translate('common.expensifyTermsOfService')} + + + )} + defaultValue={this.props.getDefaultStateForField('acceptTerms', false)} + shouldSaveDraft + />} ); From c1af6af97db6dfab6a122cc0e1b232df514c5681 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Tue, 9 May 2023 14:24:05 +0300 Subject: [PATCH 048/329] another indentation added --- src/pages/ReimbursementAccount/BankAccountPlaidStep.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/BankAccountPlaidStep.js b/src/pages/ReimbursementAccount/BankAccountPlaidStep.js index e8071d2e078f..3d6c44cc1705 100644 --- a/src/pages/ReimbursementAccount/BankAccountPlaidStep.js +++ b/src/pages/ReimbursementAccount/BankAccountPlaidStep.js @@ -135,7 +135,8 @@ class BankAccountPlaidStep extends React.Component { )} defaultValue={this.props.getDefaultStateForField('acceptTerms', false)} shouldSaveDraft - />} + /> + } ); From 5bf6f9adf0f4a6d2cc7c292fe4cbb5086faf2ecb Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Tue, 9 May 2023 13:53:24 +0200 Subject: [PATCH 049/329] fix error when GenericPressable tries to call onPress when disabled --- .../Pressable/GenericPressable/BaseGenericPressable.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.js b/src/components/Pressable/GenericPressable/BaseGenericPressable.js index 7566272abbc2..b4c2cd796e28 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.js +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.js @@ -121,11 +121,11 @@ const GenericPressable = forwardRef((props, ref) => { hitSlop={shouldUseAutoHitSlop && hitSlop} onLayout={onLayout} ref={ref} - onPress={!isDisabled && onPressHandler} - onLongPress={!isDisabled && onLongPressHandler} - onKeyPress={!isDisabled && onKeyPressHandler} - onPressIn={!isDisabled && onPressIn} - onPressOut={!isDisabled && onPressOut} + onPress={!isDisabled ? onPressHandler : undefined} + onLongPress={!isDisabled ? onLongPressHandler : undefined} + onKeyPress={!isDisabled ? onKeyPressHandler : undefined} + onPressIn={!isDisabled ? onPressIn : undefined} + onPressOut={!isDisabled ? onPressOut : undefined} style={state => [ getCursorStyle(isDisabled, [props.accessibilityRole, props.role].includes('text')), props.style, From a29631b947530833f4722adf56fa9e267850176b Mon Sep 17 00:00:00 2001 From: David Bondy Date: Tue, 9 May 2023 13:52:19 -0600 Subject: [PATCH 050/329] import style props to appease linter --- src/components/ExpensifyWordmark.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ExpensifyWordmark.js b/src/components/ExpensifyWordmark.js index 109af767341b..942ee58f9c8b 100644 --- a/src/components/ExpensifyWordmark.js +++ b/src/components/ExpensifyWordmark.js @@ -12,13 +12,14 @@ import compose from '../libs/compose'; import themeColors from '../styles/themes/default'; import * as StyleUtils from '../styles/StyleUtils'; import variables from '../styles/variables'; +import stylePropTypes from '../styles/stylePropTypes'; const propTypes = { ...environmentPropTypes, ...windowDimensionsPropTypes, /** The styles to apply for the View wrapping the svg */ - containerStyles: PropTypes.arrayOf(PropTypes.object), + containerStyles: stylePropTypes, /** Fill color of the svg */ color: PropTypes.string, From cb7eef601bcdfc3f5de5434f90594273cc1082ee Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 10 May 2023 11:34:27 +0200 Subject: [PATCH 051/329] press dim value for switch is set to 0.8 --- src/components/Switch.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Switch.js b/src/components/Switch.js index ef070768fe7a..1120e2e1ae6a 100644 --- a/src/components/Switch.js +++ b/src/components/Switch.js @@ -55,6 +55,7 @@ class Switch extends Component { // disable hover dim for switch hoverDimmingValue={1} + pressDimmingValue={0.8} > From 22e95bdf92c054edf1d26ae7db72fde5c34955ac Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Wed, 10 May 2023 10:37:31 +0100 Subject: [PATCH 052/329] Wip, prevent certain files from being uploaded --- src/CONST.js | 4 ++++ src/components/AttachmentModal.js | 13 +++++++++++++ src/languages/en.js | 2 +- src/languages/es.js | 2 +- 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index fb7e8ffe0c12..721cfe4560de 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -31,6 +31,10 @@ const CONST = { ARROW_HIDE_DELAY: 3000, API_ATTACHMENT_VALIDATIONS: { + // Same as the PHP layer allows + /* eslint-disable-next-line max-len */ + UNALLOWED_EXTENSIONS: ['ade', 'adp', 'apk', 'appx', 'appxbundle', 'bat', 'cab', 'chm', 'cmd', 'com', 'cpl', 'diagcab', 'diagcfg', 'diagpack', 'dll', 'dmg', 'ex', 'ex_', 'exe', 'hta', 'img', 'ins', 'iso', 'isp', 'jar', 'jnlp', 'js', 'jse', 'lib', 'lnk', 'mde', 'msc', 'msi', 'msix', 'msixbundle', 'msp', 'mst', 'nsh', 'pif', 'ps1', 'scr', 'sct', 'shb', 'sys', 'vb', 'vbe', 'vbs', 'vhd', 'vxd', 'wsc', 'wsf', 'wsh', 'xll'], + // 24 megabytes in bytes, this is limit set on servers, do not update without wider internal discussion MAX_SIZE: 25165824, diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 69344fb39079..1e9d62bcbefe 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -4,12 +4,14 @@ import {View, Animated, Keyboard} from 'react-native'; import Str from 'expensify-common/lib/str'; import lodashGet from 'lodash/get'; import lodashExtend from 'lodash/extend'; +import _ from 'underscore'; import CONST from '../CONST'; import Modal from './Modal'; import AttachmentView from './AttachmentView'; import AttachmentCarousel from './AttachmentCarousel'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; +import * as FileUtils from '../libs/fileDownload/FileUtils'; import themeColors from '../styles/themes/default'; import compose from '../libs/compose'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; @@ -167,6 +169,17 @@ class AttachmentModal extends PureComponent { * @returns {Boolean} */ isValidFile(file) { + const {fileExtension} = FileUtils.splitExtensionFromFileName(lodashGet(file, 'name', '')); + if (_.contains(CONST.API_ATTACHMENT_VALIDATIONS.UNALLOWED_EXTENSIONS, fileExtension.toLowerCase())) { + const invalidReason = this.props.translate('attachmentPicker.notAllowedExtension'); + this.setState({ + isAttachmentInvalid: true, + attachmentInvalidReasonTitle: this.props.translate('attachmentPicker.wrongFileType'), + attachmentInvalidReason: invalidReason, + }); + return false; + } + if (lodashGet(file, 'size', 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { this.setState({ isAttachmentInvalid: true, diff --git a/src/languages/en.js b/src/languages/en.js index 5422adba1af9..718b9ee1f7a5 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -149,7 +149,7 @@ export default { attachmentTooSmall: 'Attachment too small', sizeNotMet: 'Attachment size must be greater than 240 bytes.', wrongFileType: 'Attachment is the wrong type', - notAllowedExtension: 'Attachments must be one of the following types:', + notAllowedExtension: 'This filetype is not allowed', }, avatarCropModal: { title: 'Edit photo', diff --git a/src/languages/es.js b/src/languages/es.js index 1fb5b1c1b826..2db801dbebc5 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -148,7 +148,7 @@ export default { attachmentTooSmall: 'Archivo adjunto demasiado pequeño', sizeNotMet: 'El archivo adjunto debe ser mas grande que 240 bytes.', wrongFileType: 'El tipo del archivo adjunto es incorrecto', - notAllowedExtension: 'Los archivos adjuntos deben ser de uno de los siguientes tipos:', + notAllowedExtension: '// TODO', }, avatarCropModal: { title: 'Editar foto', From 611e44df14fbeedcf30c76d20bc7169ed82ee8b6 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 10 May 2023 12:30:36 +0200 Subject: [PATCH 053/329] trigger the same callback on press and longPress for switch --- src/components/Switch.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/Switch.js b/src/components/Switch.js index 1120e2e1ae6a..7ab83bed70cb 100644 --- a/src/components/Switch.js +++ b/src/components/Switch.js @@ -24,6 +24,7 @@ class Switch extends Component { this.offsetX = new Animated.Value(props.isOn ? this.onPosition : this.offPosition); this.toggleSwitch = this.toggleSwitch.bind(this); + this.toggleAction = this.toggleAction.bind(this); } componentDidUpdate(prevProps) { @@ -34,6 +35,7 @@ class Switch extends Component { this.toggleSwitch(); } + // animates the switch to the on or off position toggleSwitch() { Animated.timing(this.offsetX, { toValue: this.props.isOn ? this.onPosition : this.offPosition, @@ -42,12 +44,18 @@ class Switch extends Component { }).start(); } + // executes the callback passed in as a prop + toggleAction() { + this.props.onToggle(!this.props.isOn); + } + render() { const switchTransform = {transform: [{translateX: this.offsetX}]}; return ( this.props.onToggle(!this.props.isOn)} + onPress={this.toggleAction} + onLongPress={this.toggleAction} accessibilityRole="switch" accessibilityState={{checked: this.props.isOn}} aria-checked={this.props.isOn} From 5ba5beee7d0e4c59d7c95d3879f91fc9bf79a015 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Wed, 10 May 2023 16:26:35 -0400 Subject: [PATCH 054/329] add initial cancel task action with optimistic state and status changes --- src/libs/actions/Task.js | 29 ++++++++++++++++++++++++++++- src/pages/home/HeaderView.js | 5 ++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 12614343f52b..ca6948314b43 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -6,6 +6,7 @@ import * as ReportUtils from '../ReportUtils'; import * as Report from './Report'; import Navigation from '../Navigation/Navigation'; import ROUTES from '../../ROUTES'; +import CONST from '../../CONST'; /** * Clears out the task info from the store @@ -203,4 +204,30 @@ function clearOutTaskInfoAndNavigate(reportID) { Navigation.navigate(ROUTES.NEW_TASK_DETAILS); } -export {createTaskAndNavigate, setTitleValue, setDescriptionValue, setDetailsValue, setAssigneeValue, setShareDestinationValue, clearOutTaskInfo, clearOutTaskInfoAndNavigate}; +function cancelTask(taskReportID, parentReportID, originalStateNum, originalStatusNum) { + const optimisticData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, + value: { + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS.CLOSED, + }, + }, + ]; + + const failureData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, + value: { + stateNum: originalStateNum, + statusNum: originalStatusNum, + }, + }, + ]; + + API.write('CancelTask', {taskReportID}, {optimisticData, failureData}); +} + +export {createTaskAndNavigate, setTitleValue, setDescriptionValue, setDetailsValue, setAssigneeValue, setShareDestinationValue, clearOutTaskInfo, clearOutTaskInfoAndNavigate, cancelTask}; diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 937b05c97997..8facdc714b98 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -26,6 +26,7 @@ import colors from '../../styles/colors'; import reportPropTypes from '../reportPropTypes'; import ONYXKEYS from '../../ONYXKEYS'; import ThreeDotsMenu from '../../components/ThreeDotsMenu'; +import * as Task from '../../libs/actions/Task'; const propTypes = { /** Toggles the navigationMenu open and closed */ @@ -105,9 +106,7 @@ const HeaderView = (props) => { threeDotMenuItems.push({ icon: Expensicons.Trashcan, text: props.translate('common.cancel'), - - // Implementing in https://github.com/Expensify/App/issues/16857 - onSelected: () => {}, + onSelected: () => Task.cancelTask(props.report.reportID, props.report.parentReportID, props.report.stateNum, props.report.statusNum), }); } } From 94e2fb924e30c8cca42abe68c176dce395a7eb14 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Wed, 10 May 2023 23:29:47 +0300 Subject: [PATCH 055/329] removed onMouseDown and replaced the link with CONST.TERMS_URL --- src/pages/ReimbursementAccount/BankAccountManualStep.js | 4 +--- src/pages/ReimbursementAccount/BankAccountPlaidStep.js | 5 +---- src/pages/settings/Payments/AddDebitCardPage.js | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/pages/ReimbursementAccount/BankAccountManualStep.js b/src/pages/ReimbursementAccount/BankAccountManualStep.js index 2e49deecb3bb..02da681858cc 100644 --- a/src/pages/ReimbursementAccount/BankAccountManualStep.js +++ b/src/pages/ReimbursementAccount/BankAccountManualStep.js @@ -118,9 +118,7 @@ class BankAccountManualStep extends React.Component { {this.props.translate('common.iAcceptThe')} e.preventDefault()} + href={CONST.TERMS_URL} > {this.props.translate('common.expensifyTermsOfService')} diff --git a/src/pages/ReimbursementAccount/BankAccountPlaidStep.js b/src/pages/ReimbursementAccount/BankAccountPlaidStep.js index 3d6c44cc1705..d4f505720a95 100644 --- a/src/pages/ReimbursementAccount/BankAccountPlaidStep.js +++ b/src/pages/ReimbursementAccount/BankAccountPlaidStep.js @@ -124,10 +124,7 @@ class BankAccountPlaidStep extends React.Component { {this.props.translate('common.iAcceptThe')} e.preventDefault()} + href={CONST.TERMS_URL} > {this.props.translate('common.expensifyTermsOfService')} diff --git a/src/pages/settings/Payments/AddDebitCardPage.js b/src/pages/settings/Payments/AddDebitCardPage.js index 08f051e251d2..8b127e65e092 100644 --- a/src/pages/settings/Payments/AddDebitCardPage.js +++ b/src/pages/settings/Payments/AddDebitCardPage.js @@ -192,7 +192,7 @@ class DebitCardPage extends Component { LabelComponent={() => ( {`${this.props.translate('common.iAcceptThe')}`} - {`${this.props.translate('common.expensifyTermsOfService')}`} + {`${this.props.translate('common.expensifyTermsOfService')}`} )} style={[styles.mt4]} From ccbe9a938e6842613ba3db16f53d7905a76c2170 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 10 May 2023 23:07:28 +0200 Subject: [PATCH 056/329] disable dimming of PressableWithFeedback when is disabled --- .../Pressable/PressableWithFeedback.js | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/components/Pressable/PressableWithFeedback.js b/src/components/Pressable/PressableWithFeedback.js index 33e557e39ab1..704f2c6c60a6 100644 --- a/src/components/Pressable/PressableWithFeedback.js +++ b/src/components/Pressable/PressableWithFeedback.js @@ -25,26 +25,25 @@ const PressableWithFeedbackDefaultProps = { const PressableWithFeedback = forwardRef((props, ref) => { const propsWithoutStyling = _.omit(props, omittedProps); return ( - - {(state) => ( - - {props.children} - - )} + // eslint-disable-next-line react/jsx-props-no-spreading + + {(state) => { + const shouldDim = !props.disabled && (!!state.pressed || !!state.hovered); + return ( + + {props.children} + + ); + }} ); }); From fde26f3c27174876560f158b5ab510013dbc6b99 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Wed, 10 May 2023 17:12:46 -0400 Subject: [PATCH 057/329] create optimistic cancel task report action --- src/CONST.js | 9 +++++++++ src/libs/ReportUtils.js | 23 +++++++++++++++++++++++ src/libs/actions/Task.js | 19 ++++++++++++++++++- 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/CONST.js b/src/CONST.js index a501dc100f9d..4dd333018c3c 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -464,6 +464,9 @@ const CONST = { UPDATE_TIME_ENABLED: 'POLICYCHANGELOG_UPDATE_TIME_ENABLED', UPDATE_TIME_RATE: 'POLICYCHANGELOG_UPDATE_TIME_RATE', }, + TASK: { + CANCEL: 'TASK_CANCEL', + }, }, }, ARCHIVE_REASON: { @@ -913,6 +916,12 @@ const CONST = { AMOUNT_MAX_LENGTH: 10, }, + TASK: { + REPORT_ACTION_TYPE: { + CANCEL: 'cancel', + }, + }, + GROWL: { SUCCESS: 'success', ERROR: 'error', diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 060e3f6bf1a0..caceeb9cc291 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1847,6 +1847,28 @@ function getWhisperDisplayNames(participants) { return _.map(participants, (login) => getDisplayNameForParticipant(login, !isWhisperOnlyVisibleToCurrentUSer)).join(', '); } +function buildOptimisticCancelTaskReportAction(taskReportID) { + return { + taskReportID, + actionName: CONST.REPORT.ACTIONS.TYPE.TASK.CANCEL, + actorAccountID: currentUserAccountID, + actorEmail: currentUserEmail, + automatic: false, + avatar: lodashGet(currentUserPersonalDetails, 'avatar', getDefaultAvatar(currentUserEmail)), + isAttachment: false, + person: [ + { + style: 'strong', + text: lodashGet(currentUserPersonalDetails, 'displayName', currentUserEmail), + type: 'TEXT', + }, + ], + reportActionID: NumberUtils.rand64(), + shouldShow: true, + created: DateUtils.getDBTime(), + }; +} + export { getReportParticipantsTitle, isReportMessageAttachment, @@ -1925,4 +1947,5 @@ export { canRequestMoney, getWhisperDisplayNames, getWorkspaceAvatar, + buildOptimisticCancelTaskReportAction, }; diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index ca6948314b43..16810ebb96f9 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -205,6 +205,9 @@ function clearOutTaskInfoAndNavigate(reportID) { } function cancelTask(taskReportID, parentReportID, originalStateNum, originalStatusNum) { + const optimisticCancelReportAction = ReportUtils.buildOptimisticCancelTaskReportAction(taskReportID); + const optimisticReportActionID = optimisticCancelReportAction.reportActionID; + const optimisticData = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -214,6 +217,13 @@ function cancelTask(taskReportID, parentReportID, originalStateNum, originalStat statusNum: CONST.REPORT.STATUS.CLOSED, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, + value: { + [optimisticReportActionID]: optimisticCancelReportAction, + }, + }, ]; const failureData = [ @@ -225,9 +235,16 @@ function cancelTask(taskReportID, parentReportID, originalStateNum, originalStat statusNum: originalStatusNum, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, + value: { + [optimisticReportActionID]: null, + }, + }, ]; - API.write('CancelTask', {taskReportID}, {optimisticData, failureData}); + API.write('CancelTask', {taskReportID, optimisticReportActionID}, {optimisticData, failureData}); } export {createTaskAndNavigate, setTitleValue, setDescriptionValue, setDetailsValue, setAssigneeValue, setShareDestinationValue, clearOutTaskInfo, clearOutTaskInfoAndNavigate, cancelTask}; From b2b4d0db22b5a56e7c3f61a519ee2d71556fd0f2 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 10 May 2023 23:14:05 +0200 Subject: [PATCH 058/329] run prettier on changed files --- .../Pressable/GenericPressable/BaseGenericPressable.js | 2 +- src/components/Pressable/PressableWithFeedback.js | 8 ++++++-- src/components/Switch.js | 1 - 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.js b/src/components/Pressable/GenericPressable/BaseGenericPressable.js index 231a023818d6..5de5e8774ebb 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.js +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.js @@ -124,7 +124,7 @@ const GenericPressable = forwardRef((props, ref) => { onKeyPress={!isDisabled ? onKeyPressHandler : undefined} onPressIn={!isDisabled ? onPressIn : undefined} onPressOut={!isDisabled ? onPressOut : undefined} - style={state => [ + style={(state) => [ getCursorStyle(isDisabled, [props.accessibilityRole, props.role].includes('text')), props.style, isScreenReaderActive && StyleUtils.parseStyleFromFunction(props.screenReaderActiveStyle, state), diff --git a/src/components/Pressable/PressableWithFeedback.js b/src/components/Pressable/PressableWithFeedback.js index 704f2c6c60a6..1e2ee03666a1 100644 --- a/src/components/Pressable/PressableWithFeedback.js +++ b/src/components/Pressable/PressableWithFeedback.js @@ -25,8 +25,12 @@ const PressableWithFeedbackDefaultProps = { const PressableWithFeedback = forwardRef((props, ref) => { const propsWithoutStyling = _.omit(props, omittedProps); return ( - // eslint-disable-next-line react/jsx-props-no-spreading - + {(state) => { const shouldDim = !props.disabled && (!!state.pressed || !!state.hovered); return ( diff --git a/src/components/Switch.js b/src/components/Switch.js index 7ab83bed70cb..f0fd20f7af48 100644 --- a/src/components/Switch.js +++ b/src/components/Switch.js @@ -60,7 +60,6 @@ class Switch extends Component { accessibilityState={{checked: this.props.isOn}} aria-checked={this.props.isOn} accessibilityLabel={this.props.accessibilityLabel} - // disable hover dim for switch hoverDimmingValue={1} pressDimmingValue={0.8} From 067e77ef20fb2fa17e9aff98b6bd2d31757b4c24 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Wed, 10 May 2023 17:20:56 -0400 Subject: [PATCH 059/329] remove unused const --- src/CONST.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index 4dd333018c3c..a0b2d32102a7 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -916,12 +916,6 @@ const CONST = { AMOUNT_MAX_LENGTH: 10, }, - TASK: { - REPORT_ACTION_TYPE: { - CANCEL: 'cancel', - }, - }, - GROWL: { SUCCESS: 'success', ERROR: 'error', From 202122a111e28c9cbfafe3fe26c756ca70340c9e Mon Sep 17 00:00:00 2001 From: David Bondy Date: Wed, 10 May 2023 18:46:23 -0600 Subject: [PATCH 060/329] create method to find if a thread exists or not --- src/libs/ReportUtils.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 060e3f6bf1a0..9f1383a32305 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1847,6 +1847,24 @@ function getWhisperDisplayNames(participants) { return _.map(participants, (login) => getDisplayNameForParticipant(login, !isWhisperOnlyVisibleToCurrentUSer)).join(', '); } +/** + * Used to determine if a thread exists already or not for a given reportActionID + * + * @param {String} + * @returns {Boolean} + */ +function doesThreadExistForReportActionID(reportActionID) { + const thread = _.find(allReports, (report) => { + if (!report.parentReportActionID) { + return false; + } + + return report.parentReportActionID === reportActionID; + }); + + return !_.isEmpty(thread); +} + export { getReportParticipantsTitle, isReportMessageAttachment, @@ -1925,4 +1943,5 @@ export { canRequestMoney, getWhisperDisplayNames, getWorkspaceAvatar, + doesThreadExistForReportActionID, }; From fa1b3dc1509c6ebf5cd29662ee25d556950ca031 Mon Sep 17 00:00:00 2001 From: David Bondy Date: Wed, 10 May 2023 18:46:59 -0600 Subject: [PATCH 061/329] add rough sketch of what needs to happen to create a thread on demand --- src/components/ReportActionItem/IOUAction.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/ReportActionItem/IOUAction.js b/src/components/ReportActionItem/IOUAction.js index c304129517b6..f15f9b6505c6 100644 --- a/src/components/ReportActionItem/IOUAction.js +++ b/src/components/ReportActionItem/IOUAction.js @@ -15,6 +15,7 @@ import Navigation from '../../libs/Navigation/Navigation'; import ROUTES from '../../ROUTES'; import styles from '../../styles/styles'; import * as IOUUtils from '../../libs/IOUUtils'; +import {doesThreadExistForReportActionID} from '../../libs/ReportUtils'; const propTypes = { /** All the data of the action */ @@ -71,6 +72,10 @@ const defaultProps = { const IOUAction = (props) => { const hasMultipleParticipants = props.chatReport.participants.length > 1; const onIOUPreviewPressed = () => { + if (!doesThreadExistForReportActionID(props.action.reportActionID)) { + // optimistically create reportID + // pass it along to OpenReport since that is build to create a thread when needed, not sure what to do about the navigation stuff below... + } if (hasMultipleParticipants) { Navigation.navigate(ROUTES.getReportParticipantsRoute(props.chatReportID)); } else { From 60d6931b95966af5be1a0700b52d833fb00ef992 Mon Sep 17 00:00:00 2001 From: Eric Han Date: Thu, 11 May 2023 09:40:38 +0800 Subject: [PATCH 062/329] return false if report action is not an attachment --- src/libs/isReportMessageAttachment.js | 3 ++- tests/unit/isReportMessageAttachmentTest.js | 22 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 tests/unit/isReportMessageAttachmentTest.js diff --git a/src/libs/isReportMessageAttachment.js b/src/libs/isReportMessageAttachment.js index e524d5f79f64..852dc924fdbb 100644 --- a/src/libs/isReportMessageAttachment.js +++ b/src/libs/isReportMessageAttachment.js @@ -8,5 +8,6 @@ import CONST from '../CONST'; * @returns {Boolean} */ export default function isReportMessageAttachment({text, html}) { - return text === CONST.ATTACHMENT_MESSAGE_TEXT && html !== CONST.ATTACHMENT_MESSAGE_TEXT; + const regex = new RegExp(` ${CONST.ATTACHMENT_SOURCE_ATTRIBUTE}="(.+?)"`, 'i'); + return text === CONST.ATTACHMENT_MESSAGE_TEXT && !!html.match(regex); } diff --git a/tests/unit/isReportMessageAttachmentTest.js b/tests/unit/isReportMessageAttachmentTest.js new file mode 100644 index 000000000000..8338513a7e7e --- /dev/null +++ b/tests/unit/isReportMessageAttachmentTest.js @@ -0,0 +1,22 @@ +import isReportMessageAttachment from '../../src/libs/isReportMessageAttachment'; + +describe('isReportMessageAttachment', () => { + it('returns true if a report action is attachment', () => { + const message = { + text: '[Attachment]', + html: '', + }; + expect(isReportMessageAttachment(message)).toBe(true); + }); + + it('returns false if a report action is not attachment', () => { + let message = {text: '[Attachment]', html: '[Attachment]'}; + expect(isReportMessageAttachment(message)).toBe(false); + + message = {text: '[Attachment]', html: '[Attachment]'}; + expect(isReportMessageAttachment(message)).toBe(false); + + message = {text: '[Attachment]', html: '[Attachment]'}; + expect(isReportMessageAttachment(message)).toBe(false); + }); +}); From 7a7d3d62c55f9d0f8c1aab0f2b368db8181f64b8 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 11 May 2023 10:38:00 +0100 Subject: [PATCH 063/329] allow pasting magic codes in iOS Safari --- src/components/MagicCodeInput.js | 9 ++++++++- src/styles/styles.js | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index b33e686001db..1b1b8f9a0aa7 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -8,6 +8,7 @@ import CONST from '../CONST'; import Text from './Text'; import TextInput from './TextInput'; import FormHelpMessage from './FormHelpMessage'; +import * as Browser from '../libs/Browser'; const propTypes = { /** Name attribute for the input */ @@ -84,6 +85,10 @@ function MagicCodeInput(props) { const [focusedIndex, setFocusedIndex] = useState(0); const [editIndex, setEditIndex] = useState(0); + // For Safari on iOS, a transparent background will be used instead of opacity: 0, so that magic code pasting can work. + // Alternate styling will be applied based on this condition. + const isMobileSafari = Browser.isMobileSafari(); + useImperativeHandle(props.innerRef, () => ({ focus() { setFocusedIndex(0); @@ -266,7 +271,7 @@ function MagicCodeInput(props) { {decomposeString(props.value)[index] || ''} - + (inputRefs.current[index] = ref)} autoFocus={index === 0 && props.autoFocus} @@ -291,6 +296,8 @@ function MagicCodeInput(props) { onKeyPress={onKeyPress} onPress={(event) => onPress(event, index)} onFocus={onFocus} + caretHidden={isMobileSafari} + inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} /> diff --git a/src/styles/styles.js b/src/styles/styles.js index a8e86b220a03..7f49c50743cc 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -2332,6 +2332,10 @@ const styles = { lineHeight: variables.inputHeight, }, + magicCodeInputTransparent: { + color: 'transparent', + }, + iouAmountText: { ...headlineFont, fontSize: variables.iouAmountTextSize, From f0b4058b13c52fcb1b4869c05385e25c5fd61ec3 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Thu, 11 May 2023 15:09:58 +0100 Subject: [PATCH 064/329] Truncate long names --- src/components/AvatarWithDisplayName.js | 2 +- src/components/MoneyRequestHeader.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index 3be660ab09c5..558adf18a2be 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -53,7 +53,7 @@ const AvatarWithDisplayName = (props) => { return ( {Boolean(props.report && title) && ( - + {isExpenseReport ? ( { {props.translate('common.to')} - + - + Date: Fri, 12 May 2023 01:15:06 +0700 Subject: [PATCH 065/329] refactor: remove User from EmojiUtils to avoid import cycle --- src/components/EmojiPicker/EmojiPickerMenu/index.js | 3 ++- src/components/EmojiPicker/EmojiPickerMenu/index.native.js | 3 ++- src/libs/EmojiUtils.js | 6 +++--- src/pages/home/report/ReportActionCompose.js | 3 ++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js index bf1f3710424c..0c4da56a1c43 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js @@ -236,7 +236,8 @@ class EmojiPickerMenu extends Component { * @param {Object} emojiObject */ addToFrequentAndSelectEmoji(emoji, emojiObject) { - EmojiUtils.addToFrequentlyUsedEmojis(this.props.frequentlyUsedEmojis, emojiObject); + const frequentEmojiList = EmojiUtils.addToFrequentlyUsedEmojis(this.props.frequentlyUsedEmojis, emojiObject); + User.updateFrequentlyUsedEmojis(frequentEmojiList); this.props.onEmojiSelected(emoji, emojiObject); } diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js index 3fe44d7d1dca..3b9e7dc518c3 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js @@ -79,7 +79,8 @@ class EmojiPickerMenu extends Component { * @param {Object} emojiObject */ addToFrequentAndSelectEmoji(emoji, emojiObject) { - EmojiUtils.addToFrequentlyUsedEmojis(this.props.frequentlyUsedEmojis, emojiObject); + const frequentEmojiList = EmojiUtils.addToFrequentlyUsedEmojis(this.props.frequentlyUsedEmojis, emojiObject); + User.updateFrequentlyUsedEmojis(frequentEmojiList); this.props.onEmojiSelected(emoji, emojiObject); } diff --git a/src/libs/EmojiUtils.js b/src/libs/EmojiUtils.js index 78e0eda27363..e0d130acb78e 100644 --- a/src/libs/EmojiUtils.js +++ b/src/libs/EmojiUtils.js @@ -3,7 +3,6 @@ import lodashOrderBy from 'lodash/orderBy'; import moment from 'moment'; import Str from 'expensify-common/lib/str'; import CONST from '../CONST'; -import * as User from './actions/User'; import emojisTrie from './EmojiTrie'; import FrequentlyUsed from '../../assets/images/history.svg'; @@ -164,9 +163,10 @@ function mergeEmojisWithFrequentlyUsedEmojis(emojis, frequentlyUsedEmojis = []) } /** - * Update the frequently used emojis list by usage and sync with API + * Get the updated frequently used emojis list by usage * @param {Object[]} frequentlyUsedEmojis * @param {Object} newEmoji + * @return {Object[]} */ function addToFrequentlyUsedEmojis(frequentlyUsedEmojis, newEmoji) { let frequentEmojiList = frequentlyUsedEmojis; @@ -188,7 +188,7 @@ function addToFrequentlyUsedEmojis(frequentlyUsedEmojis, newEmoji) { // Second sorting is required so that new emoji is properly placed at sort-ordered location frequentEmojiList = lodashOrderBy(frequentEmojiList, ['count', 'lastUpdatedAt'], ['desc', 'desc']); - User.updateFrequentlyUsedEmojis(frequentEmojiList); + return frequentEmojiList; } /** diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index f985014c1dc3..c975b85bcac7 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -607,7 +607,8 @@ class ReportActionCompose extends React.Component { }, suggestedEmojis: [], })); - EmojiUtils.addToFrequentlyUsedEmojis(this.props.frequentlyUsedEmojis, emojiObject); + const frequentEmojiList = EmojiUtils.addToFrequentlyUsedEmojis(this.props.frequentlyUsedEmojis, emojiObject); + User.updateFrequentlyUsedEmojis(frequentEmojiList); } /** From ce7ee2ec81a6142bb8c2c691cd2304f363d73b20 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 12 May 2023 01:22:36 +0700 Subject: [PATCH 066/329] fix: preferredSkinTone type warning --- src/libs/EmojiUtils.js | 26 +++++++++++++++++++- src/libs/actions/Report.js | 11 ++------- src/pages/home/report/ReportActionCompose.js | 1 + 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/libs/EmojiUtils.js b/src/libs/EmojiUtils.js index e0d130acb78e..6764d3474e59 100644 --- a/src/libs/EmojiUtils.js +++ b/src/libs/EmojiUtils.js @@ -270,4 +270,28 @@ function suggestEmojis(text, limit = 5) { return []; } -export {getHeaderEmojis, mergeEmojisWithFrequentlyUsedEmojis, addToFrequentlyUsedEmojis, containsOnlyEmojis, replaceEmojis, suggestEmojis, trimEmojiUnicode, getEmojiCodeWithSkinColor}; +/** + * Retrieve preferredSkinTone as Number to prevent legacy 'default' String value + * + * @param {Number | String} val + * @returns {Number} + */ +const getPreferredSkinToneIndex = (val) => { + if (!_.isNull(val) && Number.isInteger(Number(val))) { + return val; + } + + return CONST.EMOJI_DEFAULT_SKIN_TONE; +}; + +export { + getHeaderEmojis, + mergeEmojisWithFrequentlyUsedEmojis, + addToFrequentlyUsedEmojis, + containsOnlyEmojis, + replaceEmojis, + suggestEmojis, + trimEmojiUnicode, + getEmojiCodeWithSkinColor, + getPreferredSkinToneIndex, +}; diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 26d9b9da7357..b02737de1bd7 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -21,6 +21,7 @@ import * as ReportActionsUtils from '../ReportActionsUtils'; import * as OptionsListUtils from '../OptionsListUtils'; import * as Localize from '../Localize'; import * as CollectionUtils from '../CollectionUtils'; +import * as EmojiUtils from '../EmojiUtils'; let currentUserEmail; let currentUserAccountID; @@ -40,15 +41,7 @@ Onyx.connect({ let preferredSkinTone; Onyx.connect({ key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, - callback: (val) => { - // the preferred skin tone is sometimes still "default", although it - // was changed that "default" has become -1. - if (!_.isNull(val) && Number.isInteger(Number(val))) { - preferredSkinTone = val; - } else { - preferredSkinTone = -1; - } - }, + callback: EmojiUtils.getPreferredSkinToneIndex, }); const allReportActions = {}; diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index c975b85bcac7..b6863d597c7a 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -1208,6 +1208,7 @@ export default compose( }, preferredSkinTone: { key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, + selector: EmojiUtils.getPreferredSkinToneIndex, }, reports: { key: ONYXKEYS.COLLECTION.REPORT, From 66f7c0ab8c64749b8d2e6a3ec2f7fe6357b38086 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 12 May 2023 01:27:19 +0700 Subject: [PATCH 067/329] prevent preferredSkinTone's legacy value where necessary --- src/components/EmojiPicker/EmojiPickerMenu/index.js | 1 + src/components/EmojiPicker/EmojiPickerMenu/index.native.js | 1 + src/components/Reactions/MiniQuickEmojiReactions.js | 2 ++ .../Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js | 2 ++ src/pages/home/report/ReportActionItem.js | 2 ++ 5 files changed, 8 insertions(+) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js index 0c4da56a1c43..a443662d7192 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js @@ -548,6 +548,7 @@ export default compose( withOnyx({ preferredSkinTone: { key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, + selector: EmojiUtils.getPreferredSkinToneIndex, }, frequentlyUsedEmojis: { key: ONYXKEYS.FREQUENTLY_USED_EMOJIS, diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js index 3b9e7dc518c3..3852daa405e3 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js @@ -185,6 +185,7 @@ export default compose( withOnyx({ preferredSkinTone: { key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, + selector: EmojiUtils.getPreferredSkinToneIndex, }, frequentlyUsedEmojis: { key: ONYXKEYS.FREQUENTLY_USED_EMOJIS, diff --git a/src/components/Reactions/MiniQuickEmojiReactions.js b/src/components/Reactions/MiniQuickEmojiReactions.js index e4bc4664a70c..4519258930a4 100644 --- a/src/components/Reactions/MiniQuickEmojiReactions.js +++ b/src/components/Reactions/MiniQuickEmojiReactions.js @@ -17,6 +17,7 @@ import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; import getPreferredEmojiCode from './getPreferredEmojiCode'; +import * as EmojiUtils from '../../libs/EmojiUtils'; const propTypes = { ...baseQuickEmojiReactionsPropTypes, @@ -96,6 +97,7 @@ export default compose( withOnyx({ preferredSkinTone: { key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, + selector: EmojiUtils.getPreferredSkinToneIndex, }, }), )(MiniQuickEmojiReactions); diff --git a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js index 705897b4893e..2b69566084db 100644 --- a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js +++ b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js @@ -10,6 +10,7 @@ import styles from '../../../styles/styles'; import ONYXKEYS from '../../../ONYXKEYS'; import getPreferredEmojiCode from '../getPreferredEmojiCode'; import Tooltip from '../../Tooltip'; +import * as EmojiUtils from '../../../libs/EmojiUtils'; const baseQuickEmojiReactionsPropTypes = { /** @@ -78,6 +79,7 @@ BaseQuickEmojiReactions.defaultProps = defaultProps; export default withOnyx({ preferredSkinTone: { key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, + selector: EmojiUtils.getPreferredSkinToneIndex, }, })(BaseQuickEmojiReactions); diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index b2c86d55204e..08972134d5a1 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -48,6 +48,7 @@ import personalDetailsPropType from '../../personalDetailsPropType'; import ReportActionItemDraft from './ReportActionItemDraft'; import TaskPreview from '../../../components/ReportActionItem/TaskPreview'; import * as ReportActionUtils from '../../../libs/ReportActionsUtils'; +import * as EmojiUtils from '../../../libs/EmojiUtils'; const propTypes = { /** Report for this action */ @@ -400,6 +401,7 @@ export default compose( withOnyx({ preferredSkinTone: { key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, + selector: EmojiUtils.getPreferredSkinToneIndex, }, }), )(ReportActionItem); From bf8240a34236dcd5708edf99cda60a4a8c3028e0 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Thu, 11 May 2023 20:08:49 +0100 Subject: [PATCH 068/329] fix avatar background, position and border radius --- src/components/AvatarWithDisplayName.js | 2 ++ src/styles/StyleUtils.js | 2 +- src/styles/styles.js | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index 558adf18a2be..62a4caf098c1 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -8,6 +8,7 @@ import participantPropTypes from './participantPropTypes'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import styles from '../styles/styles'; +import themeColors from '../styles/themes/default'; import SubscriptAvatar from './SubscriptAvatar'; import * as ReportUtils from '../libs/ReportUtils'; import Avatar from './Avatar'; @@ -56,6 +57,7 @@ const AvatarWithDisplayName = (props) => { {isExpenseReport ? ( Date: Thu, 11 May 2023 17:14:43 -0400 Subject: [PATCH 069/329] make canceled task actions visible --- src/libs/ReportActionsUtils.js | 6 +++++- src/libs/ReportUtils.js | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index de77e7f5243e..bdecb8d9cdc7 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -197,7 +197,11 @@ function shouldReportActionBeVisible(reportAction, key) { } // Filter out any unsupported reportAction types - if (!_.has(CONST.REPORT.ACTIONS.TYPE, reportAction.actionName) && !_.contains(_.values(CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG), reportAction.actionName)) { + if ( + !_.has(CONST.REPORT.ACTIONS.TYPE, reportAction.actionName) && + !_.contains(_.values(CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG), reportAction.actionName) && + !_.contains(_.values(CONST.REPORT.ACTIONS.TYPE.TASK), reportAction.actionName) + ) { return false; } diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index c38479cad699..3cf3ffd51fd0 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1913,6 +1913,18 @@ function buildOptimisticCancelTaskReportAction(taskReportID) { reportActionID: NumberUtils.rand64(), shouldShow: true, created: DateUtils.getDBTime(), + message: [ + { + type: CONST.REPORT.MESSAGE.TYPE.TEXT, + style: 'strong', + text: 'You', + }, + { + type: CONST.REPORT.MESSAGE.TYPE.TEXT, + style: 'normal', + text: ' closed this report', + }, + ], }; } From ab5a6ff4b991e8f0f20bbb9f192f59fce79ab2bc Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Thu, 11 May 2023 22:39:52 +0100 Subject: [PATCH 070/329] fix MoneyRequestHeader padding --- src/components/MoneyRequestHeader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index f7f739b81d3c..f3dc6eff7d97 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -73,7 +73,7 @@ const MoneyRequestHeader = (props) => { shouldShowBackButton={props.isSmallScreenWidth} onBackButtonPress={() => Navigation.navigate(ROUTES.HOME)} /> - + {props.translate('common.to')} From 3ac889167fabbf93187dc3d00745e2c6fba9a831 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Thu, 11 May 2023 17:54:02 -0400 Subject: [PATCH 071/329] add CancelTaskAction --- .../ReportActionItem/CancelTaskAction.js | 67 +++++++++++++++++++ src/languages/en.js | 3 + src/pages/home/report/ReportActionItem.js | 3 + 3 files changed, 73 insertions(+) create mode 100644 src/components/ReportActionItem/CancelTaskAction.js diff --git a/src/components/ReportActionItem/CancelTaskAction.js b/src/components/ReportActionItem/CancelTaskAction.js new file mode 100644 index 000000000000..7d8746675b29 --- /dev/null +++ b/src/components/ReportActionItem/CancelTaskAction.js @@ -0,0 +1,67 @@ +import React from 'react'; +import {View, Pressable} from 'react-native'; +import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; +import Navigation from '../../libs/Navigation/Navigation'; +import withLocalize, {withLocalizePropTypes} from '../withLocalize'; +import ROUTES from '../../ROUTES'; +import compose from '../../libs/compose'; +import ONYXKEYS from '../../ONYXKEYS'; +import Text from '../Text'; +import styles from '../../styles/styles'; +import Icon from '../Icon'; +import * as Expensicons from '../Icon/Expensicons'; + +const propTypes = { + /** The ID of the associated taskReport */ + taskReportID: PropTypes.string.isRequired, + + /* Onyx Props */ + + taskReport: PropTypes.shape({ + /** Title of the task */ + reportName: PropTypes.string, + + /** Email address of the manager in this iou report */ + managerEmail: PropTypes.string, + + /** Email address of the creator of this iou report */ + ownerEmail: PropTypes.string, + }), + + ...withLocalizePropTypes, +}; + +const defaultProps = { + taskReport: {}, +}; +const CancelTaskAction = (props) => { + const taskReportID = props.taskReportID; + const taskReportName = props.taskReport.reportName || ''; + + return ( + Navigation.navigate(ROUTES.getReportRoute(taskReportID))} + style={[styles.flexRow, styles.justifyContentBetween]} + > + + {props.translate('task.canceled')} + {` ${taskReportName}`} + + + + ); +}; + +CancelTaskAction.propTypes = propTypes; +CancelTaskAction.defaultProps = defaultProps; +CancelTaskAction.displayName = 'CancelTaskAction'; + +export default compose( + withLocalize, + withOnyx({ + taskReport: { + key: ({taskReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, + }, + }), +)(CancelTaskAction); diff --git a/src/languages/en.js b/src/languages/en.js index b9f49302a43a..ad10961c60ea 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -1183,6 +1183,9 @@ export default { pleaseEnterTaskAssignee: 'Please select an assignee', pleaseEnterTaskDestination: 'Please select a share destination', }, + task: { + canceled: 'Canceled task', + }, statementPage: { generatingPDF: "We're generating your PDF right now. Please come back later!", }, diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index b2c86d55204e..f019b4a3096d 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -37,6 +37,7 @@ import reportPropTypes from '../../reportPropTypes'; import {ShowContextMenuContext} from '../../../components/ShowContextMenuContext'; import focusTextInputAfterAnimation from '../../../libs/focusTextInputAfterAnimation'; import ChronosOOOListActions from '../../../components/ReportActionItem/ChronosOOOListActions'; +import CancelTaskAction from '../../../components/ReportActionItem/CancelTaskAction'; import ReportActionItemReactions from '../../../components/Reactions/ReportActionItemReactions'; import * as Report from '../../../libs/actions/Report'; import withLocalize from '../../../components/withLocalize'; @@ -200,6 +201,8 @@ class ReportActionItem extends Component { isHovered={hovered} /> ); + } else if (this.props.action.actionName === CONST.REPORT.ACTIONS.TYPE.TASK.CANCEL) { + children = ; } else { const message = _.last(lodashGet(this.props.action, 'message', [{}])); const isAttachment = _.has(this.props.action, 'isAttachment') ? this.props.action.isAttachment : ReportUtils.isReportMessageAttachment(message); From e76740c0577c371eb1ecfb98f1334d0f63ad8da2 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Fri, 12 May 2023 03:27:28 +0530 Subject: [PATCH 072/329] Fix the icon --- src/pages/home/sidebar/SidebarLinks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 9a6c23d0861b..e6d1b5129941 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -148,7 +148,7 @@ class SidebarLinks extends React.Component { style={[styles.flexRow, styles.ph5, styles.pv3, styles.justifyContentBetween, styles.alignItemsCenter]} nativeID="drag-area" > - + From b48c214e742c5fbe565da4fae0add9f5fddeafcf Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 11 May 2023 15:25:54 -0700 Subject: [PATCH 073/329] Add TASKCOMPLETED constant --- src/CONST.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CONST.js b/src/CONST.js index 614ec00917c3..e8f793df6f5e 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -409,6 +409,7 @@ const CONST = { IOU: 'IOU', RENAMED: 'RENAMED', CHRONOSOOOLIST: 'CHRONOSOOOLIST', + TASKCOMPLETED: 'TASKCOMPLETED', POLICYCHANGELOG: { ADD_APPROVER_RULE: 'POLICYCHANGELOG_ADD_APPROVER_RULE', ADD_CATEGORY: 'POLICYCHANGELOG_ADD_CATEGORY', From 15e000155abf7ea2557b860a3a6d46cc007b7098 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 11 May 2023 15:26:12 -0700 Subject: [PATCH 074/329] Add new method for task actions --- src/libs/ReportUtils.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 18a479675110..ef5d4293ab53 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1063,6 +1063,19 @@ function buildOptimisticTaskCommentReportAction(taskReportID, taskTitle, taskAss return reportAction; } +function buildOptimisticTaskReportAction(taskReportID, text, actionName) { + const reportAction = buildOptimisticAddCommentReportAction(text); + reportAction.reportAction.message[0].taskReportID = taskReportID; + reportAction.reportAction.actionName = actionName; + + // These parameters are not saved on the reportAction, but are used to display the task in the UI + // Added when we fetch the reportActions on a report + reportAction.reportAction.originalMessage = { + html: reportAction.reportAction.message[0].html, + taskReportID: reportAction.reportAction.message[0].taskReportID, + }; +} + /** * Builds an optimistic IOU report with a randomly generated reportID * @@ -1964,6 +1977,7 @@ export { buildOptimisticIOUReportAction, buildOptimisticAddCommentReportAction, buildOptimisticTaskCommentReportAction, + buildOptimisticTaskReportAction, shouldReportBeInOptionList, getChatByParticipants, getChatByParticipantsAndPolicy, From ae1562a3d10c7e0413af43147276a255602b2774 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 11 May 2023 15:26:19 -0700 Subject: [PATCH 075/329] Create completeTask command --- src/libs/actions/Task.js | 41 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index d55d6b910c83..d1d0af7215c0 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -8,6 +8,7 @@ import * as Report from './Report'; import Navigation from '../Navigation/Navigation'; import ROUTES from '../../ROUTES'; import DateUtils from '../DateUtils'; +import CONST from '../../CONST'; /** * Clears out the task info from the store @@ -151,6 +152,46 @@ function createTaskAndNavigate(currentUserEmail, parentReportID, title, descript Navigation.navigate(ROUTES.getReportRoute(optimisticTaskReport.reportID)); } +function completeTask(taskReportID, parentReportID) { + // TODO: Update the text to be the task title + const completedTaskReportAction = ReportUtils.buildOptimisticTaskReportAction(taskReportID, 'Completed task: ', CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED); + + const optimisticData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, + value: { + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS.APPROVED, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, + value: {[completedTaskReportAction.reportAction.reportActionID]: completedTaskReportAction.reportAction}, + }, + ]; + + const successData = []; + const failureData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, + value: { + stateNum: CONST.REPORT.STATE_NUM.OPEN, + statusNum: CONST.REPORT.STATUS.PENDING, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, + value: {[completedTaskReportAction.reportAction.reportActionID]: {pendingAction: null}}, + }, + ]; + + API.write('CompleteTask', {taskReportID}, {optimisticData, successData, failureData}); +} + /** * Sets the title and description values for the task * @param {string} title From b9e5209cd22ea499072bbffa866ff9af91fc2931 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Fri, 12 May 2023 04:11:39 +0530 Subject: [PATCH 076/329] Logo dimensions adjustments --- src/components/ExpensifyWordmark.js | 11 ++++++----- src/pages/home/sidebar/SidebarLinks.js | 7 ++++++- src/styles/StyleUtils.js | 8 ++++++++ src/styles/variables.js | 3 +++ 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/components/ExpensifyWordmark.js b/src/components/ExpensifyWordmark.js index d2416779e078..6723640cbbec 100644 --- a/src/components/ExpensifyWordmark.js +++ b/src/components/ExpensifyWordmark.js @@ -41,11 +41,12 @@ const ExpensifyWordmark = (props) => { const LogoComponent = logoComponents[props.environment] || AdHocLogo; return ( <> - diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index e6d1b5129941..aef816e155e4 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -6,6 +6,7 @@ import _ from 'underscore'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import {Freeze} from 'react-freeze'; +import withEnvironment, {environmentPropTypes} from '../../../components/withEnvironment'; import styles from '../../../styles/styles'; import * as StyleUtils from '../../../styles/StyleUtils'; import ONYXKEYS from '../../../ONYXKEYS'; @@ -32,6 +33,7 @@ import OfflineWithFeedback from '../../../components/OfflineWithFeedback'; import ExpensifyWordmark from '../../../components/ExpensifyWordmark'; import defaultTheme from '../../../styles/themes/default'; import OptionsListSkeletonView from '../../../components/OptionsListSkeletonView'; +import variables from '../../../styles/variables'; const propTypes = { /** Toggles the navigation menu open and closed */ @@ -77,6 +79,7 @@ const propTypes = { priorityMode: PropTypes.string, ...withLocalizePropTypes, + ...environmentPropTypes, }; const defaultProps = { @@ -148,9 +151,10 @@ class SidebarLinks extends React.Component { style={[styles.flexRow, styles.ph5, styles.pv3, styles.justifyContentBetween, styles.alignItemsCenter]} nativeID="drag-area" > - + @@ -286,6 +290,7 @@ export default compose( withLocalize, withCurrentUserPersonalDetails, withWindowDimensions, + withEnvironment, withOnyx({ // Note: It is very important that the keys subscribed to here are the same // keys that are subscribed to at the top of SidebarUtils.js. If there was a key missing from here and data was updated diff --git a/src/styles/StyleUtils.js b/src/styles/StyleUtils.js index 1b43a12b253c..b27913c56c7a 100644 --- a/src/styles/StyleUtils.js +++ b/src/styles/StyleUtils.js @@ -369,6 +369,13 @@ function getSignInWordmarkWidthStyle(environment, isSmallScreenWidth) { return isSmallScreenWidth ? {width: variables.signInLogoWidthPill} : {width: variables.signInLogoWidthLargeScreenPill}; } +function getLHNWordmarkWidthStyle(environment) { + if (environment === CONST.ENVIRONMENT.PRODUCTION) { + return {width: variables.lhnLogoWidth}; + } + return {width: variables.lhnLogoWidthPill}; +} + /** * Converts a color in hexadecimal notation into RGB notation. * @@ -1174,6 +1181,7 @@ export { getDirectionStyle, getFontSizeStyle, getSignInWordmarkWidthStyle, + getLHNWordmarkWidthStyle, getGoogleListViewStyle, getMentionStyle, getMentionTextColor, diff --git a/src/styles/variables.js b/src/styles/variables.js index f6bc4b2fe16c..683bcc2d02a9 100644 --- a/src/styles/variables.js +++ b/src/styles/variables.js @@ -123,9 +123,12 @@ export default { signInContentMinHeight: 800, signInLogoHeightSmallScreen: 28, signInLogoHeight: 34, + lhnLogoHeight: 30, signInLogoWidth: 120, signInLogoWidthLargeScreen: 144, signInLogoWidthPill: 132, + lhnLogoWidth: 126, + lhnLogoWidthPill: 140, signInLogoWidthLargeScreenPill: 162, modalContentMaxWidth: 360, listItemHeightNormal: 64, From f815004e63ca6cf1866b4d86add611b7af1baacf Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 11 May 2023 16:07:04 -0700 Subject: [PATCH 077/329] Hook up functionality to complete task --- src/components/ReportActionItem/TaskPreview.js | 3 +++ src/libs/ReportUtils.js | 2 ++ src/libs/actions/Task.js | 13 ++++++++++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js index 19c8349571f3..9cf4dca7c888 100644 --- a/src/components/ReportActionItem/TaskPreview.js +++ b/src/components/ReportActionItem/TaskPreview.js @@ -16,6 +16,7 @@ import getButtonState from '../../libs/getButtonState'; import Navigation from '../../libs/Navigation/Navigation'; import ROUTES from '../../ROUTES'; import reportActionPropTypes from '../../pages/home/report/reportActionPropTypes'; +import * as TaskUtils from '../../libs/actions/Task'; const propTypes = { /** The ID of the associated taskReport */ @@ -56,6 +57,7 @@ const TaskPreview = (props) => { (props.taskReport.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && props.taskReport.statusNum === CONST.REPORT.STATUS.APPROVED) || (props.action.childStateNum === CONST.REPORT.STATE_NUM.CLOSED && props.action.childStatusNum === CONST.REPORT.STATUS.APPROVED); const taskTitle = props.action.taskTitle || props.taskReport.reportName; + const parentReportID = props.taskReport.parentReportID; return ( { isChecked={isTaskCompleted} onPress={() => { // Being implemented in https://github.com/Expensify/App/issues/16858 + TaskUtils.completeTask(props.taskReportID, parentReportID); }} /> {taskTitle} diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index ef5d4293ab53..86e39c06a51c 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1074,6 +1074,8 @@ function buildOptimisticTaskReportAction(taskReportID, text, actionName) { html: reportAction.reportAction.message[0].html, taskReportID: reportAction.reportAction.message[0].taskReportID, }; + + return reportAction; } /** diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index d1d0af7215c0..b2aa95701356 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -188,8 +188,15 @@ function completeTask(taskReportID, parentReportID) { value: {[completedTaskReportAction.reportAction.reportActionID]: {pendingAction: null}}, }, ]; - - API.write('CompleteTask', {taskReportID}, {optimisticData, successData, failureData}); + console.log('completeTask', taskReportID, parentReportID, optimisticData, successData, failureData); + API.write( + 'CompleteTask', + { + taskReportID, + completedTaskReportActionID: completedTaskReportAction.reportAction.reportActionID, + }, + {optimisticData, successData, failureData}, + ); } /** @@ -274,4 +281,4 @@ function clearOutTaskInfoAndNavigate(reportID) { Navigation.navigate(ROUTES.NEW_TASK_DETAILS); } -export {createTaskAndNavigate, setTitleValue, setDescriptionValue, setDetailsValue, setAssigneeValue, setShareDestinationValue, clearOutTaskInfo, clearOutTaskInfoAndNavigate}; +export {createTaskAndNavigate, completeTask, setTitleValue, setDescriptionValue, setDetailsValue, setAssigneeValue, setShareDestinationValue, clearOutTaskInfo, clearOutTaskInfoAndNavigate}; From c75c4208fefbea2d8cdc23bbeeda9a8dc7969ed7 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 11 May 2023 16:07:29 -0700 Subject: [PATCH 078/329] Update TaskPreview.js --- src/components/ReportActionItem/TaskPreview.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js index 9cf4dca7c888..a4cc32aaf421 100644 --- a/src/components/ReportActionItem/TaskPreview.js +++ b/src/components/ReportActionItem/TaskPreview.js @@ -70,7 +70,6 @@ const TaskPreview = (props) => { containerStyle={[styles.taskCheckbox]} isChecked={isTaskCompleted} onPress={() => { - // Being implemented in https://github.com/Expensify/App/issues/16858 TaskUtils.completeTask(props.taskReportID, parentReportID); }} /> From d125fe44e9675c73f4f0acb1218b5db4dab59cbc Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 11 May 2023 16:36:47 -0700 Subject: [PATCH 079/329] add grabbing the parentReportID --- src/components/ReportActionItem/TaskPreview.js | 4 ++-- src/libs/ReportUtils.js | 3 ++- src/libs/actions/Task.js | 10 ++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js index a4cc32aaf421..cbfe17e231e1 100644 --- a/src/components/ReportActionItem/TaskPreview.js +++ b/src/components/ReportActionItem/TaskPreview.js @@ -57,7 +57,7 @@ const TaskPreview = (props) => { (props.taskReport.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && props.taskReport.statusNum === CONST.REPORT.STATUS.APPROVED) || (props.action.childStateNum === CONST.REPORT.STATE_NUM.CLOSED && props.action.childStatusNum === CONST.REPORT.STATUS.APPROVED); const taskTitle = props.action.taskTitle || props.taskReport.reportName; - const parentReportID = props.taskReport.parentReportID; + const parentReportID = props.action.parentReportID || props.taskReport.parentReportID; return ( { containerStyle={[styles.taskCheckbox]} isChecked={isTaskCompleted} onPress={() => { - TaskUtils.completeTask(props.taskReportID, parentReportID); + TaskUtils.completeTask(props.taskReportID, parentReportID, taskTitle); }} /> {taskTitle} diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 86e39c06a51c..114db6edb3a3 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1043,7 +1043,7 @@ function buildOptimisticAddCommentReportAction(text, file) { * @param {String} text - Text of the comment * @returns {Object} */ -function buildOptimisticTaskCommentReportAction(taskReportID, taskTitle, taskAssignee, text) { +function buildOptimisticTaskCommentReportAction(taskReportID, taskTitle, taskAssignee, text, parentReportID) { const reportAction = buildOptimisticAddCommentReportAction(text); reportAction.reportAction.message[0].taskReportID = taskReportID; @@ -1054,6 +1054,7 @@ function buildOptimisticTaskCommentReportAction(taskReportID, taskTitle, taskAss taskReportID: reportAction.reportAction.message[0].taskReportID, }; reportAction.reportAction.childReportID = taskReportID; + reportAction.reportAction.parentReportID = parentReportID; reportAction.reportAction.childType = CONST.REPORT.TYPE.TASK; reportAction.reportAction.taskTitle = taskTitle; reportAction.reportAction.taskAssignee = taskAssignee; diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index b2aa95701356..bca7590e88c1 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -39,12 +39,12 @@ function createTaskAndNavigate(currentUserEmail, parentReportID, title, descript const taskReportID = optimisticTaskReport.reportID; let optimisticAssigneeAddComment; if (assigneeChatReportID && assigneeChatReportID !== parentReportID) { - optimisticAssigneeAddComment = ReportUtils.buildOptimisticTaskCommentReportAction(taskReportID, title, assignee, `Assigned a task to you: ${title}`); + optimisticAssigneeAddComment = ReportUtils.buildOptimisticTaskCommentReportAction(taskReportID, title, assignee, `Assigned a task to you: ${title}`, parentReportID); } // Create the CreatedReportAction on the task const optimisticTaskCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(optimisticTaskReport.reportID); - const optimisticAddCommentReport = ReportUtils.buildOptimisticTaskCommentReportAction(taskReportID, title, assignee, `Created a task: ${title}`); + const optimisticAddCommentReport = ReportUtils.buildOptimisticTaskCommentReportAction(taskReportID, title, assignee, `Created a task: ${title}`, parentReportID); const currentTime = DateUtils.getDBTime(); @@ -152,10 +152,8 @@ function createTaskAndNavigate(currentUserEmail, parentReportID, title, descript Navigation.navigate(ROUTES.getReportRoute(optimisticTaskReport.reportID)); } -function completeTask(taskReportID, parentReportID) { - // TODO: Update the text to be the task title - const completedTaskReportAction = ReportUtils.buildOptimisticTaskReportAction(taskReportID, 'Completed task: ', CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED); - +function completeTask(taskReportID, parentReportID, taskTitle) { + const completedTaskReportAction = ReportUtils.buildOptimisticTaskReportAction(taskReportID, `Completed task: ${taskTitle}`, CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED); const optimisticData = [ { onyxMethod: Onyx.METHOD.MERGE, From 92ddb6f9ff48067468ab276951ae2a4a7cb43369 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 11 May 2023 16:46:33 -0700 Subject: [PATCH 080/329] add new taskAction component --- src/components/ReportActionItem/TaskAction.js | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/components/ReportActionItem/TaskAction.js diff --git a/src/components/ReportActionItem/TaskAction.js b/src/components/ReportActionItem/TaskAction.js new file mode 100644 index 000000000000..241f7f26b160 --- /dev/null +++ b/src/components/ReportActionItem/TaskAction.js @@ -0,0 +1,80 @@ +import React from 'react'; +import {View, Pressable} from 'react-native'; +import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; +import Navigation from '../../libs/Navigation/Navigation'; +import withLocalize, {withLocalizePropTypes} from '../withLocalize'; +import ROUTES from '../../ROUTES'; +import compose from '../../libs/compose'; +import ONYXKEYS from '../../ONYXKEYS'; +import Text from '../Text'; +import styles from '../../styles/styles'; +import Icon from '../Icon'; +import * as Expensicons from '../Icon/Expensicons'; +import reportActionPropTypes from '../../pages/home/report/reportActionPropTypes'; +import * as StyleUtils from '../../styles/StyleUtils'; +import getButtonState from '../../libs/getButtonState'; + +const propTypes = { + /** The ID of the associated taskReport */ + taskReportID: PropTypes.string.isRequired, + + /** Whether the task preview is hovered so we can modify its style */ + isHovered: PropTypes.bool, + + /** The linked reportAction */ + action: PropTypes.shape(reportActionPropTypes).isRequired, + + /* Onyx Props */ + + taskReport: PropTypes.shape({ + /** Title of the task */ + reportName: PropTypes.string, + + /** Email address of the manager in this iou report */ + managerEmail: PropTypes.string, + + /** Email address of the creator of this iou report */ + ownerEmail: PropTypes.string, + }), + + ...withLocalizePropTypes, +}; + +const defaultProps = { + taskReport: {}, + isHovered: false, +}; +const TaskAction = (props) => { + const taskReportID = props.taskReportID; + const taskReportName = props.taskReport.reportName || ''; + console.log(props.action); + return ( + Navigation.navigate(ROUTES.getReportRoute(taskReportID))} + style={[styles.flexRow, styles.justifyContentBetween]} + > + + {'testeastadwa'} + {` ${taskReportName}`} + + + + ); +}; + +TaskAction.propTypes = propTypes; +TaskAction.defaultProps = defaultProps; +TaskAction.displayName = 'TaskAction'; + +export default compose( + withLocalize, + withOnyx({ + taskReport: { + key: ({taskReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, + }, + }), +)(TaskAction); From 3ad9ffff37eb74b4338699cbabdf368f430d6859 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 11 May 2023 16:46:43 -0700 Subject: [PATCH 081/329] render on demand --- src/pages/home/report/ReportActionItem.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index b2c86d55204e..80e168525257 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -47,6 +47,7 @@ import DisplayNames from '../../../components/DisplayNames'; import personalDetailsPropType from '../../personalDetailsPropType'; import ReportActionItemDraft from './ReportActionItemDraft'; import TaskPreview from '../../../components/ReportActionItem/TaskPreview'; +import TaskAction from '../../../components/ReportActionItem/TaskAction'; import * as ReportActionUtils from '../../../libs/ReportActionsUtils'; const propTypes = { @@ -200,6 +201,14 @@ class ReportActionItem extends Component { isHovered={hovered} /> ); + } else if (this.props.action.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED) { + children = ( + + ); } else { const message = _.last(lodashGet(this.props.action, 'message', [{}])); const isAttachment = _.has(this.props.action, 'isAttachment') ? this.props.action.isAttachment : ReportUtils.isReportMessageAttachment(message); From 90a0df4deb56f5841a90ce017ea73ca641fe7b1b Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 11 May 2023 16:55:09 -0700 Subject: [PATCH 082/329] add copy for completed task --- src/languages/en.js | 5 +++++ src/languages/es.js | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/languages/en.js b/src/languages/en.js index b9f49302a43a..948a18ea3c26 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -1183,6 +1183,11 @@ export default { pleaseEnterTaskAssignee: 'Please select an assignee', pleaseEnterTaskDestination: 'Please select a share destination', }, + task: { + messages: { + completed: 'Completed task', + }, + }, statementPage: { generatingPDF: "We're generating your PDF right now. Please come back later!", }, diff --git a/src/languages/es.js b/src/languages/es.js index 01878fafe5e2..991afc8c959b 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -1188,6 +1188,11 @@ export default { pleaseEnterTaskAssignee: 'Por favor, asigna una persona a esta tarea', pleaseEnterTaskDestination: 'Por favor, selecciona un destino de tarea', }, + task: { + messages: { + completed: 'Completed task', + }, + }, statementPage: { generatingPDF: 'Estamos generando tu PDF ahora mismo. ¡Por favor, vuelve más tarde!', }, From 50a2b4d084925fc6bfcb7c55ca3f72252eff5c78 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 11 May 2023 16:55:23 -0700 Subject: [PATCH 083/329] update task action to pass in actionName --- src/components/ReportActionItem/TaskAction.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/TaskAction.js b/src/components/ReportActionItem/TaskAction.js index 241f7f26b160..74c0454d092d 100644 --- a/src/components/ReportActionItem/TaskAction.js +++ b/src/components/ReportActionItem/TaskAction.js @@ -14,6 +14,7 @@ import * as Expensicons from '../Icon/Expensicons'; import reportActionPropTypes from '../../pages/home/report/reportActionPropTypes'; import * as StyleUtils from '../../styles/StyleUtils'; import getButtonState from '../../libs/getButtonState'; +import CONST from '../../CONST'; const propTypes = { /** The ID of the associated taskReport */ @@ -22,8 +23,8 @@ const propTypes = { /** Whether the task preview is hovered so we can modify its style */ isHovered: PropTypes.bool, - /** The linked reportAction */ - action: PropTypes.shape(reportActionPropTypes).isRequired, + /** Name of the reportAction action */ + actionName: PropTypes.string.isRequired, /* Onyx Props */ @@ -48,14 +49,15 @@ const defaultProps = { const TaskAction = (props) => { const taskReportID = props.taskReportID; const taskReportName = props.taskReport.reportName || ''; - console.log(props.action); + + const messageLinkText = props.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED ? props.translate('task.messages.completed') : props.translate('newTaskPage.task'); return ( Navigation.navigate(ROUTES.getReportRoute(taskReportID))} style={[styles.flexRow, styles.justifyContentBetween]} > - {'testeastadwa'} + {messageLinkText} {` ${taskReportName}`} Date: Thu, 11 May 2023 16:55:26 -0700 Subject: [PATCH 084/329] Update ReportActionItem.js --- src/pages/home/report/ReportActionItem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 80e168525257..d3ae125c7859 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -205,7 +205,7 @@ class ReportActionItem extends Component { children = ( ); From a5e043eab511b6952366e19e6519f71b31d872f0 Mon Sep 17 00:00:00 2001 From: David Bondy Date: Thu, 11 May 2023 20:42:03 -0600 Subject: [PATCH 085/329] rename method and update to return the report object for a thread --- src/libs/ReportUtils.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index e1b482775c43..25762a1e041b 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1900,16 +1900,14 @@ function getWhisperDisplayNames(participants) { * @param {String} * @returns {Boolean} */ -function doesThreadExistForReportActionID(reportActionID) { - const thread = _.find(allReports, (report) => { +function getThreadForReportActionID(reportActionID) { + return _.find(allReports, (report) => { if (!report.parentReportActionID) { return false; } return report.parentReportActionID === reportActionID; }); - - return !_.isEmpty(thread); } /** @@ -2008,6 +2006,6 @@ export { canRequestMoney, getWhisperDisplayNames, getWorkspaceAvatar, - doesThreadExistForReportActionID, + getThreadForReportActionID, shouldReportShowSubscript, }; From e9620c1c64f727bb3736d5c3ee743020b478facf Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 12 May 2023 10:03:45 +0700 Subject: [PATCH 086/329] fix: update comment before set new state --- src/pages/home/report/ReportActionCompose.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index f985014c1dc3..3296d09f029b 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -640,13 +640,13 @@ class ReportActionCompose extends React.Component { * @param {String} emoji */ addEmojiToTextBox(emoji) { + this.updateComment(ComposerUtils.insertText(this.comment, this.state.selection, emoji)); this.setState((prevState) => ({ selection: { start: prevState.selection.start + emoji.length, end: prevState.selection.start + emoji.length, }, })); - this.updateComment(ComposerUtils.insertText(this.comment, this.state.selection, emoji)); } /** From 20cbe507bb06921e0e8c59333a032b4a6feaae21 Mon Sep 17 00:00:00 2001 From: honnamkuan Date: Fri, 12 May 2023 11:08:46 +0800 Subject: [PATCH 087/329] align lastMessageText formatting with backend by trimming leading and trailing spaces --- src/libs/ReportActionsUtils.js | 5 ++++- src/libs/ReportUtils.js | 7 +++++-- src/libs/actions/Report.js | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index de77e7f5243e..314d7a6740a1 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -158,7 +158,10 @@ function getLastVisibleMessageText(reportID, actionsToMerge = {}) { const htmlText = lodashGet(lastVisibleAction, 'message[0].html', ''); const parser = new ExpensiMark(); const messageText = parser.htmlToText(htmlText); - return String(messageText).replace(CONST.REGEX.AFTER_FIRST_LINE_BREAK, '').substring(0, CONST.REPORT.LAST_MESSAGE_TEXT_MAX_LENGTH); + return String(messageText) + .replace(CONST.REGEX.AFTER_FIRST_LINE_BREAK, '') + .substring(0, CONST.REPORT.LAST_MESSAGE_TEXT_MAX_LENGTH) + .trim(); } /** diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 18a479675110..649627f2b1bd 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -525,12 +525,15 @@ function canShowReportRecipientLocalTime(personalDetails, report) { } /** - * Trim the last message text to a fixed limit. + * Html decode, shorten last message text to fixed length and trim spaces. * @param {String} lastMessageText * @returns {String} */ function formatReportLastMessageText(lastMessageText) { - return String(lastMessageText).replace(CONST.REGEX.AFTER_FIRST_LINE_BREAK, '').substring(0, CONST.REPORT.LAST_MESSAGE_TEXT_MAX_LENGTH); + return Str.htmlDecode(String(lastMessageText)) + .replace(CONST.REGEX.AFTER_FIRST_LINE_BREAK, '') + .substring(0, CONST.REPORT.LAST_MESSAGE_TEXT_MAX_LENGTH) + .trim(); } /** diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 26d9b9da7357..d881994302ab 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -218,7 +218,7 @@ function addActions(reportID, text = '', file) { const optimisticReport = { lastVisibleActionCreated: currentTime, - lastMessageText: Str.htmlDecode(lastCommentText), + lastMessageText: lastCommentText, lastActorEmail: currentUserEmail, lastReadTime: currentTime, }; @@ -979,7 +979,7 @@ function editReportComment(reportID, originalReportAction, textForNewComment) { const reportComment = parser.htmlToText(htmlForNewComment); const lastMessageText = ReportUtils.formatReportLastMessageText(reportComment); const optimisticReport = { - lastMessageText: Str.htmlDecode(lastMessageText), + lastMessageText, }; optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, From 58cbcaff837a9582753180f6daf08fbdf4880b03 Mon Sep 17 00:00:00 2001 From: Sujit Kumar <60378235+therealsujitk@users.noreply.github.com> Date: Fri, 12 May 2023 10:08:21 +0530 Subject: [PATCH 088/329] Use round() instead of trunc() to fix precision issues --- src/libs/CurrencyUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/CurrencyUtils.js b/src/libs/CurrencyUtils.js index 2784b32edbdf..a30e1255dd3c 100644 --- a/src/libs/CurrencyUtils.js +++ b/src/libs/CurrencyUtils.js @@ -86,7 +86,7 @@ function isCurrencySymbolLTR(currencyCode) { */ function convertToSmallestUnit(currency, amountAsFloat) { const currencyUnit = getCurrencyUnit(currency); - return Math.trunc(amountAsFloat * currencyUnit); + return Math.round(amountAsFloat * currencyUnit); } /** From 94c9125e3a3e7986010b4ce17a0dd9a13e79b4f2 Mon Sep 17 00:00:00 2001 From: David Bondy Date: Thu, 11 May 2023 23:09:30 -0600 Subject: [PATCH 089/329] allow the passing of a parentReportActionID so we can create threads --- src/libs/ReportUtils.js | 2 ++ src/libs/actions/Report.js | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 25762a1e041b..c13b15f30887 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1276,6 +1276,7 @@ function buildOptimisticChatReport( oldPolicyName = '', visibility = undefined, notificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + parentReportActionID = '', ) { const currentTime = DateUtils.getDBTime(); return { @@ -1295,6 +1296,7 @@ function buildOptimisticChatReport( participants: participantList, policyID, reportID: generateReportID(), + parentReportActionID, reportName, stateNum: 0, statusNum: 0, diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 26d9b9da7357..a9e8b36b8e6c 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -399,6 +399,11 @@ function openReport(reportID, participantList = [], newReportObject = {}) { // Add the createdReportActionID parameter to the API call params.createdReportActionID = optimisticCreatedAction.reportActionID; + + // Only add the parentReportActionID if it's been added to the optimistic report + if (newReportObject.parentReportActionID) { + params.parentReportActionID = newReportObject.parentReportActionID; + } } API.write('OpenReport', params, onyxData); From 94c19d6adfe1c3a2547748ce16d9ea287a507f6e Mon Sep 17 00:00:00 2001 From: David Bondy Date: Thu, 11 May 2023 23:10:21 -0600 Subject: [PATCH 090/329] generate report optimistically and pass parentReportActionID to create a thread --- .../ReportActionItem/MoneyRequestAction.js | 48 +++++++++++++++++-- src/languages/en.js | 1 + 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestAction.js b/src/components/ReportActionItem/MoneyRequestAction.js index d07d76640745..46894288e2db 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.js +++ b/src/components/ReportActionItem/MoneyRequestAction.js @@ -15,7 +15,10 @@ import Navigation from '../../libs/Navigation/Navigation'; import ROUTES from '../../ROUTES'; import styles from '../../styles/styles'; import * as IOUUtils from '../../libs/IOUUtils'; -import {doesThreadExistForReportActionID} from '../../libs/ReportUtils'; +import {getThreadForReportActionID, buildOptimisticChatReport} from '../../libs/ReportUtils'; +import * as Report from '../../libs/actions/Report'; +import withLocalize, {withLocalizePropTypes} from '../withLocalize'; +import lodashGet from 'lodash/get'; const propTypes = { /** All the data of the action */ @@ -56,6 +59,14 @@ const propTypes = { isHovered: PropTypes.bool, network: networkPropTypes.isRequired, + + /** Session info for the currently logged in user. */ + session: PropTypes.shape({ + /** Currently logged in user email */ + email: PropTypes.string, + }), + + ...withLocalizePropTypes, }; const defaultProps = { @@ -67,14 +78,39 @@ const defaultProps = { iouReport: {}, reportActions: {}, isHovered: false, + session: { + email: null, + }, }; const MoneyRequestAction = (props) => { const hasMultipleParticipants = props.chatReport.participants.length > 1; const onIOUPreviewPressed = () => { - if (!doesThreadExistForReportActionID(props.action.reportActionID)) { - // optimistically create reportID - // pass it along to OpenReport since that is build to create a thread when needed, not sure what to do about the navigation stuff below... + // This would ideally be passed as a prop or hooked up via withOnyx so that we are not be triggering a potentially intensive + // search in an onPress handler, I think this could lead to performance issues but it probably ok for now. + const thread = getThreadForReportActionID(props.action.reportActionID); + console.log({thread}); + if (_.isEmpty(thread)) { + // Since a thread does not exist yet then we need to create it now. This is done by creating the report object + // and passing the parentReportActionID of the reportAction. OpenReport will then automatically create the thread for us. + const optimisticThreadReport = buildOptimisticChatReport( + props.chatReport.participants, + props.translate('iou.threadReportName', {payee: props.action.actorEmail, comment: props.action.originalMessage.comment}), + '', + props.chatReport.policyID, + props.chatReport.owner, + props.chatReport.type === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + props.chatReport.oldPolicyName, + undefined, + CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + props.action.reportActionID, + ); + console.log({optimisticThreadReport}); + Report.openReport( + optimisticThreadReport.reportID, + [_.reject(optimisticThreadReport.participants, (login) => login === lodashGet(props.session, 'email', ''))], + optimisticThreadReport, + ); } if (hasMultipleParticipants) { Navigation.navigate(ROUTES.getReportParticipantsRoute(props.chatReportID)); @@ -131,6 +167,7 @@ MoneyRequestAction.defaultProps = defaultProps; MoneyRequestAction.displayName = 'MoneyRequestAction'; export default compose( + withLocalize, withOnyx({ chatReport: { key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, @@ -142,6 +179,9 @@ export default compose( key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, canEvict: false, }, + session: { + key: ONYXKEYS.SESSION, + }, }), withNetwork(), )(MoneyRequestAction); diff --git a/src/languages/en.js b/src/languages/en.js index b9f49302a43a..7c08af03ce3f 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -328,6 +328,7 @@ export default { payerOwesAmount: ({payer, amount}) => `${payer} owes ${amount}`, noReimbursableExpenses: 'This report has an invalid amount', pendingConversionMessage: "Total will update when you're back online", + threadReportName: ({payee, comment}) => `${payee} request${comment ? ` for ${comment}` : ''}`, error: { invalidSplit: 'Split amounts do not equal total amount', other: 'Unexpected error, please try again later', From c77e3925449fc0a6f706ee5c166f591e545f0278 Mon Sep 17 00:00:00 2001 From: Kadie Alexander Date: Fri, 12 May 2023 17:30:41 +1200 Subject: [PATCH 091/329] colour changes for reaction bubble --- src/styles/themes/default.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/themes/default.js b/src/styles/themes/default.js index 0917d1d5b794..2ce80c94981b 100644 --- a/src/styles/themes/default.js +++ b/src/styles/themes/default.js @@ -15,7 +15,7 @@ const darkTheme = { iconReversed: colors.greenAppBackground, textSupporting: colors.greenSupportingText, text: colors.white, - link: colors.blueLink, + link: colors.green100, linkHover: colors.blueLinkHover, buttonDefaultBG: colors.greenDefaultButton, buttonDisabledBG: colors.greenDefaultButtonDisabled, @@ -66,7 +66,7 @@ const darkTheme = { pickerOptionsTextColor: colors.white, imageCropBackgroundColor: colors.greenIcons, fallbackIconColor: colors.green700, - reactionActive: '#003C73', + reactionActive: colors.green600, badgeAdHoc: colors.pink600, badgeAdHocHover: colors.pink700, mentionText: colors.blue100, From e641afd0eed624c52504768980b4bdbcfe183359 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 11 May 2023 22:34:01 -0700 Subject: [PATCH 092/329] Enforce there's no prettier diff in PR checks --- .github/workflows/lint.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a6562bbef58c..9ea309ce0afd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -22,3 +22,9 @@ jobs: - name: Lint shell scripts with ShellCheck run: npm run shellcheck + + - name: Verify there's no Prettier diff + run: | + source Expensify/App/scripts/shellUtils.sh + npm run prettier + git diff --name-only --exit-code || error 'Error: Prettier diff detected! Please run \`npm run prettier\` and commit the changes.' From be3aa732dc2b6961bcd0e7f9501777ccc71df93a Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 11 May 2023 22:45:41 -0700 Subject: [PATCH 093/329] Test ls --- .github/workflows/lint.yml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9ea309ce0afd..20b1bb27b27c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,18 +13,19 @@ jobs: steps: - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 - - uses: Expensify/App/.github/actions/composite/setupNode@main - - - name: Lint JavaScript with ESLint - run: npm run lint - env: - CI: true - - - name: Lint shell scripts with ShellCheck - run: npm run shellcheck +# - uses: Expensify/App/.github/actions/composite/setupNode@main +# +# - name: Lint JavaScript with ESLint +# run: npm run lint +# env: +# CI: true +# +# - name: Lint shell scripts with ShellCheck +# run: npm run shellcheck - name: Verify there's no Prettier diff run: | + ls source Expensify/App/scripts/shellUtils.sh npm run prettier git diff --name-only --exit-code || error 'Error: Prettier diff detected! Please run \`npm run prettier\` and commit the changes.' From b96aaa7b1884fc1014b2b5f0e73df3dfc1a7a4d9 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 11 May 2023 22:46:54 -0700 Subject: [PATCH 094/329] Fix source --- .github/workflows/lint.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 20b1bb27b27c..e8564c0a3056 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -25,7 +25,6 @@ jobs: - name: Verify there's no Prettier diff run: | - ls - source Expensify/App/scripts/shellUtils.sh + source ./scripts/shellUtils.sh npm run prettier git diff --name-only --exit-code || error 'Error: Prettier diff detected! Please run \`npm run prettier\` and commit the changes.' From e59beee4a9c1a905f2eb0518d0640bb73beb1f90 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 11 May 2023 22:48:04 -0700 Subject: [PATCH 095/329] Uncomment other code --- .github/workflows/lint.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e8564c0a3056..a5c46d3086c6 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,15 +13,15 @@ jobs: steps: - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 -# - uses: Expensify/App/.github/actions/composite/setupNode@main -# -# - name: Lint JavaScript with ESLint -# run: npm run lint -# env: -# CI: true -# -# - name: Lint shell scripts with ShellCheck -# run: npm run shellcheck + - uses: Expensify/App/.github/actions/composite/setupNode@main + + - name: Lint JavaScript with ESLint + run: npm run lint + env: + CI: true + + - name: Lint shell scripts with ShellCheck + run: npm run shellcheck - name: Verify there's no Prettier diff run: | From 1ec0e066fd92976540bcfb92e8fc4efd27348876 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 11 May 2023 22:59:33 -0700 Subject: [PATCH 096/329] fix exit code --- .github/workflows/lint.yml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a5c46d3086c6..3b6c5a64c428 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,16 +15,20 @@ jobs: - uses: Expensify/App/.github/actions/composite/setupNode@main - - name: Lint JavaScript with ESLint - run: npm run lint - env: - CI: true - - - name: Lint shell scripts with ShellCheck - run: npm run shellcheck +# - name: Lint JavaScript with ESLint +# run: npm run lint +# env: +# CI: true +# +# - name: Lint shell scripts with ShellCheck +# run: npm run shellcheck - name: Verify there's no Prettier diff run: | source ./scripts/shellUtils.sh npm run prettier - git diff --name-only --exit-code || error 'Error: Prettier diff detected! Please run \`npm run prettier\` and commit the changes.' + if ! git diff --name-only --exit-code; then + # shellcheck disable=SC2016 + echo 'Error: Prettier diff detected! Please run `npm run prettier` and commit the changes.' + exit 1 + fi From 468c0f87ddf975d082c989d907273ab5912f1492 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 11 May 2023 23:08:05 -0700 Subject: [PATCH 097/329] Finalize workflow --- .github/workflows/lint.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3b6c5a64c428..689c094c89d8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,18 +15,18 @@ jobs: - uses: Expensify/App/.github/actions/composite/setupNode@main -# - name: Lint JavaScript with ESLint -# run: npm run lint -# env: -# CI: true -# -# - name: Lint shell scripts with ShellCheck -# run: npm run shellcheck + - name: Lint JavaScript with ESLint + run: npm run lint + env: + CI: true + + - name: Lint shell scripts with ShellCheck + run: npm run shellcheck - name: Verify there's no Prettier diff run: | source ./scripts/shellUtils.sh - npm run prettier + npm run prettier -- --loglevel silent if ! git diff --name-only --exit-code; then # shellcheck disable=SC2016 echo 'Error: Prettier diff detected! Please run `npm run prettier` and commit the changes.' From aa8d4182f45a9fc5096372c6844a24de9b42e7d6 Mon Sep 17 00:00:00 2001 From: honnamkuan Date: Fri, 12 May 2023 15:17:21 +0800 Subject: [PATCH 098/329] apply prettier formatting --- src/libs/ReportActionsUtils.js | 5 +---- src/libs/ReportUtils.js | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 314d7a6740a1..4ab84faad6e4 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -158,10 +158,7 @@ function getLastVisibleMessageText(reportID, actionsToMerge = {}) { const htmlText = lodashGet(lastVisibleAction, 'message[0].html', ''); const parser = new ExpensiMark(); const messageText = parser.htmlToText(htmlText); - return String(messageText) - .replace(CONST.REGEX.AFTER_FIRST_LINE_BREAK, '') - .substring(0, CONST.REPORT.LAST_MESSAGE_TEXT_MAX_LENGTH) - .trim(); + return String(messageText).replace(CONST.REGEX.AFTER_FIRST_LINE_BREAK, '').substring(0, CONST.REPORT.LAST_MESSAGE_TEXT_MAX_LENGTH).trim(); } /** diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 649627f2b1bd..dc295460175b 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -530,10 +530,7 @@ function canShowReportRecipientLocalTime(personalDetails, report) { * @returns {String} */ function formatReportLastMessageText(lastMessageText) { - return Str.htmlDecode(String(lastMessageText)) - .replace(CONST.REGEX.AFTER_FIRST_LINE_BREAK, '') - .substring(0, CONST.REPORT.LAST_MESSAGE_TEXT_MAX_LENGTH) - .trim(); + return Str.htmlDecode(String(lastMessageText)).replace(CONST.REGEX.AFTER_FIRST_LINE_BREAK, '').substring(0, CONST.REPORT.LAST_MESSAGE_TEXT_MAX_LENGTH).trim(); } /** From 89f1105dc6b9eaf77c68b6c0cda103e10b1fa3b9 Mon Sep 17 00:00:00 2001 From: PrashantKumar Mangukiya Date: Fri, 12 May 2023 14:18:40 +0530 Subject: [PATCH 099/329] Add horizontal margin to attachment view --- src/components/AttachmentModal.js | 1 + src/components/AttachmentView.js | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 00f745da0769..692c9748db80 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -282,6 +282,7 @@ class AttachmentModal extends PureComponent { Boolean(this.state.source) && this.state.shouldLoadAttachment && ( {}, onToggleKeyboard: () => {}, + containerStyles: [], }; const AttachmentView = (props) => { @@ -124,7 +129,7 @@ const AttachmentView = (props) => { } return ( - + From 2a431b5d38f24bae378cf2089e3d82d9a0714087 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 12 May 2023 16:21:49 +0700 Subject: [PATCH 100/329] revert: prevent preferredSkinTone's legacy value where necessary --- src/components/EmojiPicker/EmojiPickerMenu/index.js | 1 - src/components/EmojiPicker/EmojiPickerMenu/index.native.js | 1 - src/components/Reactions/MiniQuickEmojiReactions.js | 1 - .../Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js | 1 - src/pages/home/report/ReportActionItem.js | 1 - 5 files changed, 5 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js index 5d134c469ebd..b617492a995b 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js @@ -539,7 +539,6 @@ export default compose( withOnyx({ preferredSkinTone: { key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, - selector: EmojiUtils.getPreferredSkinToneIndex, }, }), )( diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js index 9a52887b1f04..a396ac98e49f 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js @@ -176,7 +176,6 @@ export default compose( withOnyx({ preferredSkinTone: { key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, - selector: EmojiUtils.getPreferredSkinToneIndex, }, }), )( diff --git a/src/components/Reactions/MiniQuickEmojiReactions.js b/src/components/Reactions/MiniQuickEmojiReactions.js index 4519258930a4..980f4bbd1cd9 100644 --- a/src/components/Reactions/MiniQuickEmojiReactions.js +++ b/src/components/Reactions/MiniQuickEmojiReactions.js @@ -97,7 +97,6 @@ export default compose( withOnyx({ preferredSkinTone: { key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, - selector: EmojiUtils.getPreferredSkinToneIndex, }, }), )(MiniQuickEmojiReactions); diff --git a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js index 2b69566084db..008bd5a3d7c0 100644 --- a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js +++ b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js @@ -79,7 +79,6 @@ BaseQuickEmojiReactions.defaultProps = defaultProps; export default withOnyx({ preferredSkinTone: { key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, - selector: EmojiUtils.getPreferredSkinToneIndex, }, })(BaseQuickEmojiReactions); diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 08972134d5a1..3962648714a8 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -401,7 +401,6 @@ export default compose( withOnyx({ preferredSkinTone: { key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, - selector: EmojiUtils.getPreferredSkinToneIndex, }, }), )(ReportActionItem); From b4e137a1859044a2ccec2d37e28fc0c63105bb13 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 12 May 2023 16:22:52 +0700 Subject: [PATCH 101/329] remove redundant imports --- src/components/Reactions/MiniQuickEmojiReactions.js | 1 - .../Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js | 1 - src/pages/home/report/ReportActionItem.js | 1 - 3 files changed, 3 deletions(-) diff --git a/src/components/Reactions/MiniQuickEmojiReactions.js b/src/components/Reactions/MiniQuickEmojiReactions.js index 980f4bbd1cd9..e4bc4664a70c 100644 --- a/src/components/Reactions/MiniQuickEmojiReactions.js +++ b/src/components/Reactions/MiniQuickEmojiReactions.js @@ -17,7 +17,6 @@ import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; import getPreferredEmojiCode from './getPreferredEmojiCode'; -import * as EmojiUtils from '../../libs/EmojiUtils'; const propTypes = { ...baseQuickEmojiReactionsPropTypes, diff --git a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js index 008bd5a3d7c0..705897b4893e 100644 --- a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js +++ b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js @@ -10,7 +10,6 @@ import styles from '../../../styles/styles'; import ONYXKEYS from '../../../ONYXKEYS'; import getPreferredEmojiCode from '../getPreferredEmojiCode'; import Tooltip from '../../Tooltip'; -import * as EmojiUtils from '../../../libs/EmojiUtils'; const baseQuickEmojiReactionsPropTypes = { /** diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 3962648714a8..b2c86d55204e 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -48,7 +48,6 @@ import personalDetailsPropType from '../../personalDetailsPropType'; import ReportActionItemDraft from './ReportActionItemDraft'; import TaskPreview from '../../../components/ReportActionItem/TaskPreview'; import * as ReportActionUtils from '../../../libs/ReportActionsUtils'; -import * as EmojiUtils from '../../../libs/EmojiUtils'; const propTypes = { /** Report for this action */ From 564914fb83523c7b648a34ad9cb86c59a354a079 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 12 May 2023 16:27:57 +0700 Subject: [PATCH 102/329] fix eslint --- src/libs/EmojiUtils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/EmojiUtils.js b/src/libs/EmojiUtils.js index 283ea50cf9a9..241cb61c2ae2 100644 --- a/src/libs/EmojiUtils.js +++ b/src/libs/EmojiUtils.js @@ -171,6 +171,7 @@ function mergeEmojisWithFrequentlyUsedEmojis(emojis) { /** * Get the updated frequently used emojis list by usage * @param {Object|Object[]} newEmoji + * @return {Object[]} */ function addToFrequentlyUsedEmojis(newEmoji) { let frequentEmojiList = [...frequentlyUsedEmojis]; From e815dd54ab5e587ec02c14465bbf87c4081c87ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Fri, 12 May 2023 13:40:06 +0200 Subject: [PATCH 103/329] fix e2e pipeline --- .github/workflows/e2ePerformanceTests.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml index cec1d11c7456..9c608357ab82 100644 --- a/.github/workflows/e2ePerformanceTests.yml +++ b/.github/workflows/e2ePerformanceTests.yml @@ -126,16 +126,24 @@ jobs: - name: Download baseline APK uses: actions/download-artifact@e9ef242655d12993efdcda9058dee2db83a2cb9b + id: downloadBaselineAPK with: name: baseline-apk-${{ needs.buildBaseline.outputs.VERSION }} path: zip + # The downloaded artifact will be a file named "app-e2eRelease.apk" so we have to rename it + - name: Rename baseline APK + run: mv "${{steps.downloadBaselineAPK.outputs.download-path}}/app-e2eRelease.apk" "${{steps.downloadBaselineAPK.outputs.download-path}}/app-e2eRelease-baseline.apk" + - name: Download delta APK uses: actions/download-artifact@e9ef242655d12993efdcda9058dee2db83a2cb9b with: name: delta-apk-${{ needs.buildDelta.outputs.DELTA_REF }} path: zip + - name: Rename delta APK + run: mv "${{steps.downloadBaselineAPK.outputs.download-path}}/app-e2eRelease.apk" "${{steps.downloadBaselineAPK.outputs.download-path}}/app-e2eRelease-compare.apk" + - name: Copy e2e code into zip folder run: cp -r tests/e2e zip @@ -163,19 +171,19 @@ jobs: test_spec_file: tests/e2e/TestSpec.yml test_spec_type: APPIUM_NODE_TEST_SPEC remote_src: false - file_artifacts: CustomerArtifacts.zip + file_artifacts: Customer Artifacts.zip cleanup: true - name: Unzip AWS Device Farm results if: ${{ always() }} - run: unzip CustomerArtifacts.zip + run: unzip "Customer Artifacts.zip" - name: Print AWS Device Farm run results if: ${{ always() }} run: cat "./Host_Machine_Files/\$WORKING_DIRECTORY/output.md" - name: Print AWS Device Farm verbose run results - if: ${{ always() && fromJSON(runner.debug) }} + if: ${{ always() && contains(runner, 'debug') && fromJSON(runner.debug) }} run: cat "./Host_Machine_Files/\$WORKING_DIRECTORY/debug.log" - name: Check if test failed, if so post the results and add the DeployBlocker label From 5498816b25ae661eafedc7ce5b7077997f0800f4 Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Fri, 12 May 2023 19:19:47 +0530 Subject: [PATCH 104/329] Remove additional letters --- .../com/expensify/chat/MainApplication.java | 1 + .../chat/RNTextInputResetModule.java | 46 +++++++++++++++++++ .../chat/RNTextInputResetPackage.java | 28 +++++++++++ src/pages/home/report/ReportActionCompose.js | 6 ++- 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 android/app/src/main/java/com/expensify/chat/RNTextInputResetModule.java create mode 100644 android/app/src/main/java/com/expensify/chat/RNTextInputResetPackage.java diff --git a/android/app/src/main/java/com/expensify/chat/MainApplication.java b/android/app/src/main/java/com/expensify/chat/MainApplication.java index b24e8eb49eb1..a4f2bc97416d 100644 --- a/android/app/src/main/java/com/expensify/chat/MainApplication.java +++ b/android/app/src/main/java/com/expensify/chat/MainApplication.java @@ -37,6 +37,7 @@ protected List getPackages() { // packages.add(new MyReactNativePackage()); packages.add(new BootSplashPackage()); packages.add(new ExpensifyAppPackage()); + packages.add(new RNTextInputResetPackage()); return packages; } diff --git a/android/app/src/main/java/com/expensify/chat/RNTextInputResetModule.java b/android/app/src/main/java/com/expensify/chat/RNTextInputResetModule.java new file mode 100644 index 000000000000..57c67f1bc5f0 --- /dev/null +++ b/android/app/src/main/java/com/expensify/chat/RNTextInputResetModule.java @@ -0,0 +1,46 @@ +package com.expensify.chat; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.Callback; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.UIBlock; +import com.facebook.react.uimanager.NativeViewHierarchyManager; +import android.content.Context; +import android.view.View; +import android.widget.TextView; +import android.view.inputmethod.InputMethodManager; +import android.util.Log; + +public class RNTextInputResetModule extends ReactContextBaseJavaModule { + + private final ReactApplicationContext reactContext; + + public RNTextInputResetModule(ReactApplicationContext reactContext) { + super(reactContext); + this.reactContext = reactContext; + } + + @Override + public String getName() { + return "RNTextInputReset"; + } + + // Props to https://github.com/MattFoley for this temporary hack + // https://github.com/facebook/react-native/pull/12462#issuecomment-298812731 + @ReactMethod + public void resetKeyboardInput(final int reactTagToReset) { + UIManagerModule uiManager = getReactApplicationContext().getNativeModule(UIManagerModule.class); + uiManager.addUIBlock(new UIBlock() { + @Override + public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) { + InputMethodManager imm = (InputMethodManager) getReactApplicationContext().getBaseContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + View viewToReset = nativeViewHierarchyManager.resolveView(reactTagToReset); + imm.restartInput(viewToReset); + } + } + }); + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/expensify/chat/RNTextInputResetPackage.java b/android/app/src/main/java/com/expensify/chat/RNTextInputResetPackage.java new file mode 100644 index 000000000000..8e5d9994fd4b --- /dev/null +++ b/android/app/src/main/java/com/expensify/chat/RNTextInputResetPackage.java @@ -0,0 +1,28 @@ +package com.expensify.chat; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; +import com.facebook.react.bridge.JavaScriptModule; + +public class RNTextInputResetPackage implements ReactPackage { + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + return Arrays.asList(new RNTextInputResetModule(reactContext)); + } + + // Deprecated from RN 0.47 + public List> createJSModules() { + return Collections.emptyList(); + } + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } +} diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index 1309d518b539..1820b7be4820 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {View, TouchableOpacity, InteractionManager, LayoutAnimation} from 'react-native'; +import {View, TouchableOpacity, InteractionManager, LayoutAnimation, NativeModules, findNodeHandle} from 'react-native'; import _ from 'underscore'; import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; @@ -139,6 +139,7 @@ const defaultProps = { ...withCurrentUserPersonalDetailsDefaultProps, }; +const { RNTextInputReset } = NativeModules; /** * Return the max available index for arrow manager. * @param {Number} numRows @@ -591,6 +592,9 @@ class ReportActionCompose extends React.Component { const commentAfterColonWithEmojiNameRemoved = this.state.value.slice(this.state.selection.end).replace(CONST.REGEX.EMOJI_REPLACER, CONST.SPACE); this.updateComment(`${commentBeforeColon}${emojiCode} ${commentAfterColonWithEmojiNameRemoved}`, true); + if (RNTextInputReset) { + RNTextInputReset.resetKeyboardInput(findNodeHandle(this.textInput)); + } this.setState((prevState) => ({ selection: { start: prevState.colonIndex + emojiCode.length + CONST.SPACE_LENGTH, From bc231ddf135495df7299f0a64c18017b3f5fac18 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Fri, 12 May 2023 16:52:14 +0300 Subject: [PATCH 105/329] refactor requestMoney --- src/libs/actions/IOU.js | 295 +++++++++++++++++++++++----------------- 1 file changed, 169 insertions(+), 126 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 4fe031727807..dcc53341f36a 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -55,9 +55,11 @@ Onyx.connect({ */ function requestMoney(report, amount, currency, payeeEmail, participant, comment) { const payerEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login); - let chatReport = lodashGet(report, 'reportID', null) ? report : null; const isPolicyExpenseChat = participant.isPolicyExpenseChat || participant.isOwnPolicyExpenseChat; - let isNewChat = false; + + // STEP 1: Get existing chat report OR build a new optimistic one + let isNewChatReport = false; + let chatReport = lodashGet(report, 'reportID', null) ? report : null; // If this is a policyExpenseChat, the chatReport must exist and we can get it from Onyx. // report is null if the flow is initiated from the global create menu. However, participant always stores the reportID if it exists, which is the case for policyExpenseChats @@ -68,52 +70,40 @@ function requestMoney(report, amount, currency, payeeEmail, participant, comment if (!chatReport) { chatReport = ReportUtils.getChatByParticipants([payerEmail]); } + + // If we still don't have a report, it likely doens't exist and we need to build an optimistic one if (!chatReport) { + isNewChatReport = true; chatReport = ReportUtils.buildOptimisticChatReport([payerEmail]); - isNewChat = true; } - let moneyRequestReport; - if (chatReport.iouReportID) { + + // STEP 2: Get existing IOU report and update its total OR build a new optimistic one + const isNewIOUReport = !chatReport.iouReportID; + let iouReport; + + if (!isNewIOUReport) { if (isPolicyExpenseChat) { - moneyRequestReport = {...iouReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`]}; + iouReport = {...iouReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`]}; // Because of the Expense reports are stored as negative values, we substract the total from the amount - moneyRequestReport.total = ReportUtils.isExpenseReport(moneyRequestReport) ? moneyRequestReport.total - amount : moneyRequestReport.total + amount; + iouReport.total = ReportUtils.isExpenseReport(iouReport) ? iouReport.total - amount : iouReport.total + amount; } else { - moneyRequestReport = IOUUtils.updateIOUOwnerAndTotal(iouReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`], payeeEmail, amount, currency); + iouReport = IOUUtils.updateIOUOwnerAndTotal(iouReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`], payeeEmail, amount, currency); } } else { - moneyRequestReport = isPolicyExpenseChat + iouReport = isPolicyExpenseChat ? ReportUtils.buildOptimisticExpenseReport(chatReport.reportID, chatReport.policyID, payeeEmail, amount, currency) : ReportUtils.buildOptimisticIOUReport(payeeEmail, payerEmail, amount, chatReport.reportID, currency); } - const optimisticTransaction = TransactionUtils.buildOptimisticTransaction(amount, currency, moneyRequestReport.reportID, comment); - const optimisticTransactionData = { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`, - value: optimisticTransaction, - }; - const transactionSuccessData = { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`, - value: { - pendingAction: null, - }, - }; - const transactionFailureData = { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`, - value: { - errors: { - [DateUtils.getMicroseconds()]: Localize.translateLocal('iou.error.genericCreateFailureMessage'), - }, - }, - }; + // STEP 3: Build optimistic transaction + const optimisticTransaction = TransactionUtils.buildOptimisticTransaction(amount, currency, iouReport.reportID, comment); - // Note: The created action must be optimistically generated before the IOU action so there's no chance that the created action appears after the IOU action in the chat - const optimisticCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail); - const optimisticReportAction = ReportUtils.buildOptimisticIOUReportAction( + // STEP 4: Build optimistic reportActions. We need a CREATED action for each report and an IOU action for the IOU report + // Note: The CREATED action for the IOU report must be optimistically generated before the IOU action so there's no chance that it appears after the IOU action in the chat + const optimisticCreatedActionForChat = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail); + const optimisticCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail); + const optimisticIOUAction = ReportUtils.buildOptimisticIOUReportAction( CONST.IOU.REPORT_ACTION_TYPE.CREATE, amount, currency, @@ -121,114 +111,167 @@ function requestMoney(report, amount, currency, payeeEmail, participant, comment [participant], optimisticTransaction.transactionID, '', - moneyRequestReport.reportID, + iouReport.reportID, ); - // First, add data that will be used in all cases - const optimisticChatReportData = { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, - value: { - ...chatReport, - lastReadTime: DateUtils.getDBTime(), - hasOutstandingIOU: moneyRequestReport.total !== 0, - iouReportID: moneyRequestReport.reportID, + // STEP 5: Build Onyx Data + const optimisticData = [ + { + // Use SET for new reports because it doesn't exist yet, is faster and we need the data to be available when we navigate to the chat page + onyxMethod: isNewChatReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: { + ...chatReport, + lastReadTime: DateUtils.getDBTime(), + hasOutstandingIOU: iouReport.total !== 0, + iouReportID: iouReport.reportID, + ...(isNewChatReport ? {pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}} : {}), + }, }, - }; - - const optimisticIOUReportData = { - onyxMethod: chatReport.hasOutstandingIOU ? Onyx.METHOD.MERGE : Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT}${moneyRequestReport.reportID}`, - value: { - ...moneyRequestReport, - lastMessageText: optimisticReportAction.message[0].text, - lastMessageHtml: optimisticReportAction.message[0].html, + { + onyxMethod: isNewIOUReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, + value: { + ...iouReport, + lastMessageText: optimisticIOUAction.message[0].text, + lastMessageHtml: optimisticIOUAction.message[0].html, + ...(isNewIOUReport ? {pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}} : {}), + }, }, - }; - - const optimisticReportActionsData = { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport.reportID}`, - value: { - [optimisticReportAction.reportActionID]: optimisticReportAction, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`, + value: optimisticTransaction, }, - }; - - let chatReportSuccessData = {}; - const reportActionsSuccessData = { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport.reportID}`, - value: { - [optimisticReportAction.reportActionID]: { - pendingAction: null, + ...(isNewChatReport + ? [ + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, + value: { + [optimisticCreatedActionForChat.reportActionID]: optimisticCreatedActionForChat, + }, + }, + ] + : []), + { + onyxMethod: isNewIOUReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, + value: { + ...(isNewIOUReport ? {[optimisticCreatedActionForIOU.reportActionID]: optimisticCreatedActionForIOU} : {}), + [optimisticIOUAction.reportActionID]: optimisticIOUAction, }, }, - }; + ]; - const chatReportFailureData = { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, - value: { - hasOutstandingIOU: chatReport.hasOutstandingIOU, + const successData = [ + ...(isNewChatReport + ? [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: { + pendingFields: null, + errorFields: null, + }, + }, + ] + : []), + ...(isNewIOUReport + ? [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, + value: { + pendingFields: null, + errorFields: null, + }, + }, + ] + : []), + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`, + value: {pendingAction: null}, }, - }; + ...(isNewChatReport + ? [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, + value: {[optimisticCreatedActionForChat.reportActionID]: {pendingAction: null}}, + }, + ] + : []), + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, + value: { + ...(isNewIOUReport ? {[optimisticCreatedActionForIOU.reportActionID]: {pendingAction: null}} : {}), + [optimisticIOUAction.reportActionID]: {pendingAction: null}, + }, + }, + ]; - const reportActionsFailureData = { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport.reportID}`, - value: { - [optimisticReportAction.reportActionID]: { + const failureData = [ + ...(isNewChatReport + ? [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: { + hasOutstandingIOU: false, + pendingFields: {createChat: null}, + createChat: { + [DateUtils.getMicroseconds()]: Localize.translateLocal('report.genericCreateReportFailureMessage'), + }, + }, + }, + ] + : []), + ...(isNewIOUReport + ? [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, + value: { + pendingFields: {createChat: null}, + createChat: { + [DateUtils.getMicroseconds()]: Localize.translateLocal('report.genericCreateReportFailureMessage'), + }, + }, + }, + ] + : []), + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`, + value: { errors: { [DateUtils.getMicroseconds()]: Localize.translateLocal('iou.error.genericCreateFailureMessage'), }, }, }, - }; - - // Now, let's add the data we need just when we are creating a new chat report - if (isNewChat) { - // Change the method to set for new reports because it doesn't exist yet, is faster, - // and we need the data to be available when we navigate to the chat page - optimisticChatReportData.onyxMethod = Onyx.METHOD.SET; - optimisticIOUReportData.onyxMethod = Onyx.METHOD.SET; - optimisticReportActionsData.onyxMethod = Onyx.METHOD.SET; - - // Then add and clear pending fields from the chat report - optimisticChatReportData.value.pendingFields = {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}; - chatReportSuccessData = { + ...(isNewChatReport + ? [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, + value: {[optimisticCreatedActionForChat.reportActionID]: {pendingAction: null}}, + }, + ] + : []), + { onyxMethod: Onyx.METHOD.MERGE, - key: optimisticChatReportData.key, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, value: { - pendingFields: null, - errorFields: null, - }, - }; - chatReportFailureData.value.pendingFields = {createChat: null}; - delete chatReportFailureData.value.hasOutstandingIOU; - chatReportFailureData.value.errorFields = { - createChat: { - [DateUtils.getMicroseconds()]: Localize.translateLocal('report.genericCreateReportFailureMessage'), + ...(isNewIOUReport ? {[optimisticCreatedActionForIOU.reportActionID]: {pendingAction: null}} : {}), + [optimisticIOUAction.reportActionID]: {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}, }, - }; - - // Then add an optimistic created action - optimisticReportActionsData.value[optimisticCreatedAction.reportActionID] = optimisticCreatedAction; - reportActionsSuccessData.value[optimisticCreatedAction.reportActionID] = {pendingAction: null}; - - // Failure data should feature red brick road - reportActionsFailureData.value[optimisticCreatedAction.reportActionID] = {pendingAction: null}; - reportActionsFailureData.value[optimisticReportAction.reportActionID] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}; - } - - const optimisticData = [optimisticChatReportData, optimisticIOUReportData, optimisticReportActionsData, optimisticTransactionData]; - - const successData = [reportActionsSuccessData, transactionSuccessData]; - if (!_.isEmpty(chatReportSuccessData)) { - successData.push(chatReportSuccessData); - } - - const failureData = [chatReportFailureData, reportActionsFailureData, transactionFailureData]; + }, + ]; + // STEP 6: Make the request const parsedComment = ReportUtils.getParsedComment(comment); API.write( 'RequestMoney', @@ -237,11 +280,11 @@ function requestMoney(report, amount, currency, payeeEmail, participant, comment amount, currency, comment: parsedComment, - iouReportID: moneyRequestReport.reportID, + iouReportID: iouReport.reportID, chatReportID: chatReport.reportID, transactionID: optimisticTransaction.transactionID, - reportActionID: optimisticReportAction.reportActionID, - createdReportActionID: isNewChat ? optimisticCreatedAction.reportActionID : 0, + reportActionID: optimisticIOUAction.reportActionID, + createdReportActionID: isNewChatReport ? optimisticCreatedActionForChat.reportActionID : 0, }, {optimisticData, successData, failureData}, ); From 7046f6bc0115647502e8ce02d99f01b9e501bad2 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Fri, 12 May 2023 16:59:15 +0300 Subject: [PATCH 106/329] update total --- src/libs/actions/IOU.js | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index dcc53341f36a..07bfaf40c466 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -86,7 +86,7 @@ function requestMoney(report, amount, currency, payeeEmail, participant, comment iouReport = {...iouReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`]}; // Because of the Expense reports are stored as negative values, we substract the total from the amount - iouReport.total = ReportUtils.isExpenseReport(iouReport) ? iouReport.total - amount : iouReport.total + amount; + iouReport.total -= amount; } else { iouReport = IOUUtils.updateIOUOwnerAndTotal(iouReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`], payeeEmail, amount, currency); } @@ -694,12 +694,12 @@ function splitBillAndOpenReport(participants, currentUserLogin, amount, comment, * @param {Boolean} shouldCloseOnDelete */ function deleteMoneyRequest(chatReportID, iouReportID, moneyRequestAction, shouldCloseOnDelete) { - const iouReport = iouReports[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`]; + const iouReport = {...iouReports[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`]}; const transactionID = moneyRequestAction.originalMessage.IOUTransactionID; // Get the amount we are deleting const amount = moneyRequestAction.originalMessage.amount; - const optimisticReportAction = ReportUtils.buildOptimisticIOUReportAction( + const optimisticIOUAction = ReportUtils.buildOptimisticIOUReportAction( CONST.IOU.REPORT_ACTION_TYPE.DELETE, amount, moneyRequestAction.originalMessage.currency, @@ -710,10 +710,16 @@ function deleteMoneyRequest(chatReportID, iouReportID, moneyRequestAction, shoul iouReportID, ); - const currentUserEmail = optimisticReportAction.actorEmail; + const currentUserEmail = optimisticIOUAction.actorEmail; + if (isPolicyExpenseChat) { + // Because of the Expense reports are stored as negative values, we add the total from the amount + iouReport.total = ReportUtils.isExpenseReport(iouReport) ? iouReport.total - amount : iouReport.total + amount; + } else { + iouReport = IOUUtils.updateIOUOwnerAndTotal(iouReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`], payeeEmail, amount, currency); + } const updatedIOUReport = IOUUtils.updateIOUOwnerAndTotal(iouReport, currentUserEmail, amount, moneyRequestAction.originalMessage.currency, CONST.IOU.REPORT_ACTION_TYPE.DELETE); - updatedIOUReport.lastMessageText = optimisticReportAction.message[0].text; - updatedIOUReport.lastMessageHtml = optimisticReportAction.message[0].html; + updatedIOUReport.lastMessageText = optimisticIOUAction.message[0].text; + updatedIOUReport.lastMessageHtml = optimisticIOUAction.message[0].html; updatedIOUReport.hasOutstandingIOU = updatedIOUReport.total !== 0; const optimisticData = [ @@ -721,8 +727,8 @@ function deleteMoneyRequest(chatReportID, iouReportID, moneyRequestAction, shoul onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`, value: { - [optimisticReportAction.reportActionID]: { - ...optimisticReportAction, + [optimisticIOUAction.reportActionID]: { + ...optimisticIOUAction, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, }, }, @@ -743,7 +749,7 @@ function deleteMoneyRequest(chatReportID, iouReportID, moneyRequestAction, shoul onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`, value: { - [optimisticReportAction.reportActionID]: { + [optimisticIOUAction.reportActionID]: { pendingAction: null, }, }, @@ -754,7 +760,7 @@ function deleteMoneyRequest(chatReportID, iouReportID, moneyRequestAction, shoul onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`, value: { - [optimisticReportAction.reportActionID]: { + [optimisticIOUAction.reportActionID]: { errors: { [DateUtils.getMicroseconds()]: Localize.translateLocal('iou.error.genericDeleteFailureMessage'), }, @@ -778,7 +784,7 @@ function deleteMoneyRequest(chatReportID, iouReportID, moneyRequestAction, shoul { transactionID, chatReportID, - reportActionID: optimisticReportAction.reportActionID, + reportActionID: optimisticIOUAction.reportActionID, iouReportID: updatedIOUReport.reportID, }, {optimisticData, successData, failureData}, From 3a59bcde9bbf8d3be7b697058bc66b0f4bb69cdd Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Fri, 12 May 2023 17:09:22 +0300 Subject: [PATCH 107/329] refactor deleteMoneyRequest --- src/libs/actions/IOU.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 07bfaf40c466..b647a64d728a 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -694,7 +694,7 @@ function splitBillAndOpenReport(participants, currentUserLogin, amount, comment, * @param {Boolean} shouldCloseOnDelete */ function deleteMoneyRequest(chatReportID, iouReportID, moneyRequestAction, shouldCloseOnDelete) { - const iouReport = {...iouReports[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`]}; + const iouReport = iouReports[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`]; const transactionID = moneyRequestAction.originalMessage.IOUTransactionID; // Get the amount we are deleting @@ -711,13 +711,21 @@ function deleteMoneyRequest(chatReportID, iouReportID, moneyRequestAction, shoul ); const currentUserEmail = optimisticIOUAction.actorEmail; - if (isPolicyExpenseChat) { + let updatedIOUReport = {}; + if (ReportUtils.isExpenseReport(iouReportID)) { + updatedIOUReport = {...iouReport}; + // Because of the Expense reports are stored as negative values, we add the total from the amount - iouReport.total = ReportUtils.isExpenseReport(iouReport) ? iouReport.total - amount : iouReport.total + amount; + updatedIOUReport.total += amount; } else { - iouReport = IOUUtils.updateIOUOwnerAndTotal(iouReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`], payeeEmail, amount, currency); + updatedIOUReport = IOUUtils.updateIOUOwnerAndTotal( + iouReports[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`], + currentUserEmail, + amount, + moneyRequestAction.originalMessage.currency, + CONST.IOU.REPORT_ACTION_TYPE.DELETE, + ); } - const updatedIOUReport = IOUUtils.updateIOUOwnerAndTotal(iouReport, currentUserEmail, amount, moneyRequestAction.originalMessage.currency, CONST.IOU.REPORT_ACTION_TYPE.DELETE); updatedIOUReport.lastMessageText = optimisticIOUAction.message[0].text; updatedIOUReport.lastMessageHtml = optimisticIOUAction.message[0].html; updatedIOUReport.hasOutstandingIOU = updatedIOUReport.total !== 0; @@ -761,6 +769,7 @@ function deleteMoneyRequest(chatReportID, iouReportID, moneyRequestAction, shoul key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`, value: { [optimisticIOUAction.reportActionID]: { + pendingAction: null, errors: { [DateUtils.getMicroseconds()]: Localize.translateLocal('iou.error.genericDeleteFailureMessage'), }, From 7400977313110e68109b51f5a5a50b2e66a98735 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Fri, 12 May 2023 17:37:51 +0300 Subject: [PATCH 108/329] create buildOnyxDataForMoneyRequest --- src/libs/actions/IOU.js | 278 ++++++++++++++++++++++++---------------- 1 file changed, 165 insertions(+), 113 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index b647a64d728a..23cc03e9fbb0 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -43,78 +43,7 @@ Onyx.connect({ }, }); -/** - * Request money from another user - * - * @param {Object} report - * @param {Number} amount - always in the smallest unit of the currency - * @param {String} currency - * @param {String} payeeEmail - * @param {Object} participant - * @param {String} comment - */ -function requestMoney(report, amount, currency, payeeEmail, participant, comment) { - const payerEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login); - const isPolicyExpenseChat = participant.isPolicyExpenseChat || participant.isOwnPolicyExpenseChat; - - // STEP 1: Get existing chat report OR build a new optimistic one - let isNewChatReport = false; - let chatReport = lodashGet(report, 'reportID', null) ? report : null; - - // If this is a policyExpenseChat, the chatReport must exist and we can get it from Onyx. - // report is null if the flow is initiated from the global create menu. However, participant always stores the reportID if it exists, which is the case for policyExpenseChats - if (!chatReport && isPolicyExpenseChat) { - chatReport = chatReports[`${ONYXKEYS.COLLECTION.REPORT}${participant.reportID}`]; - } - - if (!chatReport) { - chatReport = ReportUtils.getChatByParticipants([payerEmail]); - } - - // If we still don't have a report, it likely doens't exist and we need to build an optimistic one - if (!chatReport) { - isNewChatReport = true; - chatReport = ReportUtils.buildOptimisticChatReport([payerEmail]); - } - - // STEP 2: Get existing IOU report and update its total OR build a new optimistic one - const isNewIOUReport = !chatReport.iouReportID; - let iouReport; - - if (!isNewIOUReport) { - if (isPolicyExpenseChat) { - iouReport = {...iouReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`]}; - - // Because of the Expense reports are stored as negative values, we substract the total from the amount - iouReport.total -= amount; - } else { - iouReport = IOUUtils.updateIOUOwnerAndTotal(iouReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`], payeeEmail, amount, currency); - } - } else { - iouReport = isPolicyExpenseChat - ? ReportUtils.buildOptimisticExpenseReport(chatReport.reportID, chatReport.policyID, payeeEmail, amount, currency) - : ReportUtils.buildOptimisticIOUReport(payeeEmail, payerEmail, amount, chatReport.reportID, currency); - } - - // STEP 3: Build optimistic transaction - const optimisticTransaction = TransactionUtils.buildOptimisticTransaction(amount, currency, iouReport.reportID, comment); - - // STEP 4: Build optimistic reportActions. We need a CREATED action for each report and an IOU action for the IOU report - // Note: The CREATED action for the IOU report must be optimistically generated before the IOU action so there's no chance that it appears after the IOU action in the chat - const optimisticCreatedActionForChat = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail); - const optimisticCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail); - const optimisticIOUAction = ReportUtils.buildOptimisticIOUReportAction( - CONST.IOU.REPORT_ACTION_TYPE.CREATE, - amount, - currency, - comment, - [participant], - optimisticTransaction.transactionID, - '', - iouReport.reportID, - ); - - // STEP 5: Build Onyx Data +function buildOnyxDataForMoneyRequest(chatReport, iouReport, transaction, chatCreatedAction, iouCreatedAction, iouAction, isNewChatReport, isNewIOUReport) { const optimisticData = [ { // Use SET for new reports because it doesn't exist yet, is faster and we need the data to be available when we navigate to the chat page @@ -133,15 +62,15 @@ function requestMoney(report, amount, currency, payeeEmail, participant, comment key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, value: { ...iouReport, - lastMessageText: optimisticIOUAction.message[0].text, - lastMessageHtml: optimisticIOUAction.message[0].html, + lastMessageText: iouAction.message[0].text, + lastMessageHtml: iouAction.message[0].html, ...(isNewIOUReport ? {pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}} : {}), }, }, { onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`, - value: optimisticTransaction, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, + value: transaction, }, ...(isNewChatReport ? [ @@ -149,7 +78,7 @@ function requestMoney(report, amount, currency, payeeEmail, participant, comment onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, value: { - [optimisticCreatedActionForChat.reportActionID]: optimisticCreatedActionForChat, + [chatCreatedAction.reportActionID]: chatCreatedAction, }, }, ] @@ -158,8 +87,8 @@ function requestMoney(report, amount, currency, payeeEmail, participant, comment onyxMethod: isNewIOUReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, value: { - ...(isNewIOUReport ? {[optimisticCreatedActionForIOU.reportActionID]: optimisticCreatedActionForIOU} : {}), - [optimisticIOUAction.reportActionID]: optimisticIOUAction, + ...(isNewIOUReport ? {[iouCreatedAction.reportActionID]: iouCreatedAction} : {}), + [iouAction.reportActionID]: iouAction, }, }, ]; @@ -191,7 +120,7 @@ function requestMoney(report, amount, currency, payeeEmail, participant, comment : []), { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, value: {pendingAction: null}, }, ...(isNewChatReport @@ -199,7 +128,7 @@ function requestMoney(report, amount, currency, payeeEmail, participant, comment { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, - value: {[optimisticCreatedActionForChat.reportActionID]: {pendingAction: null}}, + value: {[chatCreatedAction.reportActionID]: {pendingAction: null}}, }, ] : []), @@ -207,8 +136,8 @@ function requestMoney(report, amount, currency, payeeEmail, participant, comment onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, value: { - ...(isNewIOUReport ? {[optimisticCreatedActionForIOU.reportActionID]: {pendingAction: null}} : {}), - [optimisticIOUAction.reportActionID]: {pendingAction: null}, + ...(isNewIOUReport ? {[iouCreatedAction.reportActionID]: {pendingAction: null}} : {}), + [iouAction.reportActionID]: {pendingAction: null}, }, }, ]; @@ -245,7 +174,7 @@ function requestMoney(report, amount, currency, payeeEmail, participant, comment : []), { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, value: { errors: { [DateUtils.getMicroseconds()]: Localize.translateLocal('iou.error.genericCreateFailureMessage'), @@ -257,7 +186,7 @@ function requestMoney(report, amount, currency, payeeEmail, participant, comment { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, - value: {[optimisticCreatedActionForChat.reportActionID]: {pendingAction: null}}, + value: {[chatCreatedAction.reportActionID]: {pendingAction: null}}, }, ] : []), @@ -265,12 +194,98 @@ function requestMoney(report, amount, currency, payeeEmail, participant, comment onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, value: { - ...(isNewIOUReport ? {[optimisticCreatedActionForIOU.reportActionID]: {pendingAction: null}} : {}), - [optimisticIOUAction.reportActionID]: {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}, + ...(isNewIOUReport ? {[iouCreatedAction.reportActionID]: {pendingAction: null}} : {}), + [iouAction.reportActionID]: {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}, }, }, ]; + return [optimisticData, successData, failureData]; +} + +/** + * Request money from another user + * + * @param {Object} report + * @param {Number} amount - always in the smallest unit of the currency + * @param {String} currency + * @param {String} payeeEmail + * @param {Object} participant + * @param {String} comment + */ +function requestMoney(report, amount, currency, payeeEmail, participant, comment) { + const payerEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login); + const isPolicyExpenseChat = participant.isPolicyExpenseChat || participant.isOwnPolicyExpenseChat; + + // STEP 1: Get existing chat report OR build a new optimistic one + let isNewChatReport = false; + let chatReport = lodashGet(report, 'reportID', null) ? report : null; + + // If this is a policyExpenseChat, the chatReport must exist and we can get it from Onyx. + // report is null if the flow is initiated from the global create menu. However, participant always stores the reportID if it exists, which is the case for policyExpenseChats + if (!chatReport && isPolicyExpenseChat) { + chatReport = chatReports[`${ONYXKEYS.COLLECTION.REPORT}${participant.reportID}`]; + } + + if (!chatReport) { + chatReport = ReportUtils.getChatByParticipants([payerEmail]); + } + + // If we still don't have a report, it likely doens't exist and we need to build an optimistic one + if (!chatReport) { + isNewChatReport = true; + chatReport = ReportUtils.buildOptimisticChatReport([payerEmail]); + } + + // STEP 2: Get existing IOU report and update its total OR build a new optimistic one + const isNewIOUReport = !chatReport.iouReportID; + let iouReport; + + if (!isNewIOUReport) { + if (isPolicyExpenseChat) { + iouReport = {...iouReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`]}; + + // Because of the Expense reports are stored as negative values, we substract the total from the amount + iouReport.total -= amount; + } else { + iouReport = IOUUtils.updateIOUOwnerAndTotal(iouReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`], payeeEmail, amount, currency); + } + } else { + iouReport = isPolicyExpenseChat + ? ReportUtils.buildOptimisticExpenseReport(chatReport.reportID, chatReport.policyID, payeeEmail, amount, currency) + : ReportUtils.buildOptimisticIOUReport(payeeEmail, payerEmail, amount, chatReport.reportID, currency); + } + + // STEP 3: Build optimistic transaction + const optimisticTransaction = TransactionUtils.buildOptimisticTransaction(amount, currency, iouReport.reportID, comment); + + // STEP 4: Build optimistic reportActions. We need a CREATED action for each report and an IOU action for the IOU report + // Note: The CREATED action for the IOU report must be optimistically generated before the IOU action so there's no chance that it appears after the IOU action in the chat + const optimisticCreatedActionForChat = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail); + const optimisticCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail); + const optimisticIOUAction = ReportUtils.buildOptimisticIOUReportAction( + CONST.IOU.REPORT_ACTION_TYPE.CREATE, + amount, + currency, + comment, + [participant], + optimisticTransaction.transactionID, + '', + iouReport.reportID, + ); + + // STEP 5: Build Onyx Data + const [optimisticData, successData, failureData] = buildOnyxDataForMoneyRequest( + chatReport, + iouReport, + optimisticTransaction, + optimisticCreatedActionForChat, + optimisticCreatedActionForIOU, + optimisticIOUAction, + isNewChatReport, + isNewIOUReport, + ); + // STEP 6: Make the request const parsedComment = ReportUtils.getParsedComment(comment); API.write( @@ -445,10 +460,10 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment const existingOneOnOneChatReport = !hasMultipleParticipants && !existingGroupChatReportID ? groupChatReport : ReportUtils.getChatByParticipants([email]); const oneOnOneChatReport = existingOneOnOneChatReport || ReportUtils.buildOptimisticChatReport([email]); let oneOnOneIOUReport; - let existingIOUReport = null; + let existingOneOnOneIOUReport = null; if (oneOnOneChatReport.iouReportID) { - existingIOUReport = iouReports[`${ONYXKEYS.COLLECTION.REPORT}${oneOnOneChatReport.iouReportID}`]; - oneOnOneIOUReport = IOUUtils.updateIOUOwnerAndTotal(existingIOUReport, currentUserEmail, splitAmount, currency); + existingOneOnOneIOUReport = iouReports[`${ONYXKEYS.COLLECTION.REPORT}${oneOnOneChatReport.iouReportID}`]; + oneOnOneIOUReport = IOUUtils.updateIOUOwnerAndTotal(existingOneOnOneIOUReport, currentUserEmail, splitAmount, currency); oneOnOneChatReport.hasOutstandingIOU = oneOnOneIOUReport.total !== 0; } else { oneOnOneIOUReport = ReportUtils.buildOptimisticIOUReport(currentUserEmail, email, splitAmount, oneOnOneChatReport.reportID, currency); @@ -466,9 +481,9 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment ); // Note: The created action must be optimistically generated before the IOU action so there's no chance that the created action appears after the IOU action in the chat - const oneOnOneCreatedChatReportAction = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmail); - const oneOnOneCreatedIOUReportAction = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmail); - const oneOnOneIOUReportAction = ReportUtils.buildOptimisticIOUReportAction( + const oneOnOneCreatedActionForChat = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmail); + const oneOnOneCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmail); + const oneOnOneIOUAction = ReportUtils.buildOptimisticIOUReportAction( CONST.IOU.REPORT_ACTION_TYPE.CREATE, splitAmount, currency, @@ -479,8 +494,8 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment oneOnOneIOUReport.reportID, ); - oneOnOneIOUReport.lastMessageText = oneOnOneIOUReportAction.message[0].text; - oneOnOneIOUReport.lastMessageHtml = oneOnOneIOUReportAction.message[0].html; + oneOnOneIOUReport.lastMessageText = oneOnOneIOUAction.message[0].text; + oneOnOneIOUReport.lastMessageHtml = oneOnOneIOUAction.message[0].html; if (!existingOneOnOneChatReport) { oneOnOneChatReport.pendingFields = { @@ -492,14 +507,17 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment { onyxMethod: existingOneOnOneChatReport ? Onyx.METHOD.MERGE : Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT}${oneOnOneChatReport.reportID}`, - value: oneOnOneChatReport, + value: { + ...oneOnOneChatReport, + ...(existingOneOnOneChatReport ? {} : {pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}}), + }, }, { - onyxMethod: existingOneOnOneChatReport ? Onyx.METHOD.MERGE : Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneChatReport.reportID}`, + onyxMethod: existingOneOnOneIOUReport ? Onyx.METHOD.MERGE : Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT}${oneOnOneIOUReport.reportID}`, value: { - ...(existingOneOnOneChatReport ? {} : {[oneOnOneCreatedChatReportAction.reportActionID]: oneOnOneCreatedChatReportAction}), - [oneOnOneIOUReportAction.reportActionID]: oneOnOneIOUReportAction, + ...oneOnOneIOUReport, + ...(existingOneOnOneIOUReport ? {} : {pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}}), }, }, { @@ -507,22 +525,56 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment key: `${ONYXKEYS.COLLECTION.TRANSACTION}${oneOnOneTransaction.transactionID}`, value: oneOnOneTransaction, }, - ); - - successData.push( + ...(existingOneOnOneChatReport + ? [] + : [ + { + onyxMethod: existingOneOnOneChatReport ? Onyx.METHOD.MERGE : Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneChatReport.reportID}`, + value: { + [oneOnOneCreatedActionForChat.reportActionID]: oneOnOneCreatedActionForChat, + }, + }, + ]), { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneChatReport.reportID}`, + onyxMethod: existingOneOnOneIOUReport ? Onyx.METHOD.MERGE : Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneIOUReport.reportID}`, value: { - ...(existingOneOnOneChatReport ? {} : {[oneOnOneCreatedChatReportAction.reportActionID]: {pendingAction: null}}), + ...(existingOneOnOneIOUReport ? {} : {[oneOnOneCreatedActionForIOU.reportActionID]: oneOnOneCreatedActionForIOU}), + [oneOnOneIOUAction.reportActionID]: oneOnOneIOUAction, }, }, + ); + + successData.push( + ...(existingOneOnOneChatReport + ? [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneChatReport.reportID}`, + value: { + [oneOnOneCreatedActionForChat.reportActionID]: {pendingAction: null}, + }, + }, + ] + : []), + ...(existingOneOnOneIOUReport + ? [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneChatReport.reportID}`, + value: { + [oneOnOneCreatedActionForChat.reportActionID]: {pendingAction: null}, + }, + }, + ] + : []), { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneIOUReport.reportID}`, value: { - ...(existingIOUReport ? {} : {[oneOnOneCreatedIOUReportAction.reportActionID]: {pendingAction: null}}), - [oneOnOneIOUReportAction.reportActionID]: {pendingAction: null}, + ...(existingOneOnOneIOUReport ? {} : {[oneOnOneCreatedActionForIOU.reportActionID]: {pendingAction: null}}), + [oneOnOneIOUAction.reportActionID]: {pendingAction: null}, }, }, { @@ -545,7 +597,7 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneIOUReport.reportID}`, value: { - [oneOnOneIOUReportAction.reportActionID]: { + [oneOnOneIOUAction.reportActionID]: { errors: { [DateUtils.getMicroseconds()]: Localize.translateLocal('iou.error.genericCreateFailureMessage'), }, @@ -592,7 +644,7 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment failureData.push({ onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT}${oneOnOneIOUReport.reportID}`, - value: existingIOUReport || oneOnOneIOUReport, + value: existingOneOnOneIOUReport || oneOnOneIOUReport, }); const splitData = { @@ -601,11 +653,11 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment iouReportID: oneOnOneIOUReport.reportID, chatReportID: oneOnOneChatReport.reportID, transactionID: oneOnOneTransaction.transactionID, - reportActionID: oneOnOneIOUReportAction.reportActionID, + reportActionID: oneOnOneIOUAction.reportActionID, }; - if (!_.isEmpty(oneOnOneCreatedChatReportAction)) { - splitData.createdReportActionID = oneOnOneCreatedChatReportAction.reportActionID; + if (!_.isEmpty(oneOnOneCreatedActionForChat)) { + splitData.createdReportActionID = oneOnOneCreatedActionForChat.reportActionID; } splits.push(splitData); From b849daa151d1c508aab9c76b828aecb7ba434cb3 Mon Sep 17 00:00:00 2001 From: Sujit Kumar <60378235+therealsujitk@users.noreply.github.com> Date: Fri, 12 May 2023 21:05:21 +0530 Subject: [PATCH 109/329] Add logic to to take care of zero-decimal currencies --- src/libs/CurrencyUtils.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libs/CurrencyUtils.js b/src/libs/CurrencyUtils.js index a30e1255dd3c..02a986e94d77 100644 --- a/src/libs/CurrencyUtils.js +++ b/src/libs/CurrencyUtils.js @@ -86,6 +86,14 @@ function isCurrencySymbolLTR(currencyCode) { */ function convertToSmallestUnit(currency, amountAsFloat) { const currencyUnit = getCurrencyUnit(currency); + + // If the currency is zero-decimal, we simply drop everything after the + // decimal point. + if (currencyUnit === 1) { + return Math.trunc(amountAsFloat); + } + + // We round off the number to resolve floating-point precision issues. return Math.round(amountAsFloat * currencyUnit); } From 08f6a64f0e7e7a28b16d27d0e499db9fedea789e Mon Sep 17 00:00:00 2001 From: Sujit Kumar <60378235+therealsujitk@users.noreply.github.com> Date: Fri, 12 May 2023 21:17:25 +0530 Subject: [PATCH 110/329] Add unit test cases to check for precision loss --- tests/unit/CurrencyUtilsTest.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/CurrencyUtilsTest.js b/tests/unit/CurrencyUtilsTest.js index c70ec9fb18f5..acfc81572775 100644 --- a/tests/unit/CurrencyUtilsTest.js +++ b/tests/unit/CurrencyUtilsTest.js @@ -89,7 +89,9 @@ describe('CurrencyUtils', () => { test.each([ [CONST.CURRENCY.USD, 25, 2500], [CONST.CURRENCY.USD, 25.5, 2550], - [CONST.CURRENCY.USD, 25.5, 2550], + [CONST.CURRENCY.USD, 80.6, 8060], + [CONST.CURRENCY.USD, 80.9, 8090], + [CONST.CURRENCY.USD, 80.99, 8099], ['JPY', 25, 25], ['JPY', 2500, 2500], ['JPY', 25.5, 25], From d764d50ba913465f940e2a2613c8c79e611b9761 Mon Sep 17 00:00:00 2001 From: hublot Date: Sat, 13 May 2023 00:39:22 +0800 Subject: [PATCH 111/329] Remove borderWidth of fullScreen smallScreen modal and modify PDF width --- src/components/AttachmentCarousel/index.js | 6 +++--- src/components/PDFView/index.js | 5 +++-- src/styles/getModalStyles/getBaseModalStyles.js | 8 ++++---- src/styles/styles.js | 4 ++-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/components/AttachmentCarousel/index.js b/src/components/AttachmentCarousel/index.js index b9315894a4b4..1ef7a6fe906e 100644 --- a/src/components/AttachmentCarousel/index.js +++ b/src/components/AttachmentCarousel/index.js @@ -244,8 +244,8 @@ class AttachmentCarousel extends React.Component { renderCell(props) { // Use window width instead of layout width to address the issue in https://github.com/Expensify/App/issues/17760 // considering horizontal margin and border width in centered modal - const modalStyles = styles.centeredModalStyles(this.props.isSmallScreenWidth); - const style = [props.style, styles.h100, {width: this.props.windowWidth - (modalStyles.marginHorizontal + modalStyles.borderWidth) * 2 + 1}]; + const modalStyles = styles.centeredModalStyles(this.props.isSmallScreenWidth, true); + const style = [props.style, styles.h100, {width: this.props.windowWidth - (modalStyles.marginHorizontal + modalStyles.borderWidth) * 2}]; return ( this.setState({containerWidth: nativeEvent.layout.width + 1})} + onLayout={({nativeEvent}) => this.setState({containerWidth: nativeEvent.layout.width})} onMouseEnter={() => !this.canUseTouchScreen && this.toggleArrowsVisibility(true)} onMouseLeave={() => !this.canUseTouchScreen && this.toggleArrowsVisibility(false)} > diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index 77886ff512f2..2027ce501af3 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -108,8 +108,9 @@ class PDFView extends Component { render() { const pdfContainerWidth = this.state.windowWidth - 100; const pageWidthOnLargeScreen = pdfContainerWidth <= variables.pdfPageMaxWidth ? pdfContainerWidth : variables.pdfPageMaxWidth; - const pageWidth = this.props.isSmallScreenWidth ? this.state.windowWidth - 30 : pageWidthOnLargeScreen; - + // On android_chrome, the actual display width of the pdf will be slightly larger than pageWidth, maybe the react-pdf canvas decimal calculation is incorrect, + // or maybe because of window.visualViewport.width - window.innerWidth = 0.090911865234375, so we reduce it by 1px + const pageWidth = this.props.isSmallScreenWidth ? this.state.windowWidth - 1 : pageWidthOnLargeScreen; const outerContainerStyle = [styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]; // If we're requesting a password then we need to hide - but still render - diff --git a/src/styles/getModalStyles/getBaseModalStyles.js b/src/styles/getModalStyles/getBaseModalStyles.js index ddc926b3ce09..9f37642b9b25 100644 --- a/src/styles/getModalStyles/getBaseModalStyles.js +++ b/src/styles/getModalStyles/getBaseModalStyles.js @@ -3,8 +3,8 @@ import variables from '../variables'; import themeColors from '../themes/default'; import styles from '../styles'; -const getCenteredModalStyles = (windowWidth, isSmallScreenWidth) => ({ - borderWidth: styles.centeredModalStyles(isSmallScreenWidth).borderWidth, +const getCenteredModalStyles = (windowWidth, isSmallScreenWidth, isFullScreenWhenSmall) => ({ + borderWidth: styles.centeredModalStyles(isSmallScreenWidth, isFullScreenWhenSmall).borderWidth, width: isSmallScreenWidth ? '100%' : windowWidth - styles.centeredModalStyles(isSmallScreenWidth).marginHorizontal * 2, }); @@ -83,7 +83,7 @@ export default (type, windowDimensions, popoverAnchorPosition = {}, innerContain marginBottom: isSmallScreenWidth ? 0 : 20, borderRadius: isSmallScreenWidth ? 0 : 12, overflow: 'hidden', - ...getCenteredModalStyles(windowWidth, isSmallScreenWidth), + ...getCenteredModalStyles(windowWidth, isSmallScreenWidth, false), }; // Allow this modal to be dismissed with a swipe down or swipe right @@ -118,7 +118,7 @@ export default (type, windowDimensions, popoverAnchorPosition = {}, innerContain marginBottom: isSmallScreenWidth ? 0 : 20, borderRadius: isSmallScreenWidth ? 0 : 12, overflow: 'hidden', - ...getCenteredModalStyles(windowWidth, isSmallScreenWidth), + ...getCenteredModalStyles(windowWidth, isSmallScreenWidth, true), }; swipeDirection = undefined; animationIn = isSmallScreenWidth ? 'slideInRight' : 'fadeIn'; diff --git a/src/styles/styles.js b/src/styles/styles.js index 9749b0cbd324..c7228c2a2f69 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -1977,8 +1977,8 @@ const styles = { backgroundColor: themeColors.modalBackdrop, }, - centeredModalStyles: (isSmallScreenWidth) => ({ - borderWidth: isSmallScreenWidth ? 1 : 0, + centeredModalStyles: (isSmallScreenWidth, isFullScreenWhenSmall) => ({ + borderWidth: isSmallScreenWidth && !isFullScreenWhenSmall ? 1 : 0, marginHorizontal: isSmallScreenWidth ? 0 : 20, }), From 80208feeca24bd05bbe2acfd2961a7527ef627f4 Mon Sep 17 00:00:00 2001 From: Prince Mendiratta Date: Fri, 12 May 2023 22:33:54 +0530 Subject: [PATCH 112/329] feat: save magic code in onyx Signed-off-by: Prince Mendiratta --- src/libs/actions/Session/index.js | 11 ++++-- .../ValidateCodeForm/BaseValidateCodeForm.js | 35 ++++++++++--------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/libs/actions/Session/index.js b/src/libs/actions/Session/index.js index df811173e6b8..ba226d6d0808 100644 --- a/src/libs/actions/Session/index.js +++ b/src/libs/actions/Session/index.js @@ -335,6 +335,13 @@ function signIn(password, validateCode, twoFactorAuthCode, preferredLocale = CON isLoading: false, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.CREDENTIALS, + value: { + validateCode, + }, + }, ]; const failureData = [ @@ -350,8 +357,8 @@ function signIn(password, validateCode, twoFactorAuthCode, preferredLocale = CON const params = {twoFactorAuthCode, email: credentials.login, preferredLocale}; // Conditionally pass a password or validateCode to command since we temporarily allow both flows - if (validateCode) { - params.validateCode = validateCode; + if (validateCode || twoFactorAuthCode) { + params.validateCode = validateCode || credentials.validateCode; } else { params.password = password; } diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js index 7e3fe92c86c8..83b709ab55bf 100755 --- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js @@ -161,24 +161,25 @@ class BaseValidateCodeForm extends React.Component { validateAndSubmitForm() { const requiresTwoFactorAuth = this.props.account.requiresTwoFactorAuth; - if (!this.state.validateCode.trim()) { - this.setState({formError: {validateCode: 'validateCodeForm.error.pleaseFillMagicCode'}}); - return; - } - - if (!ValidationUtils.isValidValidateCode(this.state.validateCode)) { - this.setState({formError: {validateCode: 'validateCodeForm.error.incorrectMagicCode'}}); - return; - } + if (requiresTwoFactorAuth) { + if (!this.state.twoFactorAuthCode.trim()) { + this.setState({formError: {twoFactorAuthCode: 'validateCodeForm.error.pleaseFillTwoFactorAuth'}}); + return; + } - if (requiresTwoFactorAuth && !this.state.twoFactorAuthCode.trim()) { - this.setState({formError: {twoFactorAuthCode: 'validateCodeForm.error.pleaseFillTwoFactorAuth'}}); - return; - } - - if (requiresTwoFactorAuth && !ValidationUtils.isValidTwoFactorCode(this.state.twoFactorAuthCode)) { - this.setState({formError: {twoFactorAuthCode: 'passwordForm.error.incorrect2fa'}}); - return; + if (!ValidationUtils.isValidTwoFactorCode(this.state.twoFactorAuthCode)) { + this.setState({formError: {twoFactorAuthCode: 'passwordForm.error.incorrect2fa'}}); + return; + } + } else { + if (!this.state.validateCode.trim()) { + this.setState({formError: {validateCode: 'validateCodeForm.error.pleaseFillMagicCode'}}); + return; + } + if (!ValidationUtils.isValidValidateCode(this.state.validateCode)) { + this.setState({formError: {validateCode: 'validateCodeForm.error.incorrectMagicCode'}}); + return; + } } this.setState({ From 00c0523248cf266e8b6c033da3253f8bc54895d8 Mon Sep 17 00:00:00 2001 From: AmjedNazzal Date: Fri, 12 May 2023 20:35:58 +0300 Subject: [PATCH 113/329] Issue18265 --- src/components/OptionsSelector/BaseOptionsSelector.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 21af8d1762db..84097a10dbb0 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -52,6 +52,7 @@ class BaseOptionsSelector extends Component { componentDidMount() { const enterConfig = CONST.KEYBOARD_SHORTCUTS.ENTER; + let isEnterDisabled = false; this.unsubscribeEnter = KeyboardShortcut.subscribe( enterConfig.shortcutKey, () => { @@ -60,7 +61,14 @@ class BaseOptionsSelector extends Component { return; } - this.selectRow(focusedOption); + // We are handeling multiple Enter events to address the issue in https://github.com/Expensify/App/issues/18265 + if (!isEnterDisabled) { + isEnterDisabled = true; + this.selectRow(focusedOption); + setTimeout(() => { + isEnterDisabled = false; + }, 300); + } }, enterConfig.descriptionKey, enterConfig.modifiers, From c554f5dc88e82af1f5a7022c33e9840d2efae4d2 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Fri, 12 May 2023 19:20:47 +0100 Subject: [PATCH 114/329] Removed redundant border radius in Avatar --- src/components/Avatar.js | 10 ++++++++-- src/components/MultipleAvatars.js | 2 +- src/components/SubscriptAvatar.js | 32 +++++++++++++++---------------- src/styles/StyleUtils.js | 27 ++++++++++++++++++++------ src/styles/styles.js | 6 ------ 5 files changed, 45 insertions(+), 32 deletions(-) diff --git a/src/components/Avatar.js b/src/components/Avatar.js index 9bdd0049aef3..3efe0d4052d1 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -21,6 +21,10 @@ const propTypes = { // eslint-disable-next-line react/forbid-prop-types imageStyles: PropTypes.arrayOf(PropTypes.object), + /** Additional styles to pass to Icon */ + // eslint-disable-next-line react/forbid-prop-types + iconAdditionalStyles: PropTypes.arrayOf(PropTypes.object), + /** Extra styles to pass to View wrapper */ containerStyles: stylePropTypes, @@ -48,6 +52,7 @@ const propTypes = { const defaultProps = { source: null, imageStyles: [], + iconAdditionalStyles: [], containerStyles: [], size: CONST.AVATAR_SIZE.DEFAULT, fill: themeColors.icon, @@ -68,9 +73,9 @@ function Avatar(props) { const isWorkspace = props.type === CONST.ICON_TYPE_WORKSPACE; const iconSize = StyleUtils.getAvatarSize(props.size); - const imageStyle = [StyleUtils.getAvatarStyle(props.size), ...props.imageStyles, StyleUtils.getAvatarBorderRadius(props.size, props.type)]; + const imageStyle = props.imageStyles ? [StyleUtils.getAvatarStyle(props.size), ...props.imageStyles, StyleUtils.getAvatarBorderRadius(props.size, props.type)] : []; - const iconStyle = [StyleUtils.getAvatarStyle(props.size), styles.bgTransparent, ...props.imageStyles]; + const iconStyle = props.imageStyles ? [StyleUtils.getAvatarStyle(props.size), styles.bgTransparent, ...props.imageStyles] : null; const iconFillColor = isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(props.name).fill : props.fill; const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(props.name) : props.fallbackIcon; @@ -91,6 +96,7 @@ function Avatar(props) { StyleUtils.getAvatarBorderStyle(props.size, props.type), isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(props.name) : {}, imageError ? StyleUtils.getBackgroundColorStyle(themeColors.fallbackIconColor) : {}, + ...props.iconAdditionalStyles, ]} /> diff --git a/src/components/MultipleAvatars.js b/src/components/MultipleAvatars.js index 9b4d10c5c8e8..58bc12f48402 100644 --- a/src/components/MultipleAvatars.js +++ b/src/components/MultipleAvatars.js @@ -80,7 +80,7 @@ const MultipleAvatars = (props) => { } const oneAvatarSize = StyleUtils.getAvatarStyle(props.size); - const oneAvatarBorderWidth = StyleUtils.getAvatarBorderWidth(props.size); + const oneAvatarBorderWidth = StyleUtils.getAvatarBorderWidth(props.size).borderWidth; const overlapSize = oneAvatarSize.width / 3; if (props.shouldStackHorizontally) { diff --git a/src/components/SubscriptAvatar.js b/src/components/SubscriptAvatar.js index d9f6b5246cb3..65c016e29ff3 100644 --- a/src/components/SubscriptAvatar.js +++ b/src/components/SubscriptAvatar.js @@ -63,23 +63,21 @@ const SubscriptAvatar = (props) => { type={props.mainAvatar.type} /> - - - - - + + + ); }; diff --git a/src/styles/StyleUtils.js b/src/styles/StyleUtils.js index 5e15250be12d..537c88564fe8 100644 --- a/src/styles/StyleUtils.js +++ b/src/styles/StyleUtils.js @@ -33,11 +33,11 @@ const workspaceColorOptions = [ const avatarBorderSizes = { [CONST.AVATAR_SIZE.SMALL_SUBSCRIPT]: variables.componentBorderRadiusSmall, [CONST.AVATAR_SIZE.MID_SUBSCRIPT]: variables.componentBorderRadiusSmall, - [CONST.AVATAR_SIZE.SUBSCRIPT]: variables.componentBorderRadiusSmall, + [CONST.AVATAR_SIZE.SUBSCRIPT]: variables.componentBorderRadiusMedium, [CONST.AVATAR_SIZE.SMALLER]: variables.componentBorderRadiusMedium, [CONST.AVATAR_SIZE.SMALL]: variables.componentBorderRadiusMedium, [CONST.AVATAR_SIZE.HEADER]: variables.componentBorderRadiusMedium, - [CONST.AVATAR_SIZE.DEFAULT]: variables.componentBorderRadiusMedium, + [CONST.AVATAR_SIZE.DEFAULT]: variables.componentBorderRadiusNormal, [CONST.AVATAR_SIZE.MEDIUM]: variables.componentBorderRadiusLarge, [CONST.AVATAR_SIZE.LARGE]: variables.componentBorderRadiusLarge, [CONST.AVATAR_SIZE.LARGE_BORDERED]: variables.componentBorderRadiusRounded, @@ -85,7 +85,7 @@ function getAvatarStyle(size) { /** * Get Font size of '+1' text on avatar overlay * @param {String} size - * @returns {Number} + * @returns {Object} */ function getAvatarExtraFontSizeStyle(size) { const AVATAR_SIZES = { @@ -107,12 +107,12 @@ function getAvatarExtraFontSizeStyle(size) { /** * Get Bordersize of Avatar based on avatar size * @param {String} size - * @returns {Number} + * @returns {Object} */ function getAvatarBorderWidth(size) { const AVATAR_SIZES = { [CONST.AVATAR_SIZE.DEFAULT]: 3, - [CONST.AVATAR_SIZE.SMALL_SUBSCRIPT]: 2, + [CONST.AVATAR_SIZE.SMALL_SUBSCRIPT]: 1, [CONST.AVATAR_SIZE.MID_SUBSCRIPT]: 2, [CONST.AVATAR_SIZE.SUBSCRIPT]: 2, [CONST.AVATAR_SIZE.SMALL]: 3, @@ -121,7 +121,9 @@ function getAvatarBorderWidth(size) { [CONST.AVATAR_SIZE.MEDIUM]: 3, [CONST.AVATAR_SIZE.LARGE_BORDERED]: 4, }; - return AVATAR_SIZES[size]; + return { + borderWidth: AVATAR_SIZES[size], + }; } /** @@ -349,6 +351,18 @@ function getBackgroundColorStyle(backgroundColor) { }; } +/** + * Returns a style with the specified borderColor + * + * @param {String} borderColor + * @returns {Object} + */ +function getBorderColorStyle(borderColor) { + return { + borderColor, + }; +} + /** * Returns the width style for the wordmark logo on the sign in page * @@ -1132,6 +1146,7 @@ export { getAutoGrowHeightInputStyle, getBackgroundAndBorderStyle, getBackgroundColorStyle, + getBorderColorStyle, getBackgroundColorWithOpacityStyle, getBadgeColorStyle, getButtonBackgroundColorStyle, diff --git a/src/styles/styles.js b/src/styles/styles.js index e0003a1114e5..9151efff0ba5 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -1780,18 +1780,12 @@ const styles = { position: 'absolute', right: -6, bottom: -6, - borderWidth: 2, - borderRadius: 18, - borderColor: 'transparent', }, secondAvatarSubscriptCompact: { position: 'absolute', bottom: -1, right: -1, - borderWidth: 1, - borderRadius: 18, - borderColor: 'transparent', }, leftSideLargeAvatar: { From 6fc32bdaaef99caf4c61fc6aff79c973de3f80fa Mon Sep 17 00:00:00 2001 From: AmjedNazzal Date: Fri, 12 May 2023 21:22:38 +0300 Subject: [PATCH 115/329] add promise to debounce --- .../OptionsSelector/BaseOptionsSelector.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 84097a10dbb0..8f9614cc70ad 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -47,12 +47,12 @@ class BaseOptionsSelector extends Component { this.state = { allOptions, focusedIndex, + isEnterDisabled: false, }; } componentDidMount() { const enterConfig = CONST.KEYBOARD_SHORTCUTS.ENTER; - let isEnterDisabled = false; this.unsubscribeEnter = KeyboardShortcut.subscribe( enterConfig.shortcutKey, () => { @@ -60,14 +60,13 @@ class BaseOptionsSelector extends Component { if (!focusedOption) { return; } - - // We are handeling multiple Enter events to address the issue in https://github.com/Expensify/App/issues/18265 - if (!isEnterDisabled) { - isEnterDisabled = true; - this.selectRow(focusedOption); - setTimeout(() => { - isEnterDisabled = false; - }, 300); + if (!this.state.isEnterDisabled) { + this.setState({isEnterDisabled: true}); + let result = this.selectRow(focusedOption); + if (!(result instanceof Promise)) { + result = Promise.resolve(); + } + setTimeout(() => result.finally(() => this.setState({isEnterDisabled: false})), 300); } }, enterConfig.descriptionKey, From 3f4eafa40201c5ddcd8a19941d850e6a150a8e49 Mon Sep 17 00:00:00 2001 From: AmjedNazzal Date: Fri, 12 May 2023 21:26:52 +0300 Subject: [PATCH 116/329] Changed variable name --- src/components/OptionsSelector/BaseOptionsSelector.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 8f9614cc70ad..695d56d426cb 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -47,7 +47,7 @@ class BaseOptionsSelector extends Component { this.state = { allOptions, focusedIndex, - isEnterDisabled: false, + shouldDisableRowSelection: false, }; } @@ -60,13 +60,13 @@ class BaseOptionsSelector extends Component { if (!focusedOption) { return; } - if (!this.state.isEnterDisabled) { - this.setState({isEnterDisabled: true}); + if (!this.state.shouldDisableRowSelection) { + this.setState({shouldDisableRowSelection: true}); let result = this.selectRow(focusedOption); if (!(result instanceof Promise)) { result = Promise.resolve(); } - setTimeout(() => result.finally(() => this.setState({isEnterDisabled: false})), 300); + setTimeout(() => result.finally(() => this.setState({shouldDisableRowSelection: false})), 300); } }, enterConfig.descriptionKey, From a49b6b6ac389711777f648cc9498ec54d49223ec Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Fri, 12 May 2023 19:31:23 +0100 Subject: [PATCH 117/329] because consistency matters --- src/components/Avatar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Avatar.js b/src/components/Avatar.js index 3efe0d4052d1..b2d90bddee17 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -73,9 +73,9 @@ function Avatar(props) { const isWorkspace = props.type === CONST.ICON_TYPE_WORKSPACE; const iconSize = StyleUtils.getAvatarSize(props.size); - const imageStyle = props.imageStyles ? [StyleUtils.getAvatarStyle(props.size), ...props.imageStyles, StyleUtils.getAvatarBorderRadius(props.size, props.type)] : []; + const imageStyle = props.imageStyles ? [StyleUtils.getAvatarStyle(props.size), ...props.imageStyles, StyleUtils.getAvatarBorderRadius(props.size, props.type)] : undefined; - const iconStyle = props.imageStyles ? [StyleUtils.getAvatarStyle(props.size), styles.bgTransparent, ...props.imageStyles] : null; + const iconStyle = props.imageStyles ? [StyleUtils.getAvatarStyle(props.size), styles.bgTransparent, ...props.imageStyles] : undefined; const iconFillColor = isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(props.name).fill : props.fill; const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(props.name) : props.fallbackIcon; From 6e3bf2c53730b7507f2d95a852b0b2a08d298ed9 Mon Sep 17 00:00:00 2001 From: David Bondy Date: Fri, 12 May 2023 12:32:54 -0600 Subject: [PATCH 118/329] appease linter --- src/components/ReportActionItem/MoneyRequestAction.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ReportActionItem/MoneyRequestAction.js b/src/components/ReportActionItem/MoneyRequestAction.js index f74fa806fd56..798393a78e46 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.js +++ b/src/components/ReportActionItem/MoneyRequestAction.js @@ -2,6 +2,7 @@ import _ from 'underscore'; import React from 'react'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; +import lodashGet from 'lodash/get'; import ONYXKEYS from '../../ONYXKEYS'; import CONST from '../../CONST'; import {withNetwork} from '../OnyxProvider'; From b4a991b92f1167687a9673863d58b88b1a53d207 Mon Sep 17 00:00:00 2001 From: David Bondy Date: Fri, 12 May 2023 12:33:13 -0600 Subject: [PATCH 119/329] appease linter and import full lib --- src/components/ReportActionItem/MoneyRequestAction.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestAction.js b/src/components/ReportActionItem/MoneyRequestAction.js index 798393a78e46..ec613e1e0cc5 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.js +++ b/src/components/ReportActionItem/MoneyRequestAction.js @@ -16,10 +16,9 @@ import Navigation from '../../libs/Navigation/Navigation'; import ROUTES from '../../ROUTES'; import styles from '../../styles/styles'; import * as IOUUtils from '../../libs/IOUUtils'; -import {getThreadForReportActionID, buildOptimisticChatReport} from '../../libs/ReportUtils'; +import * as ReportUtils from '../../libs/ReportUtils'; import * as Report from '../../libs/actions/Report'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; -import lodashGet from 'lodash/get'; const propTypes = { /** All the data of the action */ @@ -89,12 +88,12 @@ const MoneyRequestAction = (props) => { const onIOUPreviewPressed = () => { // This would ideally be passed as a prop or hooked up via withOnyx so that we are not be triggering a potentially intensive // search in an onPress handler, I think this could lead to performance issues but it probably ok for now. - const thread = getThreadForReportActionID(props.action.reportActionID); + const thread = ReportUtils.getThreadForReportActionID(props.action.reportActionID); console.log({thread}); if (_.isEmpty(thread)) { // Since a thread does not exist yet then we need to create it now. This is done by creating the report object // and passing the parentReportActionID of the reportAction. OpenReport will then automatically create the thread for us. - const optimisticThreadReport = buildOptimisticChatReport( + const optimisticThreadReport = ReportUtils.buildOptimisticChatReport( props.chatReport.participants, props.translate('iou.threadReportName', {payee: props.action.actorEmail, comment: props.action.originalMessage.comment}), '', From c0e243046d8d035313781674e9ac1de7fe8f6424 Mon Sep 17 00:00:00 2001 From: David Bondy Date: Fri, 12 May 2023 12:35:34 -0600 Subject: [PATCH 120/329] we dont need to prefilter the user making the request out since auth will do this for us --- src/components/ReportActionItem/MoneyRequestAction.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestAction.js b/src/components/ReportActionItem/MoneyRequestAction.js index ec613e1e0cc5..df3afea34ab3 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.js +++ b/src/components/ReportActionItem/MoneyRequestAction.js @@ -106,11 +106,7 @@ const MoneyRequestAction = (props) => { props.action.reportActionID, ); console.log({optimisticThreadReport}); - Report.openReport( - optimisticThreadReport.reportID, - [_.reject(optimisticThreadReport.participants, (login) => login === lodashGet(props.session, 'email', ''))], - optimisticThreadReport, - ); + Report.openReport(optimisticThreadReport.reportID, optimisticThreadReport.participants, optimisticThreadReport); } if (hasMultipleParticipants) { Navigation.navigate(ROUTES.getReportParticipantsRoute(props.chatReportID)); From 4cfefbb9ae67f862b249d692dd0155eb4de2a745 Mon Sep 17 00:00:00 2001 From: David Bondy Date: Fri, 12 May 2023 12:56:56 -0600 Subject: [PATCH 121/329] use amount not payee in report name --- src/components/ReportActionItem/MoneyRequestAction.js | 6 +++++- src/languages/en.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestAction.js b/src/components/ReportActionItem/MoneyRequestAction.js index df3afea34ab3..80a9e8bd99a3 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.js +++ b/src/components/ReportActionItem/MoneyRequestAction.js @@ -19,6 +19,7 @@ import * as IOUUtils from '../../libs/IOUUtils'; import * as ReportUtils from '../../libs/ReportUtils'; import * as Report from '../../libs/actions/Report'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; +import * as CurrencyUtils from '../../libs/CurrencyUtils'; const propTypes = { /** All the data of the action */ @@ -95,7 +96,10 @@ const MoneyRequestAction = (props) => { // and passing the parentReportActionID of the reportAction. OpenReport will then automatically create the thread for us. const optimisticThreadReport = ReportUtils.buildOptimisticChatReport( props.chatReport.participants, - props.translate('iou.threadReportName', {payee: props.action.actorEmail, comment: props.action.originalMessage.comment}), + props.translate('iou.threadReportName', { + formattedAmount: CurrencyUtils.convertToDisplayString(props.iouReport.iouReportAmount, props.iouReport.currency), + comment: props.action.originalMessage.comment, + }), '', props.chatReport.policyID, props.chatReport.owner, diff --git a/src/languages/en.js b/src/languages/en.js index d078f55d042e..42bc7dd60e5b 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -329,7 +329,7 @@ export default { payerSettled: ({amount}) => `settled up ${amount}`, noReimbursableExpenses: 'This report has an invalid amount', pendingConversionMessage: "Total will update when you're back online", - threadReportName: ({payee, comment}) => `${payee} request${comment ? ` for ${comment}` : ''}`, + threadReportName: ({formattedAmount, comment}) => `${formattedAmount} request${comment ? ` for ${comment}` : ''}`, error: { invalidSplit: 'Split amounts do not equal total amount', other: 'Unexpected error, please try again later', From 9d518ba705f355e4ae512be8d312c144d470469f Mon Sep 17 00:00:00 2001 From: David Bondy Date: Fri, 12 May 2023 12:58:54 -0600 Subject: [PATCH 122/329] fixing jsdocs and comment --- src/libs/ReportUtils.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index b46887d4f89d..b02cf301f0e7 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1295,6 +1295,7 @@ function buildOptimisticIOUReportAction(type, amount, currency, comment, partici * @param {String} oldPolicyName * @param {String} visibility * @param {String} notificationPreference + * @param {String} parentReportActionID * @returns {Object} */ function buildOptimisticChatReport( @@ -1912,9 +1913,9 @@ function getWhisperDisplayNames(participants) { } /** - * Used to determine if a thread exists already or not for a given reportActionID + * Used to determine if a thread exists already for a given reportActionID * - * @param {String} + * @param {String} reportActionID * @returns {Boolean} */ function getThreadForReportActionID(reportActionID) { From f83e2404e8ede29eaa4512a94cfc4d26255df962 Mon Sep 17 00:00:00 2001 From: David Bondy Date: Fri, 12 May 2023 13:01:32 -0600 Subject: [PATCH 123/329] use proper prop --- src/components/ReportActionItem/MoneyRequestAction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestAction.js b/src/components/ReportActionItem/MoneyRequestAction.js index 80a9e8bd99a3..7341cace4434 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.js +++ b/src/components/ReportActionItem/MoneyRequestAction.js @@ -103,7 +103,7 @@ const MoneyRequestAction = (props) => { '', props.chatReport.policyID, props.chatReport.owner, - props.chatReport.type === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + props.chatReport.isOwnPolicyExpenseReport, props.chatReport.oldPolicyName, undefined, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, From d17768f4961cad6d3681a495539d481f8ab44991 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Fri, 12 May 2023 14:38:39 -0600 Subject: [PATCH 124/329] Add information about Prettier into our style guide --- contributingGuides/STYLE.md | 93 +++---------------------------------- 1 file changed, 6 insertions(+), 87 deletions(-) diff --git a/contributingGuides/STYLE.md b/contributingGuides/STYLE.md index 9d94623f0e33..f6694cfd3c19 100644 --- a/contributingGuides/STYLE.md +++ b/contributingGuides/STYLE.md @@ -4,94 +4,13 @@ For almost all of our code style rules, refer to the [Airbnb JavaScript Style Gu When writing ES6 or React code, please also refer to the [Airbnb React/JSX Style Guide](https://github.com/airbnb/javascript/tree/master/react). -There are a few things that we have customized for our tastes which will take precedence over Airbnb's guide. - -## Functions - - Always wrap the function expression for immediately-invoked function expressions (IIFE) in parens: - - ```javascript - // Bad - (function () { - console.log('Welcome to the Internet. Please follow me.'); - }()); - - // Good - (function () { - console.log('Welcome to the Internet. Please follow me.'); - })(); - ``` - -## Whitespace - - Use soft tabs set to 4 spaces. - - ```javascript - // Bad - function () { - ∙∙const name; - } - - // Bad - function () { - ∙const name; - } - - // Good - function () { - ∙∙∙∙const name; - } - ``` - - - Place 1 space before the function keyword and the opening parent for anonymous functions. This does not count for named functions. - - ```javascript - // Bad - function() { - ... - } +We use Prettier to automatically style our code. +- Prettier runs as part of each build process (eg. `npm run web`) +- It will automatically fix the styles on files when they are saved +- You can manually run Prettier with `npm run prettier` +- You can manually run Prettier in watch mode with `npm run prettier-watch` - // Bad - function getValue (element) { - ... - } - - // Good - function∙() { - ... - } - - // Good - function getValue(element) { - ... - } - ``` - - - Do not add spaces inside curly braces. - - ```javascript - // Bad - const foo = { clark: 'kent' }; - - // Good - const foo = {clark: 'kent'}; - ``` - - Aligning tokens should be avoided as it rarely aids in readability and often - produces inconsistencies and larger diffs when updating the code. - - ```javascript - // Good - const foo = { - foo: 'bar', - foobar: 'foobar', - foobarbaz: 'foobarbaz', - }; - - // Bad - const foo = { - foo : 'bar', - foobar : 'foobar', - foobarbaz: 'foobarbaz', - }; - ``` +There are a few things that we have customized for our tastes which will take precedence over Airbnb's guide. ## Naming Conventions From 6aa42933eebdf5a723899303eaa31481ba93c804 Mon Sep 17 00:00:00 2001 From: Georgia Monahan Date: Fri, 12 May 2023 22:54:10 +0100 Subject: [PATCH 125/329] Fix avatar size, fix hover color --- src/components/MultipleAvatars.js | 8 ++++++-- src/pages/home/report/ReportActionItem.js | 1 + src/pages/home/report/ReportActionItemThread.js | 7 ++++++- src/styles/StyleUtils.js | 7 ++++--- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/components/MultipleAvatars.js b/src/components/MultipleAvatars.js index daeb7a1c780d..7e7a7bbbc649 100644 --- a/src/components/MultipleAvatars.js +++ b/src/components/MultipleAvatars.js @@ -40,6 +40,9 @@ const propTypes = { /** Whether #focus mode is on */ isFocusMode: PropTypes.bool, + + /** Whether avatars are displayed on a report */ + isInReportView: PropTypes.bool, }; const defaultProps = { @@ -52,6 +55,7 @@ const defaultProps = { isHovered: false, isPressed: false, isFocusMode: false, + isInReportView: false, }; const MultipleAvatars = (props) => { @@ -108,7 +112,7 @@ const MultipleAvatars = (props) => { style={[ styles.justifyContentCenter, styles.alignItemsCenter, - StyleUtils.getHorizontalStackedAvatarBorderStyle(props.isHovered, props.isPressed), + StyleUtils.getHorizontalStackedAvatarBorderStyle(props.isHovered, props.isPressed, props.isInReportView), StyleUtils.getHorizontalStackedAvatarStyle(index, overlapSize, oneAvatarBorderWidth, oneAvatarSize.width), icon.type === CONST.ICON_TYPE_WORKSPACE ? StyleUtils.getAvatarBorderRadius(props.size, icon.type) : {}, ]} @@ -127,7 +131,7 @@ const MultipleAvatars = (props) => { style={[ styles.alignItemsCenter, styles.justifyContentCenter, - StyleUtils.getHorizontalStackedAvatarBorderStyle(props.isHovered, props.isPressed), + StyleUtils.getHorizontalStackedAvatarBorderStyle(props.isHovered, props.isPressed, props.isInReportView), // Set overlay background color with RGBA value so that the text will not inherit opacity StyleUtils.getBackgroundColorWithOpacityStyle(themeColors.overlay, variables.overlayOpacity), diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 38b5bda6f713..fd8849925df3 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -269,6 +269,7 @@ class ReportActionItem extends Component { childReportID={`${this.props.action.childReportID}`} numberOfReplies={this.props.action.childVisibleActionCount || 0} mostRecentReply={`${this.props.action.childLastVisibleActionCreated}`} + isHovered={hovered} icons={ReportUtils.getIconsForParticipants(oldestFourEmails, this.props.personalDetails)} /> )} diff --git a/src/pages/home/report/ReportActionItemThread.js b/src/pages/home/report/ReportActionItemThread.js index 9292c4b01c37..6f0243c61416 100644 --- a/src/pages/home/report/ReportActionItemThread.js +++ b/src/pages/home/report/ReportActionItemThread.js @@ -24,6 +24,9 @@ const propTypes = { /** ID of child thread report */ childReportID: PropTypes.string.isRequired, + /** Is hovered */ + isHovered: PropTypes.bool.isRequired, + /** localization props */ ...withLocalizePropTypes, }; @@ -38,10 +41,12 @@ const ReportActionItemThread = (props) => ( > icon.name)} + isHovered={props.isHovered} + isInReportView /> Date: Sat, 13 May 2023 00:50:58 +0500 Subject: [PATCH 126/329] fix: add a timeout on BaseGenericPressable for disabled cursor style --- .../GenericPressable/BaseGenericPressable.js | 99 ++++++++++++------- .../Pressable/PressableWithFeedback.js | 8 +- 2 files changed, 67 insertions(+), 40 deletions(-) diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.js b/src/components/Pressable/GenericPressable/BaseGenericPressable.js index 80664f2d3521..efa1ba1468f5 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.js +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.js @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useMemo, forwardRef} from 'react'; +import React, {useCallback, useEffect, useState, useMemo, forwardRef} from 'react'; import {Pressable} from 'react-native'; import _ from 'underscore'; import Accessibility from '../../../libs/Accessibility'; @@ -63,45 +63,70 @@ const GenericPressable = forwardRef((props, ref) => { return props.disabled || shouldBeDisabledByScreenReader; }, [isScreenReaderActive, enableInScreenReaderStates, props.disabled]); - const onLongPressHandler = useCallback((event) => { - if (isDisabled) { - return; - } - if (!onLongPress) { - return; - } - if (shouldUseHapticsOnLongPress) { - HapticFeedback.longPress(); - } - if (ref && ref.current) { - ref.current.blur(); - } - onLongPress(event); - - Accessibility.moveAccessibilityFocus(nextFocusRef); - }, [shouldUseHapticsOnLongPress, onLongPress, nextFocusRef, ref, isDisabled]); + const [shouldUseDisabledCursor, setShouldUseDisabledCursor] = useState(isDisabled); + + const onLongPressHandler = useCallback( + (event) => { + if (isDisabled) { + return; + } + if (!onLongPress) { + return; + } + if (shouldUseHapticsOnLongPress) { + HapticFeedback.longPress(); + } + if (ref && ref.current) { + ref.current.blur(); + } + onLongPress(event); + + Accessibility.moveAccessibilityFocus(nextFocusRef); + }, + [shouldUseHapticsOnLongPress, onLongPress, nextFocusRef, ref, isDisabled], + ); - const onPressHandler = useCallback((event) => { - if (isDisabled) { - return; - } - if (shouldUseHapticsOnPress) { - HapticFeedback.press(); - } - if (ref && ref.current) { - ref.current.blur(); - } - onPress(event); + const onPressHandler = useCallback( + (event) => { + if (isDisabled) { + return; + } + if (shouldUseHapticsOnPress) { + HapticFeedback.press(); + } + if (ref && ref.current) { + ref.current.blur(); + } + onPress(event); + + Accessibility.moveAccessibilityFocus(nextFocusRef); + }, + [shouldUseHapticsOnPress, onPress, nextFocusRef, ref, isDisabled], + ); - Accessibility.moveAccessibilityFocus(nextFocusRef); - }, [shouldUseHapticsOnPress, onPress, nextFocusRef, ref, isDisabled]); + const onKeyPressHandler = useCallback( + (event) => { + if (event.key !== 'Enter') { + return; + } + onPressHandler(event); + }, + [onPressHandler], + ); - const onKeyPressHandler = useCallback((event) => { - if (event.key !== 'Enter') { - return; + useEffect(() => { + let timer = null; + if (!isDisabled) { + setShouldUseDisabledCursor(false); + } else { + timer = setTimeout(() => setShouldUseDisabledCursor(true), 1000); } - onPressHandler(event); - }, [onPressHandler]); + + return () => { + if (!timer) return; + clearTimeout(timer); + }; + }, [isDisabled]); useEffect(() => { if (!keyboardShortcut) { @@ -122,7 +147,7 @@ const GenericPressable = forwardRef((props, ref) => { onPressIn={!isDisabled ? onPressIn : undefined} onPressOut={!isDisabled ? onPressOut : undefined} style={(state) => [ - getCursorStyle(isDisabled, [props.accessibilityRole, props.role].includes('text')), + getCursorStyle(shouldUseDisabledCursor, [props.accessibilityRole, props.role].includes('text')), StyleUtils.parseStyleFromFunction(props.style, state), isScreenReaderActive && StyleUtils.parseStyleFromFunction(props.screenReaderActiveStyle, state), state.focused && StyleUtils.parseStyleFromFunction(props.focusStyle, state), diff --git a/src/components/Pressable/PressableWithFeedback.js b/src/components/Pressable/PressableWithFeedback.js index d113798e9c6e..e2d227c80472 100644 --- a/src/components/Pressable/PressableWithFeedback.js +++ b/src/components/Pressable/PressableWithFeedback.js @@ -46,9 +46,11 @@ const PressableWithFeedback = forwardRef((props, ref) => { setDisabled(props.disabled); return; } - onPress.then(() => { - setDisabled(props.disabled); - }); + onPress + .then(() => { + setDisabled(props.disabled); + }) + .catch(() => setDisabled(props.disabled)); }); }} > From 1833a6c0b0580f4a1cd5bf5e7d00165e2617f7cb Mon Sep 17 00:00:00 2001 From: Georgia Monahan Date: Fri, 12 May 2023 23:06:46 +0100 Subject: [PATCH 127/329] Cap number of replies to '99+' --- src/languages/en.js | 2 +- .../home/report/ReportActionItemThread.js | 69 ++++++++++--------- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index 22ac07e014f6..045eadca7a59 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -1311,7 +1311,7 @@ export default { deletedMessage: '[Deleted message]', }, threads: { - lastReply: 'Last Reply', + lastReply: 'Last reply', replies: 'Replies', reply: 'Reply', }, diff --git a/src/pages/home/report/ReportActionItemThread.js b/src/pages/home/report/ReportActionItemThread.js index 6f0243c61416..dddc5ae5a31f 100644 --- a/src/pages/home/report/ReportActionItemThread.js +++ b/src/pages/home/report/ReportActionItemThread.js @@ -31,39 +31,44 @@ const propTypes = { ...withLocalizePropTypes, }; -const ReportActionItemThread = (props) => ( - - { - Report.openReport(props.childReportID); - Navigation.navigate(ROUTES.getReportRoute(props.childReportID)); - }} - > - - icon.name)} - isHovered={props.isHovered} - isInReportView - /> - - - {`${props.numberOfReplies} ${props.numberOfReplies === 1 ? props.translate('threads.reply') : props.translate('threads.replies')}`} - - {`${props.translate('threads.lastReply')} ${props.datetimeToCalendarTime(props.mostRecentReply)}`} +const ReportActionItemThread = (props) => { + const numberReplies = props.numberOfReplies > 99 ? '99+' : `${props.numberOfReplies}`; + const replyText = props.numberOfReplies === 1 ? props.translate('threads.reply') : props.translate('threads.replies'); + + return ( + + { + Report.openReport(props.childReportID); + Navigation.navigate(ROUTES.getReportRoute(props.childReportID)); + }} + > + + icon.name)} + isHovered={props.isHovered} + isInReportView + /> + + + {`${numberReplies} ${replyText}`} + + {`${props.translate('threads.lastReply')} ${props.datetimeToCalendarTime(props.mostRecentReply)}`} + - - - -); + + + ); +}; ReportActionItemThread.propTypes = propTypes; ReportActionItemThread.displayName = 'ReportActionItemThread'; From 61b8392d58cfc37b69e1ae0a0961a7547096d49c Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Fri, 12 May 2023 16:11:27 -0600 Subject: [PATCH 128/329] Update getChatRoomSubtitle to accept a parentReport --- src/libs/ReportUtils.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 7b0fd560ffe1..c965125d7f0b 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -463,7 +463,7 @@ function isThreadFirstChat(reportAction, reportID) { * @param {Object} report * @returns {String} */ -function getChatRoomSubtitle(report) { +function getChatRoomSubtitle(report, parentReport) { if (isThread(report)) { if (!getChatType(report)) { return ''; @@ -471,7 +471,15 @@ function getChatRoomSubtitle(report) { // If thread is not from a DM or group chat, the subtitle will follow the pattern 'Workspace Name • #roomName' const workspaceName = getPolicyName(report); - const roomName = isChatRoom(report) ? lodashGet(report, 'displayName') : ''; + let roomName = ''; + if (isChatRoom(report)) { + if (parentReport) { + roomName = lodashGet(parentReport, 'displayName', ''); + } else { + roomName = lodashGet(report, 'displayName', ''); + } + } + return [workspaceName, roomName].join(' • '); } if (!isDefaultRoom(report) && !isUserCreatedPolicyRoom(report) && !isPolicyExpenseChat(report)) { From 2c6cf8c39e864abce3a96e8424f4b1e78655e1a4 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Fri, 12 May 2023 16:11:54 -0600 Subject: [PATCH 129/329] Remove duplicate check and pass parentReport --- src/pages/home/HeaderView.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 8ae4a56d4093..a50252a8bb61 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -75,9 +75,9 @@ const HeaderView = (props) => { const isChatRoom = ReportUtils.isChatRoom(props.report); const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(props.report); const isTaskReport = ReportUtils.isTaskReport(props.report); - const reportHeaderData = isTaskReport && !_.isEmpty(props.parentReport) && (!isThread || isTaskReport) ? props.parentReport : props.report; + const reportHeaderData = (isTaskReport || !isThread) && !_.isEmpty(props.parentReport) ? props.parentReport : props.report; const title = ReportUtils.getReportName(reportHeaderData); - const subtitle = ReportUtils.getChatRoomSubtitle(reportHeaderData); + const subtitle = ReportUtils.getChatRoomSubtitle(reportHeaderData, props.parentReport); const isConcierge = participants.length === 1 && _.contains(participants, CONST.EMAIL.CONCIERGE); const isAutomatedExpensifyAccount = participants.length === 1 && ReportUtils.hasAutomatedExpensifyEmails(participants); const guideCalendarLink = lodashGet(props.account, 'guideCalendarLink'); From d09e6538e1d848aa0c3e184e4b6690d1c892cfd5 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Fri, 12 May 2023 16:12:12 -0600 Subject: [PATCH 130/329] Add documentation --- src/libs/ReportUtils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index c965125d7f0b..da418665d0b0 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -461,6 +461,7 @@ function isThreadFirstChat(reportAction, reportID) { /** * Get either the policyName or domainName the chat is tied to * @param {Object} report + * @param {Object} parentReport * @returns {String} */ function getChatRoomSubtitle(report, parentReport) { From 89ceb3035c1d1571b7c9003894013a156c811cab Mon Sep 17 00:00:00 2001 From: Sujit Kumar <60378235+therealsujitk@users.noreply.github.com> Date: Sat, 13 May 2023 03:52:29 +0530 Subject: [PATCH 131/329] Add unit test cases to check for precision loss --- tests/unit/CurrencyUtilsTest.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/unit/CurrencyUtilsTest.js b/tests/unit/CurrencyUtilsTest.js index acfc81572775..2430be522815 100644 --- a/tests/unit/CurrencyUtilsTest.js +++ b/tests/unit/CurrencyUtilsTest.js @@ -89,12 +89,16 @@ describe('CurrencyUtils', () => { test.each([ [CONST.CURRENCY.USD, 25, 2500], [CONST.CURRENCY.USD, 25.5, 2550], + [CONST.CURRENCY.USD, 2500, 250000], [CONST.CURRENCY.USD, 80.6, 8060], [CONST.CURRENCY.USD, 80.9, 8090], [CONST.CURRENCY.USD, 80.99, 8099], ['JPY', 25, 25], - ['JPY', 2500, 2500], ['JPY', 25.5, 25], + ['JPY', 2500, 2500], + ['JPY', 80.6, 80], + ['JPY', 80.9, 80], + ['JPY', 80.99, 80], ])('Correctly converts %s to amount in smallest units', (currency, amount, expectedResult) => { expect(CurrencyUtils.convertToSmallestUnit(currency, amount)).toBe(expectedResult); }); From 836bef7a314145099faeb70027149942a0d970e4 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Fri, 12 May 2023 16:24:22 -0600 Subject: [PATCH 132/329] Remove old parameter --- src/components/AvatarWithDisplayName.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index 3be660ab09c5..18d746a1912e 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -45,7 +45,7 @@ const defaultProps = { const AvatarWithDisplayName = (props) => { const title = ReportUtils.getDisplayNameForParticipant(props.report.ownerEmail, true); - const subtitle = ReportUtils.getChatRoomSubtitle(props.report, props.policies); + const subtitle = ReportUtils.getChatRoomSubtitle(props.report); const isExpenseReport = ReportUtils.isExpenseReport(props.report); const icons = ReportUtils.getIcons(props.report, props.personalDetails, props.policies); const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForLogins([props.report.ownerEmail], props.personalDetails); From 423ec9a622090567b7976975b7e946cf73d649af Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Fri, 12 May 2023 16:28:24 -0600 Subject: [PATCH 133/329] pass parentreport to subtitle --- src/libs/ReportUtils.js | 2 +- src/libs/SidebarUtils.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index da418665d0b0..9ae224315a5b 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -464,7 +464,7 @@ function isThreadFirstChat(reportAction, reportID) { * @param {Object} parentReport * @returns {String} */ -function getChatRoomSubtitle(report, parentReport) { +function getChatRoomSubtitle(report, parentReport = null) { if (isThread(report)) { if (!getChatType(report)) { return ''; diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js index ae28a75a5225..0bcf7d83b505 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.js @@ -262,7 +262,7 @@ function getOptionData(reportID) { const parentReport = result.parentReportID ? chatReports[`${ONYXKEYS.COLLECTION.REPORT}${result.parentReportID}`] : null; const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat; - const subtitle = ReportUtils.getChatRoomSubtitle(report); + const subtitle = ReportUtils.getChatRoomSubtitle(report, parentReport); const login = Str.removeSMSDomain(lodashGet(personalDetail, 'login', '')); const formattedLogin = Str.isSMSLogin(login) ? LocalePhoneNumber.formatPhoneNumber(login) : login; From 0ede1f5594d1de8bfd11d6c8c00068d7e6ebb94c Mon Sep 17 00:00:00 2001 From: Georgia Monahan Date: Fri, 12 May 2023 23:56:32 +0100 Subject: [PATCH 134/329] Use 2px border for Small avatars --- src/styles/StyleUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/StyleUtils.js b/src/styles/StyleUtils.js index 1867b49cf49b..375a682fc031 100644 --- a/src/styles/StyleUtils.js +++ b/src/styles/StyleUtils.js @@ -115,7 +115,7 @@ function getAvatarBorderWidth(size) { [CONST.AVATAR_SIZE.SMALL_SUBSCRIPT]: 2, [CONST.AVATAR_SIZE.MID_SUBSCRIPT]: 2, [CONST.AVATAR_SIZE.SUBSCRIPT]: 2, - [CONST.AVATAR_SIZE.SMALL]: 3, + [CONST.AVATAR_SIZE.SMALL]: 2, [CONST.AVATAR_SIZE.SMALLER]: 2, [CONST.AVATAR_SIZE.LARGE]: 4, [CONST.AVATAR_SIZE.MEDIUM]: 3, From 902b30f60bb47aed4229b722db73b6122def4514 Mon Sep 17 00:00:00 2001 From: Georgia Monahan Date: Sat, 13 May 2023 00:07:29 +0100 Subject: [PATCH 135/329] Use shortened date time on small screens --- src/components/withLocalize.js | 13 ++++++++ src/languages/en.js | 3 ++ src/languages/es.js | 3 ++ src/libs/DateUtils.js | 32 +++++++++++++++++++ .../home/report/ReportActionItemThread.js | 11 +++++-- 5 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/components/withLocalize.js b/src/components/withLocalize.js index 3b67ff939b23..dc45ef31fe27 100755 --- a/src/components/withLocalize.js +++ b/src/components/withLocalize.js @@ -29,6 +29,9 @@ const withLocalizePropTypes = { /** Formats a datetime to local date and time string */ datetimeToCalendarTime: PropTypes.func.isRequired, + /** Formats a datetime to local date and time string, short version */ + datetimeToCalendarTimeShort: PropTypes.func.isRequired, + /** Returns a locally converted phone number for numbers from the same region * and an internationally converted phone number with the country code for numbers from other regions */ formatPhoneNumber: PropTypes.func.isRequired, @@ -73,6 +76,7 @@ class LocaleContextProvider extends React.Component { numberFormat: this.numberFormat.bind(this), datetimeToRelative: this.datetimeToRelative.bind(this), datetimeToCalendarTime: this.datetimeToCalendarTime.bind(this), + datetimeToCalendarTimeShort: this.datetimeToCalendarTimeShort.bind(this), formatPhoneNumber: this.formatPhoneNumber.bind(this), fromLocaleDigit: this.fromLocaleDigit.bind(this), toLocaleDigit: this.toLocaleDigit.bind(this), @@ -115,6 +119,15 @@ class LocaleContextProvider extends React.Component { return DateUtils.datetimeToCalendarTime(this.props.preferredLocale, datetime, includeTimezone, lodashGet(this.props, 'currentUserPersonalDetails.timezone.selected')); } + /** + * @param {String} datetime - ISO-formatted datetime string + * @param {Boolean} [includeTimezone] + * @returns {String} + */ + datetimeToCalendarTimeShort(datetime, includeTimezone) { + return DateUtils.datetimeToCalendarTimeShort(this.props.preferredLocale, datetime, includeTimezone, lodashGet(this.props, 'currentUserPersonalDetails.timezone.selected')); + } + /** * @param {String} phoneNumber * @returns {String} diff --git a/src/languages/en.js b/src/languages/en.js index 045eadca7a59..7494c4d67d44 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -85,6 +85,9 @@ export default { noResultsFound: 'No results found', timePrefix: "It's", conjunctionFor: 'for', + today: 'Today', + tomorrow: 'Tomorrow', + yesterday: 'Yesterday', todayAt: 'Today at', tomorrowAt: 'Tomorrow at', yesterdayAt: 'Yesterday at', diff --git a/src/languages/es.js b/src/languages/es.js index 003192a3c8cc..935aa4a398da 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -84,6 +84,9 @@ export default { noResultsFound: 'No se han encontrado resultados', timePrefix: 'Son las', conjunctionFor: 'para', + today: 'Hoy', + tomorrow: 'Mañana', + yesterday: 'Ayer', todayAt: 'Hoy a las', tomorrowAt: 'Mañana a las', yesterdayAt: 'Ayer a las', diff --git a/src/libs/DateUtils.js b/src/libs/DateUtils.js index 91609af7b173..d799832e9b82 100644 --- a/src/libs/DateUtils.js +++ b/src/libs/DateUtils.js @@ -87,6 +87,37 @@ function datetimeToCalendarTime(locale, datetime, includeTimeZone = false, curre }); } +/** + * Formats an ISO-formatted datetime string to local date and time string + * + * e.g. + * + * Jan 20 at 5:30 PM within the past year + * Jan 20, 2019 at 5:30 PM anything over 1 year ago + * + * @param {String} locale + * @param {String} datetime + * @param {String} [currentSelectedTimezone] + * + * @returns {String} + */ +function datetimeToCalendarTimeShort(locale, datetime, currentSelectedTimezone) { + const date = getLocalMomentFromDatetime(locale, datetime, currentSelectedTimezone); + + const today = Localize.translate(locale, 'common.today'); + const tomorrow = Localize.translate(locale, 'common.tomorrow'); + const yesterday = Localize.translate(locale, 'common.yesterday'); + + return moment(date).calendar({ + sameDay: `[${today}]`, + nextDay: `[${tomorrow}]`, + lastDay: `[${yesterday}]`, + nextWeek: `MMM D`, + lastWeek: `MMM D`, + sameElse: `MMM D, YYYY`, + }); +} + /** * Converts an ISO-formatted datetime string into a localized string representation * that's relative to current moment in time. @@ -191,6 +222,7 @@ function subtractMillisecondsFromDateTime(dateTime, milliseconds) { const DateUtils = { datetimeToRelative, datetimeToCalendarTime, + datetimeToCalendarTimeShort, startCurrentDateUpdater, getLocalMomentFromDatetime, getCurrentTimezone, diff --git a/src/pages/home/report/ReportActionItemThread.js b/src/pages/home/report/ReportActionItemThread.js index dddc5ae5a31f..3f9eba5d5cb3 100644 --- a/src/pages/home/report/ReportActionItemThread.js +++ b/src/pages/home/report/ReportActionItemThread.js @@ -5,11 +5,14 @@ import _ from 'underscore'; import styles from '../../../styles/styles'; import * as Report from '../../../libs/actions/Report'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; +import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; + import CONST from '../../../CONST'; import avatarPropTypes from '../../../components/avatarPropTypes'; import MultipleAvatars from '../../../components/MultipleAvatars'; import Navigation from '../../../libs/Navigation/Navigation'; import ROUTES from '../../../ROUTES'; +import compose from '../../../libs/compose'; const propTypes = { /** List of participant icons for the thread */ @@ -27,14 +30,16 @@ const propTypes = { /** Is hovered */ isHovered: PropTypes.bool.isRequired, - /** localization props */ ...withLocalizePropTypes, + ...windowDimensionsPropTypes, }; const ReportActionItemThread = (props) => { const numberReplies = props.numberOfReplies > 99 ? '99+' : `${props.numberOfReplies}`; const replyText = props.numberOfReplies === 1 ? props.translate('threads.reply') : props.translate('threads.replies'); + const timeStamp = props.isSmallScreenWidth ? props.datetimeToCalendarTimeShort(props.mostRecentReply) : props.datetimeToCalendarTime(props.mostRecentReply); + return ( { {`${props.translate('threads.lastReply')} ${props.datetimeToCalendarTime(props.mostRecentReply)}`} + >{`${props.translate('threads.lastReply')} ${timeStamp}`} @@ -73,4 +78,4 @@ const ReportActionItemThread = (props) => { ReportActionItemThread.propTypes = propTypes; ReportActionItemThread.displayName = 'ReportActionItemThread'; -export default withLocalize(ReportActionItemThread); +export default compose(withLocalize, withWindowDimensions)(ReportActionItemThread); From 18ec7be52ee54815fb207a45e697944e05959c63 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 13 May 2023 11:44:47 +0800 Subject: [PATCH 136/329] fix composer flickers when sending multiple lines of message --- src/pages/home/report/ReportActionCompose.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index dcfd92113891..459589073b12 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -876,6 +876,7 @@ class ReportActionCompose extends React.Component { const inputPlaceholder = this.getInputPlaceholder(); const shouldUseFocusedColor = !isBlockedFromConcierge && !this.props.disabled && (this.state.isFocused || this.state.isDraggingOver); const hasExceededMaxCommentLength = this.state.hasExceededMaxCommentLength; + const isFullComposerAvailable = this.state.isFullComposerAvailable && !_.isEmpty(this.state.value); return ( {this.props.isComposerFullSize && ( @@ -933,7 +934,7 @@ class ReportActionCompose extends React.Component { )} - {!this.props.isComposerFullSize && this.state.isFullComposerAvailable && ( + {!this.props.isComposerFullSize && isFullComposerAvailable && ( { @@ -1044,7 +1045,7 @@ class ReportActionCompose extends React.Component { isDisabled={isComposeDisabled || isBlockedFromConcierge || this.props.disabled} selection={this.state.selection} onSelectionChange={this.onSelectionChange} - isFullComposerAvailable={this.state.isFullComposerAvailable} + isFullComposerAvailable={isFullComposerAvailable} setIsFullComposerAvailable={this.setIsFullComposerAvailable} isComposerFullSize={this.props.isComposerFullSize} value={this.state.value} From 5e623a01c627f2dcc43b66e2c29a91d49999b191 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Fri, 12 May 2023 21:08:02 -0700 Subject: [PATCH 137/329] Merge in IR Dan changes --- src/pages/home/TaskHeaderView.js | 72 ++++++++++++++++++++- src/pages/signin/SignInPageLayout/Footer.js | 7 +- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/src/pages/home/TaskHeaderView.js b/src/pages/home/TaskHeaderView.js index 9f00fe5e4f9c..1d0f57703e4e 100644 --- a/src/pages/home/TaskHeaderView.js +++ b/src/pages/home/TaskHeaderView.js @@ -1,18 +1,80 @@ import React from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; import lodashGet from 'lodash/get'; +import PropTypes from 'prop-types'; +import compose from '../../libs/compose'; +import styles from '../../styles/styles'; +import * as TaskUtils from '../../libs/actions/Task'; import reportPropTypes from '../reportPropTypes'; import MenuItemWithTopDescription from '../../components/MenuItemWithTopDescription'; import Navigation from '../../libs/Navigation/Navigation'; import ROUTES from '../../ROUTES'; +import ONYXKEYS from '../../ONYXKEYS'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import Text from '../../components/Text'; +import Button from '../../components/Button'; +import Icon from '../../components/Icon'; +import * as Expensicons from '../../components/Icon/Expensicons'; +import themeColors from '../../styles/themes/default'; const propTypes = { /** The report currently being looked at */ report: reportPropTypes.isRequired, + + /** All of the personal details for everyone */ + personalDetails: PropTypes.objectOf( + PropTypes.shape({ + /** Display name of the person */ + displayName: PropTypes.string, + + /** Avatar URL of the person */ + avatar: PropTypes.string, + + /** Login of the person */ + login: PropTypes.string, + }), + ), + + ...withLocalizePropTypes, +}; + +const defaultProps = { + personalDetails: {}, }; function TaskHeaderView(props) { + console.log('TaskHeaderView', props.report); return ( <> + + {props.report.assignee ? ( + {props.personalDetails[props.report.assignee] ? props.personalDetails[props.report.assignee].displayName : ''} + ) : ( + Navigation.navigate(ROUTES.getTaskReportAssigneeRoute(props.report.reportID))} + /> + )} + {props.report.stateNum === 2 && props.report.statusNum === 3 ? ( + + + {props.translate('task.messages.completed')} + + ) : ( +