From 3a905c7f3629610b08162c23b65cdd4ee5bcac61 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 17 Apr 2024 20:03:58 +0800 Subject: [PATCH 01/20] remove underscore and unused file --- src/ROUTES.ts | 5 - src/SCREENS.ts | 1 - src/components/Modal/modalPropTypes.js | 89 --------- .../OptionsSelector/BaseOptionsSelector.js | 35 ++-- src/components/Popover/popoverPropTypes.js | 48 ----- src/components/menuItemPropTypes.js | 179 ----------------- .../ModalStackNavigators/index.tsx | 1 - src/libs/Navigation/linkingConfig/config.ts | 1 - src/libs/Navigation/types.ts | 5 +- src/pages/EditRequestPage.js | 189 ------------------ ...yForRefactorRequestParticipantsSelector.js | 34 ++-- .../step/IOURequestStepRoutePropTypes.js | 29 --- src/pages/reportPropTypes.js | 79 -------- 13 files changed, 37 insertions(+), 658 deletions(-) delete mode 100644 src/components/Modal/modalPropTypes.js delete mode 100644 src/components/Popover/popoverPropTypes.js delete mode 100644 src/components/menuItemPropTypes.js delete mode 100644 src/pages/EditRequestPage.js delete mode 100644 src/pages/iou/request/step/IOURequestStepRoutePropTypes.js delete mode 100644 src/pages/reportPropTypes.js diff --git a/src/ROUTES.ts b/src/ROUTES.ts index ec2bf11957e1..11988af00d5b 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -202,11 +202,6 @@ const ROUTES = { route: 'r/:reportID/avatar', getRoute: (reportID: string) => `r/${reportID}/avatar` as const, }, - EDIT_REQUEST: { - route: 'r/:threadReportID/edit/:field/:tagIndex?', - getRoute: (threadReportID: string, field: ValueOf, tagIndex?: number) => - `r/${threadReportID}/edit/${field}${typeof tagIndex === 'number' ? `/${tagIndex}` : ''}` as const, - }, EDIT_CURRENCY_REQUEST: { route: 'r/:threadReportID/edit/currency', getRoute: (threadReportID: string, currency: string, backTo: string) => `r/${threadReportID}/edit/currency?currency=${currency}&backTo=${backTo}` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 96372d5bbabb..6fbaf047e544 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -265,7 +265,6 @@ const SCREENS = { }, EDIT_REQUEST: { - ROOT: 'EditRequest_Root', CURRENCY: 'EditRequest_Currency', REPORT_FIELD: 'EditRequest_ReportField', }, diff --git a/src/components/Modal/modalPropTypes.js b/src/components/Modal/modalPropTypes.js deleted file mode 100644 index 84e610b694e4..000000000000 --- a/src/components/Modal/modalPropTypes.js +++ /dev/null @@ -1,89 +0,0 @@ -import PropTypes from 'prop-types'; -import _ from 'underscore'; -import {windowDimensionsPropTypes} from '@components/withWindowDimensions'; -import stylePropTypes from '@styles/stylePropTypes'; -import CONST from '@src/CONST'; - -const propTypes = { - /** Decides whether the modal should cover fullscreen. FullScreen modal has backdrop */ - fullscreen: PropTypes.bool, - - /** Should we close modal on outside click */ - shouldCloseOnOutsideClick: PropTypes.bool, - - /** Should we announce the Modal visibility changes? */ - shouldSetModalVisibility: PropTypes.bool, - - /** Callback method fired when the user requests to close the modal */ - onClose: PropTypes.func.isRequired, - - /** State that determines whether to display the modal or not */ - isVisible: PropTypes.bool.isRequired, - - /** Modal contents */ - children: PropTypes.node.isRequired, - - /** Callback method fired when the user requests to submit the modal content. */ - onSubmit: PropTypes.func, - - /** Callback method fired when the modal is hidden */ - onModalHide: PropTypes.func, - - /** Callback method fired when the modal is shown */ - onModalShow: PropTypes.func, - - /** Style of modal to display */ - type: PropTypes.oneOf(_.values(CONST.MODAL.MODAL_TYPE)), - - /** A react-native-animatable animation definition for the modal display animation. */ - animationIn: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), - - /** A react-native-animatable animation definition for the modal hide animation. */ - animationOut: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), - - /** The anchor position of a popover modal. Has no effect on other modal types. */ - popoverAnchorPosition: PropTypes.shape({ - top: PropTypes.number, - right: PropTypes.number, - bottom: PropTypes.number, - left: PropTypes.number, - }), - - /** Modal container styles */ - innerContainerStyle: stylePropTypes, - - /** Whether the modal should go under the system statusbar */ - statusBarTranslucent: PropTypes.bool, - - /** Whether the modal should avoid the keyboard */ - avoidKeyboard: PropTypes.bool, - - /** - * Whether the modal should hide its content while animating. On iOS, set to true - * if `useNativeDriver` is also true, to avoid flashes in the UI. - * - * See: https://github.com/react-native-modal/react-native-modal/pull/116 - * */ - hideModalContentWhileAnimating: PropTypes.bool, - - ...windowDimensionsPropTypes, -}; - -const defaultProps = { - fullscreen: true, - shouldCloseOnOutsideClick: false, - shouldSetModalVisibility: true, - onSubmit: null, - type: '', - onModalHide: () => {}, - onModalShow: () => {}, - animationIn: null, - animationOut: null, - popoverAnchorPosition: {}, - innerContainerStyle: {}, - statusBarTranslucent: true, - avoidKeyboard: false, - hideModalContentWhileAnimating: false, -}; - -export {propTypes, defaultProps}; diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index cc73a6fc8fd7..13ac5160b30b 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -1,8 +1,9 @@ +import lodashDebounce from 'lodash/debounce'; import lodashGet from 'lodash/get'; +import lodashIsEqual from 'lodash/isEqual'; import PropTypes from 'prop-types'; import React, {Component} from 'react'; import {View} from 'react-native'; -import _ from 'underscore'; import ArrowKeyFocusManager from '@components/ArrowKeyFocusManager'; import Button from '@components/Button'; import FixedFooter from '@components/FixedFooter'; @@ -77,9 +78,9 @@ class BaseOptionsSelector extends Component { this.calculateAllVisibleOptionsCount = this.calculateAllVisibleOptionsCount.bind(this); this.handleFocusIn = this.handleFocusIn.bind(this); this.handleFocusOut = this.handleFocusOut.bind(this); - this.debouncedUpdateSearchValue = _.debounce(this.updateSearchValue, CONST.TIMING.SEARCH_OPTION_LIST_DEBOUNCE_TIME); + this.debouncedUpdateSearchValue = lodashDebounce(this.updateSearchValue, CONST.TIMING.SEARCH_OPTION_LIST_DEBOUNCE_TIME); this.relatedTarget = null; - this.accessibilityRoles = _.values(CONST.ROLE); + this.accessibilityRoles = Object.values(CONST.ROLE); this.isWebOrDesktop = [CONST.PLATFORM.DESKTOP, CONST.PLATFORM.WEB].includes(getPlatform()); const allOptions = this.flattenSections(); @@ -155,7 +156,7 @@ class BaseOptionsSelector extends Component { this.focusedOption = this.state.allOptions[this.state.focusedIndex]; } - if (_.isEqual(this.props.sections, prevProps.sections)) { + if (lodashIsEqual(this.props.sections, prevProps.sections)) { return; } @@ -171,14 +172,14 @@ class BaseOptionsSelector extends Component { } const newFocusedIndex = this.props.selectedOptions.length; const isNewFocusedIndex = newFocusedIndex !== this.state.focusedIndex; - const prevFocusedOption = _.find(newOptions, (option) => this.focusedOption && option.keyForList === this.focusedOption.keyForList); - const prevFocusedOptionIndex = prevFocusedOption ? _.findIndex(newOptions, (option) => this.focusedOption && option.keyForList === this.focusedOption.keyForList) : undefined; + const prevFocusedOption = newOptions.find((option) => this.focusedOption && option.keyForList === this.focusedOption.keyForList); + const prevFocusedOptionIndex = prevFocusedOption ? newOptions.findIndex((option) => this.focusedOption && option.keyForList === this.focusedOption.keyForList) : undefined; // eslint-disable-next-line react/no-did-update-set-state this.setState( { sections: newSections, allOptions: newOptions, - focusedIndex: prevFocusedOptionIndex || (_.isNumber(this.props.focusedIndex) ? this.props.focusedIndex : newFocusedIndex), + focusedIndex: prevFocusedOptionIndex || (typeof this.props.focusedIndex === 'number' ? this.props.focusedIndex : newFocusedIndex), }, () => { // If we just toggled an option on a multi-selection page or cleared the search input, scroll to top @@ -230,11 +231,11 @@ class BaseOptionsSelector extends Component { } else { defaultIndex = this.props.selectedOptions.length; } - if (_.isUndefined(this.props.initiallyFocusedOptionKey)) { + if (typeof this.props.initiallyFocusedOptionKey === 'undefined') { return defaultIndex; } - const indexOfInitiallyFocusedOption = _.findIndex(allOptions, (option) => option.keyForList === this.props.initiallyFocusedOptionKey); + const indexOfInitiallyFocusedOption = allOptions.findIndex((option) => option.keyForList === this.props.initiallyFocusedOptionKey); return indexOfInitiallyFocusedOption; } @@ -245,8 +246,8 @@ class BaseOptionsSelector extends Component { * @returns {Objects[]} */ sliceSections() { - return _.map(this.props.sections, (section) => { - if (_.isEmpty(section.data)) { + return this.props.sections.map((section) => { + if (section.data.length === 0) { return section; } @@ -266,7 +267,7 @@ class BaseOptionsSelector extends Component { calculateAllVisibleOptionsCount() { let count = 0; - _.forEach(this.state.sections, (section) => { + this.state.sections.forEach((section) => { count += lodashGet(section, 'data.length', 0); }); @@ -347,7 +348,7 @@ class BaseOptionsSelector extends Component { selectFocusedOption(e) { const focusedItemKey = lodashGet(e, ['target', 'attributes', 'id', 'value']); - const focusedOption = focusedItemKey ? _.find(this.state.allOptions, (option) => option.keyForList === focusedItemKey) : this.state.allOptions[this.state.focusedIndex]; + const focusedOption = focusedItemKey ? this.state.allOptions.find((option) => option.keyForList === focusedItemKey) : this.state.allOptions[this.state.focusedIndex]; if (!focusedOption || !this.props.isFocused) { return; @@ -393,8 +394,8 @@ class BaseOptionsSelector extends Component { const allOptions = []; this.disabledOptionsIndexes = []; let index = 0; - _.each(this.props.sections, (section, sectionIndex) => { - _.each(section.data, (option, optionIndex) => { + this.props.sections.forEach((section, sectionIndex) => { + section.data.forEach((option, optionIndex) => { allOptions.push({ ...option, sectionIndex, @@ -496,8 +497,8 @@ class BaseOptionsSelector extends Component { render() { const shouldShowShowMoreButton = this.state.allOptions.length > CONST.MAX_OPTIONS_SELECTOR_PAGE_LENGTH * this.state.paginationPage; const shouldShowFooter = - !this.props.isReadOnly && (this.props.shouldShowConfirmButton || this.props.footerContent) && !(this.props.canSelectMultipleOptions && _.isEmpty(this.props.selectedOptions)); - const defaultConfirmButtonText = _.isUndefined(this.props.confirmButtonText) ? this.props.translate('common.confirm') : this.props.confirmButtonText; + !this.props.isReadOnly && (this.props.shouldShowConfirmButton || this.props.footerContent) && !(this.props.canSelectMultipleOptions && this.props.selectedOptions.length === 0); + const defaultConfirmButtonText = typeof this.props.confirmButtonText === 'undefined' ? this.props.translate('common.confirm') : this.props.confirmButtonText; const shouldShowDefaultConfirmButton = !this.props.footerContent && defaultConfirmButtonText; const safeAreaPaddingBottomStyle = shouldShowFooter ? undefined : this.props.safeAreaPaddingBottomStyle; const listContainerStyles = this.props.listContainerStyles || [this.props.themeStyles.flex1]; diff --git a/src/components/Popover/popoverPropTypes.js b/src/components/Popover/popoverPropTypes.js deleted file mode 100644 index c758c4e6d311..000000000000 --- a/src/components/Popover/popoverPropTypes.js +++ /dev/null @@ -1,48 +0,0 @@ -import PropTypes from 'prop-types'; -import _ from 'underscore'; -import {defaultProps as defaultModalProps, propTypes as modalPropTypes} from '@components/Modal/modalPropTypes'; -import refPropTypes from '@components/refPropTypes'; -import CONST from '@src/CONST'; - -const propTypes = { - ..._.omit(modalPropTypes, ['type', 'popoverAnchorPosition']), - - /** The anchor position of the popover */ - anchorPosition: PropTypes.shape({ - top: PropTypes.number, - right: PropTypes.number, - bottom: PropTypes.number, - left: PropTypes.number, - }), - - /** The anchor ref of the popover */ - anchorRef: refPropTypes, - - /** A react-native-animatable animation timing for the modal display animation. */ - animationInTiming: PropTypes.number, - - /** Whether disable the animations */ - disableAnimation: PropTypes.bool, - - /** The ref of the popover */ - withoutOverlayRef: refPropTypes, - - /** Whether we want to show the popover on the right side of the screen */ - fromSidebarMediumScreen: PropTypes.bool, -}; - -const defaultProps = { - ..._.omit(defaultModalProps, ['type', 'popoverAnchorPosition']), - - animationIn: 'fadeIn', - animationOut: 'fadeOut', - animationInTiming: CONST.ANIMATED_TRANSITION, - - // Anchor position is optional only because it is not relevant on mobile - anchorPosition: {}, - anchorRef: () => {}, - disableAnimation: true, - withoutOverlayRef: () => {}, -}; - -export {propTypes, defaultProps}; diff --git a/src/components/menuItemPropTypes.js b/src/components/menuItemPropTypes.js deleted file mode 100644 index 80ae1edd5176..000000000000 --- a/src/components/menuItemPropTypes.js +++ /dev/null @@ -1,179 +0,0 @@ -import PropTypes from 'prop-types'; -import _ from 'underscore'; -import stylePropTypes from '@styles/stylePropTypes'; -import CONST from '@src/CONST'; -import avatarPropTypes from './avatarPropTypes'; -import sourcePropTypes from './Image/sourcePropTypes'; -import refPropTypes from './refPropTypes'; - -const propTypes = { - /** Text to be shown as badge near the right end. */ - badgeText: PropTypes.string, - - /** Any additional styles to apply */ - // eslint-disable-next-line react/forbid-prop-types - wrapperStyle: stylePropTypes, - - /** Used to apply offline styles to child text components */ - style: stylePropTypes, - - /** Used to apply styles specifically to the title */ - titleStyle: stylePropTypes, - - /** Function to fire when component is pressed */ - onPress: PropTypes.func, - - /** Icon to display on the left side of component */ - icon: PropTypes.oneOfType([PropTypes.string, sourcePropTypes, PropTypes.arrayOf(avatarPropTypes)]), - - /** Secondary icon to display on the left side of component, right of the icon */ - secondaryIcon: sourcePropTypes, - - /** Icon Width */ - iconWidth: PropTypes.number, - - /** Icon Height */ - iconHeight: PropTypes.number, - - /** Text to display for the item */ - title: PropTypes.string, - - /** Text that appears above the title */ - label: PropTypes.string, - - /** Boolean whether to display the title right icon */ - shouldShowTitleIcon: PropTypes.bool, - - /** Icon to display at right side of title */ - titleIcon: sourcePropTypes, - - /** Boolean whether to display the right icon */ - shouldShowRightIcon: PropTypes.bool, - - /** Should we make this selectable with a checkbox */ - shouldShowSelectedState: PropTypes.bool, - - /** Should the title show with normal font weight (not bold) */ - shouldShowBasicTitle: PropTypes.bool, - - /** Should the description be shown above the title (instead of the other way around) */ - shouldShowDescriptionOnTop: PropTypes.bool, - - /** Whether this item is selected */ - isSelected: PropTypes.bool, - - /** A boolean flag that gives the icon a green fill if true */ - success: PropTypes.bool, - - /** Overrides the icon for shouldShowRightIcon */ - iconRight: sourcePropTypes, - - /** A description text to show under the title */ - description: PropTypes.string, - - /** Any additional styles to pass to the icon container. */ - iconStyles: PropTypes.arrayOf(PropTypes.object), - - /** The fill color to pass into the icon. */ - iconFill: PropTypes.string, - - /** The fill color to pass into the secondary icon. */ - secondaryIconFill: PropTypes.string, - - /** Whether item is focused or active */ - focused: PropTypes.bool, - - /** Should we disable this menu item? */ - disabled: PropTypes.bool, - - /** A right-aligned subtitle for this menu option */ - subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - - /** Flag to choose between avatar image or an icon */ - iconType: PropTypes.oneOf([CONST.ICON_TYPE_AVATAR, CONST.ICON_TYPE_ICON, CONST.ICON_TYPE_WORKSPACE]), - - /** Whether the menu item should be interactive at all */ - interactive: PropTypes.bool, - - /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */ - fallbackIcon: PropTypes.oneOfType([PropTypes.string, sourcePropTypes]), - - /** Avatars to show on the right of the menu item */ - floatRightAvatars: PropTypes.arrayOf(avatarPropTypes), - - /** The type of brick road indicator to show. */ - brickRoadIndicator: PropTypes.oneOf([CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR, CONST.BRICK_ROAD_INDICATOR_STATUS.INFO, '']), - - /** Prop to identify if we should load avatars vertically instead of diagonally */ - shouldStackHorizontally: PropTypes.bool, - - /** Prop to represent the size of the float right avatar images to be shown */ - floatRightAvatarSize: PropTypes.oneOf(_.values(CONST.AVATAR_SIZE)), - - /** Prop to represent the size of the avatar images to be shown */ - avatarSize: PropTypes.oneOf(_.values(CONST.AVATAR_SIZE)), - - /** The function that should be called when this component is LongPressed or right-clicked. */ - onSecondaryInteraction: PropTypes.func, - - /** Flag to indicate whether or not text selection should be disabled from long-pressing the menu item. */ - shouldBlockSelection: PropTypes.bool, - - /** The ref to the menu item */ - forwardedRef: refPropTypes, - - /** Any adjustments to style when menu item is hovered or pressed */ - hoverAndPressStyle: PropTypes.arrayOf(PropTypes.object), - - /** Text to display under the main item */ - furtherDetails: PropTypes.string, - - /** An icon to display under the main item */ - furtherDetailsIcon: PropTypes.oneOfType([PropTypes.elementType, PropTypes.string]), - - /** The action accept for anonymous user or not */ - isAnonymousAction: PropTypes.bool, - - /** Whether we should use small avatar subscript sizing the for menu item */ - isSmallAvatarSubscriptMenu: PropTypes.bool, - - /** The max number of lines the title text should occupy before ellipses are added */ - numberOfLines: PropTypes.number, - - /** Should we grey out the menu item when it is disabled? */ - shouldGreyOutWhenDisabled: PropTypes.bool, - - /** Error to display below the title */ - error: PropTypes.string, - - /** Should render the content in HTML format */ - shouldRenderAsHTML: PropTypes.bool, - - /** Label to be displayed on the right */ - rightLabel: PropTypes.string, - - /** Component to be displayed on the right */ - rightComponent: PropTypes.node, - - /** Should render component on the right */ - shouldShowRightComponent: PropTypes.bool, - - /** Array of objects that map display names to their corresponding tooltip */ - titleWithTooltips: PropTypes.arrayOf(PropTypes.object), - - /** Should check anonymous user in onPress function */ - shouldCheckActionAllowedOnPress: PropTypes.bool, - - shouldPutLeftPaddingWhenNoIcon: PropTypes.bool, - - /** The menu item link or function to get the link */ - link: PropTypes.oneOfType(PropTypes.func, PropTypes.string), - - /** Icon should be displayed in its own color */ - displayInDefaultIconColor: PropTypes.bool, - - /** Is this menu item in the settings pane */ - isPaneMenu: PropTypes.bool, -}; - -export default propTypes; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 7380bf102331..b1b9449fa92b 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -299,7 +299,6 @@ const FlagCommentStackNavigator = createModalStackNavigator({ - [SCREENS.EDIT_REQUEST.ROOT]: () => require('../../../../pages/EditRequestPage').default as React.ComponentType, [SCREENS.EDIT_REQUEST.REPORT_FIELD]: () => require('../../../../pages/EditReportFieldPage').default as React.ComponentType, }); diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 6165ccb16fa3..ec8b1fcdc2b5 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -618,7 +618,6 @@ const config: LinkingOptions['config'] = { }, [SCREENS.RIGHT_MODAL.EDIT_REQUEST]: { screens: { - [SCREENS.EDIT_REQUEST.ROOT]: ROUTES.EDIT_REQUEST.route, [SCREENS.EDIT_REQUEST.REPORT_FIELD]: ROUTES.EDIT_REPORT_FIELD_REQUEST.route, }, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 8273278f971e..a8e95be68e6d 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -584,10 +584,7 @@ type FlagCommentNavigatorParamList = { }; type EditRequestNavigatorParamList = { - [SCREENS.EDIT_REQUEST.ROOT]: { - field: string; - threadReportID: string; - }; + [SCREENS.EDIT_REQUEST.REPORT_FIELD]: undefined; }; type SignInNavigatorParamList = { diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js deleted file mode 100644 index d3941dca044e..000000000000 --- a/src/pages/EditRequestPage.js +++ /dev/null @@ -1,189 +0,0 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo} from 'react'; -import {withOnyx} from 'react-native-onyx'; -import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import categoryPropTypes from '@components/categoryPropTypes'; -import ScreenWrapper from '@components/ScreenWrapper'; -import tagPropTypes from '@components/tagPropTypes'; -import transactionPropTypes from '@components/transactionPropTypes'; -import compose from '@libs/compose'; -import * as IOUUtils from '@libs/IOUUtils'; -import Navigation from '@libs/Navigation/Navigation'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; -import * as IOU from '@userActions/IOU'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import EditRequestReceiptPage from './EditRequestReceiptPage'; -import EditRequestTagPage from './EditRequestTagPage'; -import reportActionPropTypes from './home/report/reportActionPropTypes'; -import reportPropTypes from './reportPropTypes'; -import {policyPropTypes} from './workspace/withPolicy'; - -const propTypes = { - /** Route from navigation */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** Which field we are editing */ - field: PropTypes.string, - - /** reportID for the "transaction thread" */ - threadReportID: PropTypes.string, - - /** Indicates which tag list index was selected */ - tagIndex: PropTypes.string, - }), - }).isRequired, - - /** Onyx props */ - /** The report object for the thread report */ - report: reportPropTypes, - - /** The policy of the report */ - policy: policyPropTypes.policy, - - /** Collection of categories attached to a policy */ - policyCategories: PropTypes.objectOf(categoryPropTypes), - - /** Collection of tags attached to a policy */ - policyTags: tagPropTypes, - - /** The actions from the parent report */ - parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), - - /** Transaction that stores the request data */ - transaction: transactionPropTypes, -}; - -const defaultProps = { - report: {}, - policy: {}, - policyCategories: {}, - policyTags: {}, - parentReportActions: {}, - transaction: {}, -}; - -function EditRequestPage({report, route, policy, policyCategories, policyTags, parentReportActions, transaction}) { - const parentReportActionID = lodashGet(report, 'parentReportActionID', '0'); - const parentReportAction = lodashGet(parentReportActions, parentReportActionID, {}); - const {tag: transactionTag} = ReportUtils.getTransactionDetails(transaction); - - const fieldToEdit = lodashGet(route, ['params', 'field'], ''); - const tagListIndex = Number(lodashGet(route, ['params', 'tagIndex'], undefined)); - - const tag = TransactionUtils.getTag(transaction, tagListIndex); - const policyTagListName = PolicyUtils.getTagListName(policyTags, tagListIndex); - const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); - - // A flag for verifying that the current report is a sub-report of a workspace chat - const isPolicyExpenseChat = ReportUtils.isGroupPolicy(report); - - // A flag for showing the tags page - const shouldShowTags = useMemo(() => isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledTags(policyTagLists)), [isPolicyExpenseChat, policyTagLists, transactionTag]); - - // Decides whether to allow or disallow editing a money request - useEffect(() => { - // Do not dismiss the modal, when a current user can edit this property of the money request. - if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, fieldToEdit)) { - return; - } - - // Dismiss the modal when a current user cannot edit a money request. - Navigation.isNavigationReady().then(() => { - Navigation.dismissModal(); - }); - }, [parentReportAction, fieldToEdit]); - - const saveTag = useCallback( - ({tag: newTag}) => { - let updatedTag = newTag; - if (newTag === tag) { - // In case the same tag has been selected, reset the tag. - updatedTag = ''; - } - IOU.updateMoneyRequestTag( - transaction.transactionID, - report.reportID, - IOUUtils.insertTagIntoTransactionTagsString(transactionTag, updatedTag, tagListIndex), - policy, - policyTags, - policyCategories, - ); - Navigation.dismissModal(); - }, - [tag, transaction.transactionID, report.reportID, transactionTag, tagListIndex, policy, policyTags, policyCategories], - ); - - if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.TAG && shouldShowTags) { - return ( - - ); - } - - if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.RECEIPT) { - return ( - - ); - } - - return ( - - - - ); -} - -EditRequestPage.displayName = 'EditRequestPage'; -EditRequestPage.propTypes = propTypes; -EditRequestPage.defaultProps = defaultProps; -export default compose( - withOnyx({ - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`, - }, - }), - // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file - withOnyx({ - policy: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, - }, - policyCategories: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report ? report.policyID : '0'}`, - }, - policyTags: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, - }, - parentReportActions: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`, - canEvict: false, - }, - }), - // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file - withOnyx({ - transaction: { - key: ({report, parentReportActions}) => { - const parentReportActionID = lodashGet(report, 'parentReportActionID', '0'); - const parentReportAction = lodashGet(parentReportActions, parentReportActionID); - return `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(parentReportAction, 'originalMessage.IOUTransactionID', 0)}`; - }, - }, - }), -)(EditRequestPage); diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 87883972f84f..a91bf03c6ed4 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -1,8 +1,10 @@ import lodashGet from 'lodash/get'; +import lodashIsEqual from 'lodash/isEqual'; +import lodashPick from 'lodash/pick'; +import lodashReject from 'lodash/reject'; import PropTypes from 'prop-types'; import React, {memo, useCallback, useEffect, useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import BlockingView from '@components/BlockingViews/BlockingView'; import Button from '@components/Button'; import FormHelpMessage from '@components/FormHelpMessage'; @@ -51,13 +53,13 @@ const propTypes = { ), /** The type of IOU report, i.e. bill, request, send */ - iouType: PropTypes.oneOf(_.values(CONST.IOU.TYPE)).isRequired, + iouType: PropTypes.oneOf(Object.values(CONST.IOU.TYPE)).isRequired, /** The request type, ie. manual, scan, distance */ - iouRequestType: PropTypes.oneOf(_.values(CONST.IOU.REQUEST_TYPE)).isRequired, + iouRequestType: PropTypes.oneOf(Object.values(CONST.IOU.REQUEST_TYPE)).isRequired, /** The action of the IOU, i.e. create, split, move */ - action: PropTypes.oneOf(_.values(CONST.IOU.ACTION)), + action: PropTypes.oneOf(Object.values(CONST.IOU.ACTION)), }; const defaultProps = { @@ -141,21 +143,21 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF newSections.push({ title: translate('common.recents'), data: chatOptions.recentReports, - shouldShow: !_.isEmpty(chatOptions.recentReports), + shouldShow: chatOptions.recentReports.length > 0, }); if (![CONST.IOU.ACTION.CATEGORIZE, CONST.IOU.ACTION.SHARE].includes(action)) { newSections.push({ title: translate('common.contacts'), data: chatOptions.personalDetails, - shouldShow: !_.isEmpty(chatOptions.personalDetails), + shouldShow: chatOptions.personalDetails.length > 0, }); } if (chatOptions.userToInvite && !OptionsListUtils.isCurrentUser(chatOptions.userToInvite)) { newSections.push({ title: undefined, - data: _.map([chatOptions.userToInvite], (participant) => { + data: [chatOptions.userToInvite].map((participant) => { const isPolicyExpenseChat = lodashGet(participant, 'isPolicyExpenseChat', false); return isPolicyExpenseChat ? OptionsListUtils.getPolicyExpenseReportOption(participant) : OptionsListUtils.getParticipantsOption(participant, personalDetails); }), @@ -190,7 +192,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF (option) => { onParticipantsAdded([ { - ..._.pick(option, 'accountID', 'login', 'isPolicyExpenseChat', 'reportID', 'searchText'), + ...lodashPick(option, 'accountID', 'login', 'isPolicyExpenseChat', 'reportID', 'searchText'), selected: true, iouType, }, @@ -218,11 +220,11 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF return false; }; - const isOptionInList = _.some(participants, isOptionSelected); + const isOptionInList = participants.some(isOptionSelected); let newSelectedOptions; if (isOptionInList) { - newSelectedOptions = _.reject(participants, isOptionSelected); + newSelectedOptions = lodashReject(participants, isOptionSelected); } else { newSelectedOptions = [ ...participants, @@ -247,11 +249,11 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF const headerMessage = useMemo( () => OptionsListUtils.getHeaderMessage( - _.get(newChatOptions, 'personalDetails', []).length + _.get(newChatOptions, 'recentReports', []).length !== 0, + lodashGet(newChatOptions, 'personalDetails', []).length + lodashGet(newChatOptions, 'recentReports', []).length !== 0, Boolean(newChatOptions.userToInvite), debouncedSearchTerm.trim(), maxParticipantsReached, - _.some(participants, (participant) => participant.searchText.toLowerCase().includes(debouncedSearchTerm.trim().toLowerCase())), + participants.some((participant) => participant.searchText.toLowerCase().includes(debouncedSearchTerm.trim().toLowerCase())), ), [maxParticipantsReached, newChatOptions, participants, debouncedSearchTerm], ); @@ -259,7 +261,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF // Right now you can't split a request with a workspace and other additional participants // This is getting properly fixed in https://github.com/Expensify/App/issues/27508, but as a stop-gap to prevent // the app from crashing on native when you try to do this, we'll going to hide the button if you have a workspace and other participants - const hasPolicyExpenseChatParticipant = _.some(participants, (participant) => participant.isPolicyExpenseChat); + const hasPolicyExpenseChatParticipant = participants.some((participant) => participant.isPolicyExpenseChat); const shouldShowSplitBillErrorMessage = participants.length > 1 && hasPolicyExpenseChatParticipant; // canUseP2PDistanceRequests is true if the iouType is track expense, but we don't want to allow splitting distance with track expense yet @@ -377,7 +379,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF ); - const isAllSectionsEmpty = _.every(sections, (section) => section.data.length === 0); + const isAllSectionsEmpty = sections.every((section) => section.data.length === 0); if ([CONST.IOU.ACTION.CATEGORIZE, CONST.IOU.ACTION.SHARE].includes(action) && isAllSectionsEmpty && didScreenTransitionEnd && searchTerm.trim() === '') { return renderEmptyWorkspaceView(); } @@ -408,8 +410,8 @@ MoneyTemporaryForRefactorRequestParticipantsSelector.displayName = 'MoneyTempora export default memo( MoneyTemporaryForRefactorRequestParticipantsSelector, (prevProps, nextProps) => - _.isEqual(prevProps.participants, nextProps.participants) && + lodashIsEqual(prevProps.participants, nextProps.participants) && prevProps.iouRequestType === nextProps.iouRequestType && prevProps.iouType === nextProps.iouType && - _.isEqual(prevProps.betas, nextProps.betas), + lodashIsEqual(prevProps.betas, nextProps.betas), ); diff --git a/src/pages/iou/request/step/IOURequestStepRoutePropTypes.js b/src/pages/iou/request/step/IOURequestStepRoutePropTypes.js deleted file mode 100644 index 8b191fa0b58e..000000000000 --- a/src/pages/iou/request/step/IOURequestStepRoutePropTypes.js +++ /dev/null @@ -1,29 +0,0 @@ -import PropTypes from 'prop-types'; -import _ from 'underscore'; -import CONST from '@src/CONST'; - -export default PropTypes.shape({ - /** Route specific parameters used on this screen via route :iouType/new/category/:reportID? */ - params: PropTypes.shape({ - /** What action is being performed, ie. create, edit */ - action: PropTypes.oneOf(_.values(CONST.IOU.ACTION)), - - /** The type of IOU report, i.e. bill, request, send */ - iouType: PropTypes.oneOf(_.values(CONST.IOU.TYPE)).isRequired, - - /** The ID of the transaction being configured */ - transactionID: PropTypes.string.isRequired, - - /** The report ID of the IOU */ - reportID: PropTypes.string.isRequired, - - /** Index of the waypoint being edited */ - pageIndex: PropTypes.string, - - /** A path to go to when the user presses the back button */ - backTo: PropTypes.string, - - /** Indicates which tag list index was selected */ - tagIndex: PropTypes.string, - }), -}); diff --git a/src/pages/reportPropTypes.js b/src/pages/reportPropTypes.js deleted file mode 100644 index 7422bad8061f..000000000000 --- a/src/pages/reportPropTypes.js +++ /dev/null @@ -1,79 +0,0 @@ -import PropTypes from 'prop-types'; -import _ from 'underscore'; -import avatarPropTypes from '@components/avatarPropTypes'; -import CONST from '@src/CONST'; - -export default PropTypes.shape({ - /** The specific type of chat */ - chatType: PropTypes.oneOf(['', ..._.values(CONST.REPORT.CHAT_TYPE)]), - - /** List of icons for report participants */ - icons: PropTypes.arrayOf(avatarPropTypes), - - /** Whether the user is not an admin of policyExpenseChat chat */ - isOwnPolicyExpenseChat: PropTypes.bool, - - /** Indicates if the report is pinned to the LHN or not */ - isPinned: PropTypes.bool, - - /** Whether we're waiting on submitter to add a bank account */ - isWaitingOnBankAccount: PropTypes.bool, - - /** The accountID of the last message's actor */ - lastActorAccountID: PropTypes.number, - - /** The text of the last message on the report */ - lastMessageText: PropTypes.string, - - /** The time of the last message on the report */ - lastVisibleActionCreated: PropTypes.string, - - /** The time when user read the last message */ - lastReadTime: PropTypes.string, - - /** The current user's notification preference for this report */ - notificationPreference: PropTypes.oneOfType([ - // Some old reports have numbers for the notification preference - PropTypes.number, - PropTypes.string, - ]), - - /** The policy name to use for an archived report */ - oldPolicyName: PropTypes.string, - - /** The accountID of the report owner */ - ownerAccountID: PropTypes.number, - - /** List of accountIDs of participants of the report */ - participantAccountIDs: PropTypes.arrayOf(PropTypes.number), - - /** List of accountIDs of visible members of the report */ - visibleChatMemberAccountIDs: PropTypes.arrayOf(PropTypes.number), - - /** Linked policy's ID */ - policyID: PropTypes.string, - - /** Name of the report */ - reportName: PropTypes.string, - - /** ID of the report */ - reportID: PropTypes.string, - - /** The state that the report is currently in */ - stateNum: PropTypes.oneOf(_.values(CONST.REPORT.STATE_NUM)), - - /** The status of the current report */ - statusNum: PropTypes.oneOf(_.values(CONST.REPORT.STATUS_NUM)), - - /** Which user role is capable of posting messages on the report */ - writeCapability: PropTypes.oneOf(_.values(CONST.REPORT.WRITE_CAPABILITIES)), - - /** Field-specific pending states for offline UI status */ - pendingFields: PropTypes.objectOf(PropTypes.string), - - /** Custom fields attached to the report */ - reportFields: PropTypes.objectOf(PropTypes.string), - - /** ID of the transaction thread associated with the report, if any */ - transactionThreadReportID: PropTypes.string, -}); From 81f4716ef5a0a8fca88473962d6961962566bbcb Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 18 Apr 2024 11:13:10 +0800 Subject: [PATCH 02/20] remove unnecessary eslint disable --- src/components/FloatingActionButton.tsx | 2 -- src/libs/DateUtils.ts | 2 -- tests/ui/UnreadIndicatorsTest.tsx | 1 - 3 files changed, 5 deletions(-) diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx index ee61beda74ae..93ffa52bc80b 100644 --- a/src/components/FloatingActionButton.tsx +++ b/src/components/FloatingActionButton.tsx @@ -27,12 +27,10 @@ type AdapterProps = { const adapter = createAnimatedPropAdapter( (props: AdapterProps) => { - // eslint-disable-next-line rulesdir/prefer-underscore-method if (Object.keys(props).includes('fill')) { // eslint-disable-next-line no-param-reassign props.fill = {type: 0, payload: processColor(props.fill)}; } - // eslint-disable-next-line rulesdir/prefer-underscore-method if (Object.keys(props).includes('stroke')) { // eslint-disable-next-line no-param-reassign props.stroke = {type: 0, payload: processColor(props.stroke)}; diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts index 9b96bfa009dc..e0c414536c64 100644 --- a/src/libs/DateUtils.ts +++ b/src/libs/DateUtils.ts @@ -321,7 +321,6 @@ function getMonthNames(preferredLocale: Locale): string[] { end: new Date(fullYear, 11, 31), // December 31st of the current year }); - // eslint-disable-next-line rulesdir/prefer-underscore-method return monthsArray.map((monthDate) => format(monthDate, CONST.DATE.MONTH_FORMAT)); } @@ -337,7 +336,6 @@ function getDaysOfWeek(preferredLocale: Locale): string[] { const endOfCurrentWeek = endOfWeek(new Date(), {weekStartsOn}); const daysOfWeek = eachDayOfInterval({start: startOfCurrentWeek, end: endOfCurrentWeek}); - // eslint-disable-next-line rulesdir/prefer-underscore-method return daysOfWeek.map((date) => format(date, 'eeee')); } diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 2aeee2cc77bf..a78423f3838d 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -82,7 +82,6 @@ const createAddListenerMock = (): ListenerMock => { transitionEndListeners.push(callback); } return () => { - // eslint-disable-next-line rulesdir/prefer-underscore-method transitionEndListeners.filter((cb) => cb !== callback); }; }); From 84ab150cfb3b9c76189906dd9a1151a636de0adc Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 18 Apr 2024 11:20:45 +0800 Subject: [PATCH 03/20] remove unnecessary eslint disable --- src/libs/SidebarUtils.ts | 1 - src/libs/markAllPolicyReportsAsRead.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 9fc83d50f1e1..5c156add8e35 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1,4 +1,3 @@ -/* eslint-disable rulesdir/prefer-underscore-method */ import Str from 'expensify-common/lib/str'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; diff --git a/src/libs/markAllPolicyReportsAsRead.ts b/src/libs/markAllPolicyReportsAsRead.ts index c3c719b1132e..49001a851cf5 100644 --- a/src/libs/markAllPolicyReportsAsRead.ts +++ b/src/libs/markAllPolicyReportsAsRead.ts @@ -1,4 +1,3 @@ -// eslint-disable-next-line you-dont-need-lodash-underscore/each import Onyx from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Report} from '@src/types/onyx'; From 7c9704bf69368452af0dd68a8725a067bca8ea3e Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 18 Apr 2024 11:21:10 +0800 Subject: [PATCH 04/20] don't install underscore --- tests/unit/CIGitLogicTest.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/CIGitLogicTest.sh b/tests/unit/CIGitLogicTest.sh index 73fd01f5c0a0..ba26fcd45467 100755 --- a/tests/unit/CIGitLogicTest.sh +++ b/tests/unit/CIGitLogicTest.sh @@ -42,7 +42,6 @@ function init_git_server { setup_git_as_human npm init -y npm version --no-git-tag-version 1.0.0-0 - npm install underscore echo "node_modules/" >> .gitignore git add -A git commit -m "Initial commit" From ac5a4dd429e92d5617e2570b0191819c281127d5 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 18 Apr 2024 11:24:26 +0800 Subject: [PATCH 05/20] replace underscore with lodash --- src/components/transactionPropTypes.js | 4 ++-- .../MoneyRequestParticipantsSelector.js | 20 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/transactionPropTypes.js b/src/components/transactionPropTypes.js index f951837503f3..9ed443d6275e 100644 --- a/src/components/transactionPropTypes.js +++ b/src/components/transactionPropTypes.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import _ from 'underscore'; +import lodashValues from 'lodash/values' import {translatableTextPropTypes} from '@libs/Localize'; import CONST from '@src/CONST'; import sourcePropTypes from './Image/sourcePropTypes'; @@ -57,7 +57,7 @@ export default PropTypes.shape({ ]), /** The type of transaction */ - type: PropTypes.oneOf(_.values(CONST.TRANSACTION.TYPE)), + type: PropTypes.oneOf(lodashValues(CONST.TRANSACTION.TYPE)), /** Custom units attached to the transaction */ customUnits: PropTypes.arrayOf( diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 31ca7ba0098d..1c465a5ab925 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -1,8 +1,10 @@ import lodashGet from 'lodash/get'; +import lodashMap from 'lodash/map'; +import lodashSome from 'lodash/some'; +import lodashReject from 'lodash/reject'; import PropTypes from 'prop-types'; import React, {useCallback, useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import Button from '@components/Button'; import FormHelpMessage from '@components/FormHelpMessage'; import {usePersonalDetails} from '@components/OnyxProvider'; @@ -135,19 +137,19 @@ function MoneyRequestParticipantsSelector({participants, navigateToRequest, navi newSections.push({ title: translate('common.recents'), data: newChatOptions.recentReports, - shouldShow: !_.isEmpty(newChatOptions.recentReports), + shouldShow: newChatOptions.recentReports.length > 0, }); newSections.push({ title: translate('common.contacts'), data: newChatOptions.personalDetails, - shouldShow: !_.isEmpty(newChatOptions.personalDetails), + shouldShow: newChatOptions.personalDetails.length > 0, }); if (newChatOptions.userToInvite && !OptionsListUtils.isCurrentUser(newChatOptions.userToInvite)) { newSections.push({ title: undefined, - data: _.map([newChatOptions.userToInvite], (participant) => { + data: lodashMap([newChatOptions.userToInvite], (participant) => { const isPolicyExpenseChat = lodashGet(participant, 'isPolicyExpenseChat', false); return isPolicyExpenseChat ? OptionsListUtils.getPolicyExpenseReportOption(participant) : OptionsListUtils.getParticipantsOption(participant, personalDetails); }), @@ -203,11 +205,11 @@ function MoneyRequestParticipantsSelector({participants, navigateToRequest, navi return false; }; - const isOptionInList = _.some(participants, isOptionSelected); + const isOptionInList = lodashSome(participants, isOptionSelected); let newSelectedOptions; if (isOptionInList) { - newSelectedOptions = _.reject(participants, isOptionSelected); + newSelectedOptions = lodashReject(participants, isOptionSelected); } else { newSelectedOptions = [ ...participants, @@ -229,11 +231,11 @@ function MoneyRequestParticipantsSelector({participants, navigateToRequest, navi const headerMessage = useMemo( () => OptionsListUtils.getHeaderMessage( - _.get(newChatOptions, 'personalDetails', []).length + _.get(newChatOptions, 'recentReports', []).length !== 0, + lodashGet(newChatOptions, 'personalDetails', []).length + lodashGet(newChatOptions, 'recentReports', []).length !== 0, Boolean(newChatOptions.userToInvite), debouncedSearchTerm.trim(), maxParticipantsReached, - _.some(participants, (participant) => participant.searchText.toLowerCase().includes(debouncedSearchTerm.trim().toLowerCase())), + lodashSome(participants, (participant) => participant.searchText.toLowerCase().includes(debouncedSearchTerm.trim().toLowerCase())), ), [maxParticipantsReached, newChatOptions, participants, debouncedSearchTerm], ); @@ -241,7 +243,7 @@ function MoneyRequestParticipantsSelector({participants, navigateToRequest, navi // Right now you can't split a request with a workspace and other additional participants // This is getting properly fixed in https://github.com/Expensify/App/issues/27508, but as a stop-gap to prevent // the app from crashing on native when you try to do this, we'll going to show error message if you have a workspace and other participants - const hasPolicyExpenseChatParticipant = _.some(participants, (participant) => participant.isPolicyExpenseChat); + const hasPolicyExpenseChatParticipant = lodashSome(participants, (participant) => participant.isPolicyExpenseChat); const shouldShowSplitBillErrorMessage = participants.length > 1 && hasPolicyExpenseChatParticipant; // canUseP2PDistanceRequests is true if the iouType is track expense, but we don't want to allow splitting distance with track expense yet From 0cc0b12288398db476ccc6e4c7c46146c2e7bb68 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 18 Apr 2024 11:37:15 +0800 Subject: [PATCH 06/20] fix lint --- .../OptionsSelector/BaseOptionsSelector.js | 16 ++++++++++------ ...raryForRefactorRequestParticipantsSelector.js | 10 ++++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 13ac5160b30b..a6ba72b5b9a6 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -1,6 +1,10 @@ import lodashDebounce from 'lodash/debounce'; import lodashGet from 'lodash/get'; import lodashIsEqual from 'lodash/isEqual'; +import lodashValues from 'lodash/values'; +import lodashFind from 'lodash/find'; +import lodashMap from 'lodash/map'; +import lodashFindIndex from 'lodash/findIndex'; import PropTypes from 'prop-types'; import React, {Component} from 'react'; import {View} from 'react-native'; @@ -80,7 +84,7 @@ class BaseOptionsSelector extends Component { this.handleFocusOut = this.handleFocusOut.bind(this); this.debouncedUpdateSearchValue = lodashDebounce(this.updateSearchValue, CONST.TIMING.SEARCH_OPTION_LIST_DEBOUNCE_TIME); this.relatedTarget = null; - this.accessibilityRoles = Object.values(CONST.ROLE); + this.accessibilityRoles = lodashValues(CONST.ROLE); this.isWebOrDesktop = [CONST.PLATFORM.DESKTOP, CONST.PLATFORM.WEB].includes(getPlatform()); const allOptions = this.flattenSections(); @@ -172,8 +176,8 @@ class BaseOptionsSelector extends Component { } const newFocusedIndex = this.props.selectedOptions.length; const isNewFocusedIndex = newFocusedIndex !== this.state.focusedIndex; - const prevFocusedOption = newOptions.find((option) => this.focusedOption && option.keyForList === this.focusedOption.keyForList); - const prevFocusedOptionIndex = prevFocusedOption ? newOptions.findIndex((option) => this.focusedOption && option.keyForList === this.focusedOption.keyForList) : undefined; + const prevFocusedOption = lodashFind(newOptions, (option) => this.focusedOption && option.keyForList === this.focusedOption.keyForList); + const prevFocusedOptionIndex = prevFocusedOption ? lodashFindIndex(newOptions, (option) => this.focusedOption && option.keyForList === this.focusedOption.keyForList) : undefined; // eslint-disable-next-line react/no-did-update-set-state this.setState( { @@ -235,7 +239,7 @@ class BaseOptionsSelector extends Component { return defaultIndex; } - const indexOfInitiallyFocusedOption = allOptions.findIndex((option) => option.keyForList === this.props.initiallyFocusedOptionKey); + const indexOfInitiallyFocusedOption = lodashFindIndex(allOptions, (option) => option.keyForList === this.props.initiallyFocusedOptionKey); return indexOfInitiallyFocusedOption; } @@ -246,7 +250,7 @@ class BaseOptionsSelector extends Component { * @returns {Objects[]} */ sliceSections() { - return this.props.sections.map((section) => { + return lodashMap(this.props.sections, (section) => { if (section.data.length === 0) { return section; } @@ -348,7 +352,7 @@ class BaseOptionsSelector extends Component { selectFocusedOption(e) { const focusedItemKey = lodashGet(e, ['target', 'attributes', 'id', 'value']); - const focusedOption = focusedItemKey ? this.state.allOptions.find((option) => option.keyForList === focusedItemKey) : this.state.allOptions[this.state.focusedIndex]; + const focusedOption = focusedItemKey ? lodashFind(this.state.allOptions, (option) => option.keyForList === focusedItemKey) : this.state.allOptions[this.state.focusedIndex]; if (!focusedOption || !this.props.isFocused) { return; diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 80f43059dad0..b4e36778202e 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -2,6 +2,8 @@ import lodashGet from 'lodash/get'; import lodashIsEqual from 'lodash/isEqual'; import lodashPick from 'lodash/pick'; import lodashReject from 'lodash/reject'; +import lodashValues from 'lodash/values'; +import lodashMap from 'lodash/map'; import PropTypes from 'prop-types'; import React, {memo, useCallback, useEffect, useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; @@ -53,13 +55,13 @@ const propTypes = { ), /** The type of IOU report, i.e. split, request, send, track */ - iouType: PropTypes.oneOf(Object.values(CONST.IOU.TYPE)).isRequired, + iouType: PropTypes.oneOf(lodashValues(CONST.IOU.TYPE)).isRequired, /** The expense type, ie. manual, scan, distance */ - iouRequestType: PropTypes.oneOf(Object.values(CONST.IOU.REQUEST_TYPE)).isRequired, + iouRequestType: PropTypes.oneOf(lodashValues(CONST.IOU.REQUEST_TYPE)).isRequired, /** The action of the IOU, i.e. create, split, move */ - action: PropTypes.oneOf(Object.values(CONST.IOU.ACTION)), + action: PropTypes.oneOf(lodashValues(CONST.IOU.ACTION)), }; const defaultProps = { @@ -157,7 +159,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF if (chatOptions.userToInvite && !OptionsListUtils.isCurrentUser(chatOptions.userToInvite)) { newSections.push({ title: undefined, - data: [chatOptions.userToInvite].map((participant) => { + data: lodashMap([chatOptions.userToInvite], (participant) => { const isPolicyExpenseChat = lodashGet(participant, 'isPolicyExpenseChat', false); return isPolicyExpenseChat ? OptionsListUtils.getPolicyExpenseReportOption(participant) : OptionsListUtils.getParticipantsOption(participant, personalDetails); }), From 861ea798f60bb4fa726fde6c60ebb6867327a937 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 18 Apr 2024 11:37:58 +0800 Subject: [PATCH 07/20] prettier --- src/components/OptionsSelector/BaseOptionsSelector.js | 6 +++--- src/components/transactionPropTypes.js | 2 +- .../MoneyTemporaryForRefactorRequestParticipantsSelector.js | 2 +- .../MoneyRequestParticipantsSelector.js | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index a6ba72b5b9a6..767fe27cbfe8 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -1,10 +1,10 @@ import lodashDebounce from 'lodash/debounce'; +import lodashFind from 'lodash/find'; +import lodashFindIndex from 'lodash/findIndex'; import lodashGet from 'lodash/get'; import lodashIsEqual from 'lodash/isEqual'; -import lodashValues from 'lodash/values'; -import lodashFind from 'lodash/find'; import lodashMap from 'lodash/map'; -import lodashFindIndex from 'lodash/findIndex'; +import lodashValues from 'lodash/values'; import PropTypes from 'prop-types'; import React, {Component} from 'react'; import {View} from 'react-native'; diff --git a/src/components/transactionPropTypes.js b/src/components/transactionPropTypes.js index 9ed443d6275e..d1626b75d6cd 100644 --- a/src/components/transactionPropTypes.js +++ b/src/components/transactionPropTypes.js @@ -1,5 +1,5 @@ +import lodashValues from 'lodash/values'; import PropTypes from 'prop-types'; -import lodashValues from 'lodash/values' import {translatableTextPropTypes} from '@libs/Localize'; import CONST from '@src/CONST'; import sourcePropTypes from './Image/sourcePropTypes'; diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index b4e36778202e..395fcebcab79 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -1,9 +1,9 @@ import lodashGet from 'lodash/get'; import lodashIsEqual from 'lodash/isEqual'; +import lodashMap from 'lodash/map'; import lodashPick from 'lodash/pick'; import lodashReject from 'lodash/reject'; import lodashValues from 'lodash/values'; -import lodashMap from 'lodash/map'; import PropTypes from 'prop-types'; import React, {memo, useCallback, useEffect, useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 1c465a5ab925..1bcaba0c691f 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -1,7 +1,7 @@ import lodashGet from 'lodash/get'; import lodashMap from 'lodash/map'; -import lodashSome from 'lodash/some'; import lodashReject from 'lodash/reject'; +import lodashSome from 'lodash/some'; import PropTypes from 'prop-types'; import React, {useCallback, useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; From cca7dd711a087fac625d882440da1a81421422a3 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 18 Apr 2024 11:40:11 +0800 Subject: [PATCH 08/20] uninstall underscore --- package-lock.json | 9 +-------- package.json | 4 +--- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0884041f2268..e0d5cd158774 100644 --- a/package-lock.json +++ b/package-lock.json @@ -132,8 +132,7 @@ "react-webcam": "^7.1.1", "react-window": "^1.8.9", "semver": "^7.5.2", - "shim-keyboard-event-key": "^1.0.3", - "underscore": "^1.13.1" + "shim-keyboard-event-key": "^1.0.3" }, "devDependencies": { "@actions/core": "1.10.0", @@ -182,7 +181,6 @@ "@types/react-test-renderer": "^18.0.0", "@types/semver": "^7.5.4", "@types/setimmediate": "^1.0.2", - "@types/underscore": "^1.11.5", "@types/webpack": "^5.28.5", "@types/webpack-bundle-analyzer": "^4.7.0", "@typescript-eslint/eslint-plugin": "^6.13.2", @@ -12676,11 +12674,6 @@ "version": "4.0.2", "license": "MIT" }, - "node_modules/@types/underscore": { - "version": "1.11.5", - "dev": true, - "license": "MIT" - }, "node_modules/@types/unist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", diff --git a/package.json b/package.json index 71276370dfe7..0df218a6b167 100644 --- a/package.json +++ b/package.json @@ -183,8 +183,7 @@ "react-webcam": "^7.1.1", "react-window": "^1.8.9", "semver": "^7.5.2", - "shim-keyboard-event-key": "^1.0.3", - "underscore": "^1.13.1" + "shim-keyboard-event-key": "^1.0.3" }, "devDependencies": { "@actions/core": "1.10.0", @@ -233,7 +232,6 @@ "@types/react-test-renderer": "^18.0.0", "@types/semver": "^7.5.4", "@types/setimmediate": "^1.0.2", - "@types/underscore": "^1.11.5", "@types/webpack": "^5.28.5", "@types/webpack-bundle-analyzer": "^4.7.0", "@typescript-eslint/eslint-plugin": "^6.13.2", From c95933b2257187b7f3cfcf9abd522f7756d1bda7 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 18 Apr 2024 11:41:30 +0800 Subject: [PATCH 09/20] update comment --- src/libs/E2E/tests/appStartTimeTest.e2e.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/E2E/tests/appStartTimeTest.e2e.ts b/src/libs/E2E/tests/appStartTimeTest.e2e.ts index 5720af8b3641..321fc3773d51 100644 --- a/src/libs/E2E/tests/appStartTimeTest.e2e.ts +++ b/src/libs/E2E/tests/appStartTimeTest.e2e.ts @@ -20,7 +20,7 @@ const test = () => { // collect performance metrics and submit const metrics: PerformanceEntry[] = Performance.getPerformanceMetrics(); - // underscore promises in sequence without for-loop + // promises in sequence without for-loop Promise.all( metrics.map((metric) => E2EClient.submitTestResults({ From 5995e6c83add215c790ecae0a4820c4da14c7b3c Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 18 Apr 2024 11:48:48 +0800 Subject: [PATCH 10/20] fix lint --- ...yTemporaryForRefactorRequestParticipantsSelector.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 395fcebcab79..09520a9c710f 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -4,6 +4,8 @@ import lodashMap from 'lodash/map'; import lodashPick from 'lodash/pick'; import lodashReject from 'lodash/reject'; import lodashValues from 'lodash/values'; +import lodashSome from 'lodash/some'; +import lodashEvery from 'lodash/every'; import PropTypes from 'prop-types'; import React, {memo, useCallback, useEffect, useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; @@ -222,7 +224,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF return false; }; - const isOptionInList = participants.some(isOptionSelected); + const isOptionInList = lodashSome(participants, isOptionSelected); let newSelectedOptions; if (isOptionInList) { @@ -255,7 +257,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF Boolean(newChatOptions.userToInvite), debouncedSearchTerm.trim(), maxParticipantsReached, - participants.some((participant) => participant.searchText.toLowerCase().includes(debouncedSearchTerm.trim().toLowerCase())), + lodashSome(participants, (participant) => participant.searchText.toLowerCase().includes(debouncedSearchTerm.trim().toLowerCase())), ), [maxParticipantsReached, newChatOptions, participants, debouncedSearchTerm], ); @@ -263,7 +265,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF // Right now you can't split an expense with a workspace and other additional participants // This is getting properly fixed in https://github.com/Expensify/App/issues/27508, but as a stop-gap to prevent // the app from crashing on native when you try to do this, we'll going to hide the button if you have a workspace and other participants - const hasPolicyExpenseChatParticipant = participants.some((participant) => participant.isPolicyExpenseChat); + const hasPolicyExpenseChatParticipant = lodashSome(participants, (participant) => participant.isPolicyExpenseChat); const shouldShowSplitBillErrorMessage = participants.length > 1 && hasPolicyExpenseChatParticipant; // canUseP2PDistanceRequests is true if the iouType is track expense, but we don't want to allow splitting distance with track expense yet @@ -381,7 +383,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF ); - const isAllSectionsEmpty = sections.every((section) => section.data.length === 0); + const isAllSectionsEmpty = lodashEvery(sections, (section) => section.data.length === 0); if ([CONST.IOU.ACTION.CATEGORIZE, CONST.IOU.ACTION.SHARE].includes(action) && isAllSectionsEmpty && didScreenTransitionEnd && searchTerm.trim() === '') { return renderEmptyWorkspaceView(); } From 612b3242c8fea0b674112a91337527ee2779482d Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 18 Apr 2024 11:53:52 +0800 Subject: [PATCH 11/20] remove underscore rule --- .eslintrc.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 56a5236a7899..320289fb95d9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -238,7 +238,6 @@ module.exports = { 'jsdoc/no-types': 'error', 'rulesdir/no-default-props': 'error', 'import/no-extraneous-dependencies': 'off', - 'rulesdir/prefer-underscore-method': 'off', 'rulesdir/prefer-import-module-contents': 'off', 'react/require-default-props': 'off', 'react/prop-types': 'off', @@ -260,13 +259,7 @@ module.exports = { 'no-restricted-imports': [ 'error', { - paths: [ - ...restrictedImportPaths, - { - name: 'underscore', - message: 'Please use the corresponding method from lodash instead', - }, - ], + paths: restrictedImportPaths, patterns: restrictedImportPatterns, }, ], From 583ccaac353d2ecbf561178440c30d20b42ac05f Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 19 Apr 2024 18:24:09 +0800 Subject: [PATCH 12/20] turn off prefer underscore lint back --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index 320289fb95d9..4df9493b2e8c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -238,6 +238,7 @@ module.exports = { 'jsdoc/no-types': 'error', 'rulesdir/no-default-props': 'error', 'import/no-extraneous-dependencies': 'off', + 'rulesdir/prefer-underscore-method': 'off', 'rulesdir/prefer-import-module-contents': 'off', 'react/require-default-props': 'off', 'react/prop-types': 'off', From 880ef459545cc3321f916a5d13e3e8dcd9f8900a Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 19 Apr 2024 18:26:40 +0800 Subject: [PATCH 13/20] add back underscore --- tests/unit/CIGitLogicTest.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/CIGitLogicTest.sh b/tests/unit/CIGitLogicTest.sh index ba26fcd45467..73fd01f5c0a0 100755 --- a/tests/unit/CIGitLogicTest.sh +++ b/tests/unit/CIGitLogicTest.sh @@ -42,6 +42,7 @@ function init_git_server { setup_git_as_human npm init -y npm version --no-git-tag-version 1.0.0-0 + npm install underscore echo "node_modules/" >> .gitignore git add -A git commit -m "Initial commit" From 99760ff57b85d401391d8d5a801587454e1e30b0 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 19 Apr 2024 18:41:48 +0800 Subject: [PATCH 14/20] prettier --- .../MoneyTemporaryForRefactorRequestParticipantsSelector.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index adde23a22a2f..9cfa4ba2ac5a 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -1,11 +1,11 @@ +import lodashEvery from 'lodash/every'; import lodashGet from 'lodash/get'; import lodashIsEqual from 'lodash/isEqual'; import lodashMap from 'lodash/map'; import lodashPick from 'lodash/pick'; import lodashReject from 'lodash/reject'; -import lodashValues from 'lodash/values'; import lodashSome from 'lodash/some'; -import lodashEvery from 'lodash/every'; +import lodashValues from 'lodash/values'; import PropTypes from 'prop-types'; import React, {memo, useCallback, useEffect, useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; From b7bb995d2060fccaa9fa4114c7dcb750e398304f Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 19 Apr 2024 23:52:25 +0800 Subject: [PATCH 15/20] remove underscore guideline --- contributingGuides/STYLE.md | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/contributingGuides/STYLE.md b/contributingGuides/STYLE.md index 0a88ecd7bda8..2f2d136fa592 100644 --- a/contributingGuides/STYLE.md +++ b/contributingGuides/STYLE.md @@ -112,38 +112,9 @@ if (someCondition) { } ``` -## Object / Array Methods - -We have standardized on using [underscore.js](https://underscorejs.org/) methods for objects and collections instead of the native [Array instance methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#instance_methods). This is mostly to maintain consistency, but there are some type safety features and conveniences that underscore methods provide us e.g. the ability to iterate over an object and the lack of a `TypeError` thrown if a variable is `undefined`. - -```javascript -// Bad -myArray.forEach(item => doSomething(item)); -// Good -_.each(myArray, item => doSomething(item)); - -// Bad -const myArray = Object.keys(someObject).map(key => doSomething(someObject[key])); -// Good -const myArray = _.map(someObject, (value, key) => doSomething(value)); - -// Bad -myCollection.includes('item'); -// Good -_.contains(myCollection, 'item'); - -// Bad -const modifiedArray = someArray.filter(filterFunc).map(mapFunc); -// Good -const modifiedArray = _.chain(someArray) - .filter(filterFunc) - .map(mapFunc) - .value(); -``` - ## Accessing Object Properties and Default Values -Use `lodashGet()` to safely access object properties and `||` to short circuit null or undefined values that are not guaranteed to exist in a consistent way throughout the codebase. In the rare case that you want to consider a falsy value as usable and the `||` operator prevents this then be explicit about this in your code and check for the type using an underscore method e.g. `_.isBoolean(value)` or `_.isEqual(0)`. +Use `lodashGet()` to safely access object properties and `||` to short circuit null or undefined values that are not guaranteed to exist in a consistent way throughout the codebase. In the rare case that you want to consider a falsy value as usable and the `||` operator prevents this then be explicit about this in your code and check for the type. ```javascript // Bad From 3c4bcf80abfda02a296676b00d21372b7c40c5fa Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 20 Apr 2024 12:39:46 +0800 Subject: [PATCH 16/20] remove unused files --- src/components/transactionPropTypes.js | 96 ----- .../MoneyRequestParticipantsSelector.js | 361 ------------------ 2 files changed, 457 deletions(-) delete mode 100644 src/components/transactionPropTypes.js delete mode 100755 src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js diff --git a/src/components/transactionPropTypes.js b/src/components/transactionPropTypes.js deleted file mode 100644 index d1626b75d6cd..000000000000 --- a/src/components/transactionPropTypes.js +++ /dev/null @@ -1,96 +0,0 @@ -import lodashValues from 'lodash/values'; -import PropTypes from 'prop-types'; -import {translatableTextPropTypes} from '@libs/Localize'; -import CONST from '@src/CONST'; -import sourcePropTypes from './Image/sourcePropTypes'; - -export default PropTypes.shape({ - /** The transaction id */ - transactionID: PropTypes.string, - - /** The iouReportID associated with the transaction */ - reportID: PropTypes.string, - - /** The original transaction amount */ - amount: PropTypes.number, - - /** The edited transaction amount */ - modifiedAmount: PropTypes.number, - - /** The original created data */ - created: PropTypes.string, - - /** The edited transaction date */ - modifiedCreated: PropTypes.string, - - /** The filename of the associated receipt */ - filename: PropTypes.string, - - /** The original merchant name */ - merchant: PropTypes.string, - - /** The edited merchant name */ - modifiedMerchant: PropTypes.string, - - /** The comment object on the transaction */ - comment: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.shape({ - /** The text of the comment */ - comment: PropTypes.string, - - /** The waypoints defining the distance expense */ - waypoints: PropTypes.shape({ - /** The latitude of the waypoint */ - lat: PropTypes.number, - - /** The longitude of the waypoint */ - lng: PropTypes.number, - - /** The address of the waypoint */ - address: PropTypes.string, - - /** The name of the waypoint */ - name: PropTypes.string, - }), - }), - ]), - - /** The type of transaction */ - type: PropTypes.oneOf(lodashValues(CONST.TRANSACTION.TYPE)), - - /** Custom units attached to the transaction */ - customUnits: PropTypes.arrayOf( - PropTypes.shape({ - /** The name of the custom unit */ - name: PropTypes.string, - }), - ), - - /** Selected participants */ - participants: PropTypes.arrayOf( - PropTypes.shape({ - accountID: PropTypes.number, - login: PropTypes.string, - isPolicyExpenseChat: PropTypes.bool, - isOwnPolicyExpenseChat: PropTypes.bool, - selected: PropTypes.bool, - }), - ), - - /** The original currency of the transaction */ - currency: PropTypes.string, - - /** The edited currency of the transaction */ - modifiedCurrency: PropTypes.string, - - /** The receipt object associated with the transaction */ - receipt: PropTypes.shape({ - receiptID: PropTypes.number, - source: PropTypes.oneOfType([PropTypes.number, PropTypes.string, sourcePropTypes]), - state: PropTypes.string, - }), - - /** Server side errors keyed by microtime */ - errorFields: PropTypes.objectOf(PropTypes.objectOf(translatableTextPropTypes)), -}); diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js deleted file mode 100755 index 1bcaba0c691f..000000000000 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ /dev/null @@ -1,361 +0,0 @@ -import lodashGet from 'lodash/get'; -import lodashMap from 'lodash/map'; -import lodashReject from 'lodash/reject'; -import lodashSome from 'lodash/some'; -import PropTypes from 'prop-types'; -import React, {useCallback, useMemo} from 'react'; -import {useOnyx} from 'react-native-onyx'; -import Button from '@components/Button'; -import FormHelpMessage from '@components/FormHelpMessage'; -import {usePersonalDetails} from '@components/OnyxProvider'; -import {useOptionsList} from '@components/OptionListContextProvider'; -import {PressableWithFeedback} from '@components/Pressable'; -import ReferralProgramCTA from '@components/ReferralProgramCTA'; -import SelectCircle from '@components/SelectCircle'; -import SelectionList from '@components/SelectionList'; -import UserListItem from '@components/SelectionList/UserListItem'; -import useDebouncedState from '@hooks/useDebouncedState'; -import useDismissedReferralBanners from '@hooks/useDismissedReferralBanners'; -import useLocalize from '@hooks/useLocalize'; -import useNetwork from '@hooks/useNetwork'; -import usePermissions from '@hooks/usePermissions'; -import useSearchTermAndSearch from '@hooks/useSearchTermAndSearch'; -import useThemeStyles from '@hooks/useThemeStyles'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; - -const propTypes = { - /** Callback to request parent modal to go to next step, which should be request */ - navigateToRequest: PropTypes.func.isRequired, - - /** Callback to request parent modal to go to next step, which should be split */ - navigateToSplit: PropTypes.func.isRequired, - - /** Callback to add participants in MoneyRequestModal */ - onAddParticipants: PropTypes.func.isRequired, - - /** Selected participants from MoneyRequestModal with login */ - participants: PropTypes.arrayOf( - PropTypes.shape({ - accountID: PropTypes.number, - login: PropTypes.string, - isPolicyExpenseChat: PropTypes.bool, - isOwnPolicyExpenseChat: PropTypes.bool, - selected: PropTypes.bool, - }), - ), - - /** The type of IOU report, i.e. bill, request, send */ - iouType: PropTypes.string.isRequired, - - /** Whether the money request is a distance request or not */ - isDistanceRequest: PropTypes.bool, - - /** Whether the screen transition has ended */ - didScreenTransitionEnd: PropTypes.bool, -}; - -const defaultProps = { - participants: [], - isDistanceRequest: false, - didScreenTransitionEnd: false, -}; - -function MoneyRequestParticipantsSelector({participants, navigateToRequest, navigateToSplit, onAddParticipants, iouType, isDistanceRequest, didScreenTransitionEnd}) { - const {translate} = useLocalize(); - const styles = useThemeStyles(); - const [betas] = useOnyx(ONYXKEYS.BETAS); - const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); - const referralContentType = iouType === CONST.IOU.TYPE.SEND ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST; - const {isOffline} = useNetwork(); - const personalDetails = usePersonalDetails(); - const {options, areOptionsInitialized} = useOptionsList({shouldInitialize: didScreenTransitionEnd}); - const {canUseP2PDistanceRequests} = usePermissions(iouType); - - const maxParticipantsReached = participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; - const setSearchTermAndSearchInServer = useSearchTermAndSearch(setSearchTerm, maxParticipantsReached); - - const offlineMessage = isOffline ? [`${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}`, {isTranslated: true}] : ''; - - const newChatOptions = useMemo(() => { - const chatOptions = OptionsListUtils.getFilteredOptions( - options.reports, - options.personalDetails, - betas, - debouncedSearchTerm, - participants, - CONST.EXPENSIFY_EMAILS, - - // If we are using this component in the "Request money" flow then we pass the includeOwnedWorkspaceChats argument so that the current user - // sees the option to request money from their admin on their own Workspace Chat. - iouType === CONST.IOU.TYPE.REQUEST, - - canUseP2PDistanceRequests || !isDistanceRequest, - false, - {}, - [], - false, - {}, - [], - // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now. - // This functionality is being built here: https://github.com/Expensify/App/issues/23291 - canUseP2PDistanceRequests || !isDistanceRequest, - true, - ); - return { - recentReports: chatOptions.recentReports, - personalDetails: chatOptions.personalDetails, - userToInvite: chatOptions.userToInvite, - }; - }, [options.reports, options.personalDetails, betas, debouncedSearchTerm, participants, iouType, canUseP2PDistanceRequests, isDistanceRequest]); - - /** - * Returns the sections needed for the OptionsSelector - * - * @returns {Array} - */ - const sections = useMemo(() => { - const newSections = []; - - const formatResults = OptionsListUtils.formatSectionsFromSearchTerm( - debouncedSearchTerm, - participants, - newChatOptions.recentReports, - newChatOptions.personalDetails, - maxParticipantsReached, - personalDetails, - true, - ); - newSections.push(formatResults.section); - - if (maxParticipantsReached) { - return newSections; - } - - newSections.push({ - title: translate('common.recents'), - data: newChatOptions.recentReports, - shouldShow: newChatOptions.recentReports.length > 0, - }); - - newSections.push({ - title: translate('common.contacts'), - data: newChatOptions.personalDetails, - shouldShow: newChatOptions.personalDetails.length > 0, - }); - - if (newChatOptions.userToInvite && !OptionsListUtils.isCurrentUser(newChatOptions.userToInvite)) { - newSections.push({ - title: undefined, - data: lodashMap([newChatOptions.userToInvite], (participant) => { - const isPolicyExpenseChat = lodashGet(participant, 'isPolicyExpenseChat', false); - return isPolicyExpenseChat ? OptionsListUtils.getPolicyExpenseReportOption(participant) : OptionsListUtils.getParticipantsOption(participant, personalDetails); - }), - shouldShow: true, - }); - } - - return newSections; - }, [maxParticipantsReached, newChatOptions.personalDetails, newChatOptions.recentReports, newChatOptions.userToInvite, participants, personalDetails, debouncedSearchTerm, translate]); - - /** - * Adds a single participant to the request - * - * @param {Object} option - */ - const addSingleParticipant = useCallback( - (option) => { - if (participants.length) { - return; - } - onAddParticipants( - [ - { - accountID: option.accountID, - login: option.login, - isPolicyExpenseChat: option.isPolicyExpenseChat, - reportID: option.reportID, - selected: true, - searchText: option.searchText, - }, - ], - false, - ); - navigateToRequest(); - }, - [navigateToRequest, onAddParticipants, participants.length], - ); - - /** - * Removes a selected option from list if already selected. If not already selected add this option to the list. - * @param {Object} option - */ - const addParticipantToSelection = useCallback( - (option) => { - const isOptionSelected = (selectedOption) => { - if (selectedOption.accountID && selectedOption.accountID === option.accountID) { - return true; - } - - if (selectedOption.reportID && selectedOption.reportID === option.reportID) { - return true; - } - - return false; - }; - const isOptionInList = lodashSome(participants, isOptionSelected); - let newSelectedOptions; - - if (isOptionInList) { - newSelectedOptions = lodashReject(participants, isOptionSelected); - } else { - newSelectedOptions = [ - ...participants, - { - accountID: option.accountID, - login: option.login, - isPolicyExpenseChat: option.isPolicyExpenseChat, - reportID: option.reportID, - selected: true, - searchText: option.searchText, - }, - ]; - } - onAddParticipants(newSelectedOptions, newSelectedOptions.length !== 0); - }, - [participants, onAddParticipants], - ); - - const headerMessage = useMemo( - () => - OptionsListUtils.getHeaderMessage( - lodashGet(newChatOptions, 'personalDetails', []).length + lodashGet(newChatOptions, 'recentReports', []).length !== 0, - Boolean(newChatOptions.userToInvite), - debouncedSearchTerm.trim(), - maxParticipantsReached, - lodashSome(participants, (participant) => participant.searchText.toLowerCase().includes(debouncedSearchTerm.trim().toLowerCase())), - ), - [maxParticipantsReached, newChatOptions, participants, debouncedSearchTerm], - ); - - // Right now you can't split a request with a workspace and other additional participants - // This is getting properly fixed in https://github.com/Expensify/App/issues/27508, but as a stop-gap to prevent - // the app from crashing on native when you try to do this, we'll going to show error message if you have a workspace and other participants - const hasPolicyExpenseChatParticipant = lodashSome(participants, (participant) => participant.isPolicyExpenseChat); - const shouldShowSplitBillErrorMessage = participants.length > 1 && hasPolicyExpenseChatParticipant; - - // canUseP2PDistanceRequests is true if the iouType is track expense, but we don't want to allow splitting distance with track expense yet - const isAllowedToSplit = (canUseP2PDistanceRequests || !isDistanceRequest) && (iouType !== CONST.IOU.TYPE.SEND || iouType !== CONST.IOU.TYPE.TRACK_EXPENSE); - - const handleConfirmSelection = useCallback( - (keyEvent, option) => { - const shouldAddSingleParticipant = option && !participants.length; - - if (shouldShowSplitBillErrorMessage || (!participants.length && !option)) { - return; - } - - if (shouldAddSingleParticipant) { - addSingleParticipant(option); - return; - } - - navigateToSplit(); - }, - [shouldShowSplitBillErrorMessage, navigateToSplit, addSingleParticipant, participants.length], - ); - - const {isDismissed} = useDismissedReferralBanners({referralContentType}); - - const footerContent = useMemo(() => { - if (isDismissed && !shouldShowSplitBillErrorMessage && !participants.length) { - return null; - } - return ( - <> - {!isDismissed && ( - - )} - - {shouldShowSplitBillErrorMessage && ( - - )} - - {!!participants.length && ( -