diff --git a/src/CONST.ts b/src/CONST.ts index da106bdfec80..edceb11edd85 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4756,7 +4756,8 @@ type Country = keyof typeof CONST.ALL_COUNTRIES; type IOUType = ValueOf; type IOUAction = ValueOf; +type IOURequestType = ValueOf; -export type {Country, IOUAction, IOUType, RateAndUnit, OnboardingPurposeType}; +export type {Country, IOUAction, IOUType, RateAndUnit, OnboardingPurposeType, IOURequestType}; export default CONST; diff --git a/src/components/ReferralProgramCTA.tsx b/src/components/ReferralProgramCTA.tsx index 237fc8f955a3..086b875451c0 100644 --- a/src/components/ReferralProgramCTA.tsx +++ b/src/components/ReferralProgramCTA.tsx @@ -1,5 +1,5 @@ import React, {useEffect} from 'react'; -import type {ViewStyle} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; import useDismissedReferralBanners from '@hooks/useDismissedReferralBanners'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; @@ -19,7 +19,7 @@ type ReferralProgramCTAProps = { | typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT | typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.PAY_SOMEONE | typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND; - style?: ViewStyle; + style?: StyleProp; onDismiss?: () => void; }; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 6dc46383f3fa..3332932f97c7 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -282,7 +282,7 @@ function getPolicy(policyID: string | undefined): Policy | EmptyObject { /** * Returns a primary policy for the user */ -function getPrimaryPolicy(activePolicyID?: string): Policy | undefined { +function getPrimaryPolicy(activePolicyID?: OnyxEntry): Policy | undefined { const activeAdminWorkspaces = PolicyUtils.getActiveAdminWorkspaces(allPolicies); const primaryPolicy: Policy | null | undefined = allPolicies?.[activePolicyID ?? '']; diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx similarity index 78% rename from src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js rename to src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index ecf3c449b14b..ec05c5ad6fda 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -1,12 +1,8 @@ -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 lodashSome from 'lodash/some'; -import lodashValues from 'lodash/values'; -import PropTypes from 'prop-types'; import React, {memo, useCallback, useEffect, useMemo} from 'react'; +import type {GestureResponderEvent} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import FormHelpMessage from '@components/FormHelpMessage'; @@ -23,46 +19,36 @@ import usePermissions from '@hooks/usePermissions'; import useScreenWrapperTranstionStatus from '@hooks/useScreenWrapperTransitionStatus'; import useThemeStyles from '@hooks/useThemeStyles'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import type {MaybePhraseKey} from '@libs/Localize'; +import type {Options} from '@libs/OptionsListUtils'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as Policy from '@userActions/Policy'; import * as Report from '@userActions/Report'; +import type {IOUAction, IOURequestType, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Participant} from '@src/types/onyx/IOU'; -const propTypes = { +type MoneyRequestParticipantsSelectorProps = { /** Callback to request parent modal to go to next step, which should be split */ - onFinish: PropTypes.func.isRequired, + onFinish: (value?: string) => void; /** Callback to add participants in MoneyRequestModal */ - onParticipantsAdded: PropTypes.func.isRequired, - + onParticipantsAdded: (value: Participant[]) => void; /** 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, - }), - ), + participants?: Participant[]; /** The type of IOU report, i.e. split, request, send, track */ - iouType: PropTypes.oneOf(lodashValues(CONST.IOU.TYPE)).isRequired, + iouType: IOUType; /** The expense type, ie. manual, scan, distance */ - iouRequestType: PropTypes.oneOf(lodashValues(CONST.IOU.REQUEST_TYPE)).isRequired, + iouRequestType: IOURequestType; /** The action of the IOU, i.e. create, split, move */ - action: PropTypes.oneOf(lodashValues(CONST.IOU.ACTION)), -}; - -const defaultProps = { - participants: [], - action: CONST.IOU.ACTION.CREATE, + action: IOUAction; }; -function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onFinish, onParticipantsAdded, iouType, iouRequestType, action}) { +function MoneyRequestParticipantsSelector({participants = [], onFinish, onParticipantsAdded, iouType, iouRequestType, action}: MoneyRequestParticipantsSelectorProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); @@ -79,10 +65,10 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF shouldInitialize: didScreenTransitionEnd, }); - const offlineMessage = isOffline ? [`${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}`, {isTranslated: true}] : ''; + const offlineMessage: MaybePhraseKey = isOffline ? [`${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}`, {isTranslated: true}] : ''; const isIOUSplit = iouType === CONST.IOU.TYPE.SPLIT; - const isCategorizeOrShareAction = [CONST.IOU.ACTION.CATEGORIZE, CONST.IOU.ACTION.SHARE].includes(action); + const isCategorizeOrShareAction = [CONST.IOU.ACTION.CATEGORIZE, CONST.IOU.ACTION.SHARE].some((option) => option === action); const shouldShowReferralBanner = !isDismissed && iouType !== CONST.IOU.TYPE.INVOICE; @@ -96,7 +82,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF * @returns {Array} */ const [sections, newChatOptions] = useMemo(() => { - const newSections = []; + const newSections: OptionsListUtils.CategorySection[] = []; if (!areOptionsInitialized || !didScreenTransitionEnd) { return [newSections, {}]; } @@ -112,6 +98,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF // sees the option to submit an expense from their admin on their own Workspace Chat. (iouType === CONST.IOU.TYPE.SUBMIT || iouType === CONST.IOU.TYPE.SPLIT) && action !== CONST.IOU.ACTION.SUBMIT, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing (canUseP2PDistanceRequests || iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE) && !isCategorizeOrShareAction, false, {}, @@ -119,6 +106,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF false, {}, [], + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing (canUseP2PDistanceRequests || iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE) && !isCategorizeOrShareAction, false, undefined, @@ -131,7 +119,14 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF isCategorizeOrShareAction ? 0 : undefined, ); - const formatResults = OptionsListUtils.formatSectionsFromSearchTerm(debouncedSearchTerm, participants, chatOptions.recentReports, chatOptions.personalDetails, personalDetails, true); + const formatResults = OptionsListUtils.formatSectionsFromSearchTerm( + debouncedSearchTerm, + participants.map((participant) => ({...participant, reportID: participant.reportID ?? ''})), + chatOptions.recentReports, + chatOptions.personalDetails, + personalDetails, + true, + ); newSections.push(formatResults.section); @@ -147,11 +142,14 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF shouldShow: chatOptions.personalDetails.length > 0, }); - if (chatOptions.userToInvite && !OptionsListUtils.isCurrentUser(chatOptions.userToInvite)) { + if ( + chatOptions.userToInvite && + !OptionsListUtils.isCurrentUser({...chatOptions.userToInvite, accountID: chatOptions.userToInvite?.accountID ?? -1, status: chatOptions.userToInvite?.status ?? undefined}) + ) { newSections.push({ title: undefined, - data: lodashMap([chatOptions.userToInvite], (participant) => { - const isPolicyExpenseChat = lodashGet(participant, 'isPolicyExpenseChat', false); + data: [chatOptions.userToInvite].map((participant) => { + const isPolicyExpenseChat = participant?.isPolicyExpenseChat ?? false; return isPolicyExpenseChat ? OptionsListUtils.getPolicyExpenseReportOption(participant) : OptionsListUtils.getParticipantsOption(participant, personalDetails); }), shouldShow: true, @@ -182,8 +180,8 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF * @param {Object} option */ const addSingleParticipant = useCallback( - (option) => { - const newParticipants = [ + (option: Participant) => { + const newParticipants: Participant[] = [ { ...lodashPick(option, 'accountID', 'login', 'isPolicyExpenseChat', 'reportID', 'searchText', 'policyID'), selected: true, @@ -195,7 +193,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF const primaryPolicy = Policy.getPrimaryPolicy(activePolicyID); newParticipants.push({ - policyID: primaryPolicy.id, + policyID: primaryPolicy?.id, isSender: true, selected: false, iouType, @@ -214,20 +212,20 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF * @param {Object} option */ const addParticipantToSelection = useCallback( - (option) => { - const isOptionSelected = (selectedOption) => { - if (selectedOption.accountID && selectedOption.accountID === option.accountID) { + (option: Participant) => { + const isOptionSelected = (selectedOption: Participant) => { + if (selectedOption.accountID && selectedOption.accountID === option?.accountID) { return true; } - if (selectedOption.reportID && selectedOption.reportID === option.reportID) { + if (selectedOption.reportID && selectedOption.reportID === option?.reportID) { return true; } return false; }; - const isOptionInList = lodashSome(participants, isOptionSelected); - let newSelectedOptions; + const isOptionInList = participants.some(isOptionSelected); + let newSelectedOptions: Participant[]; if (isOptionInList) { newSelectedOptions = lodashReject(participants, isOptionSelected); @@ -255,10 +253,10 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF const headerMessage = useMemo( () => OptionsListUtils.getHeaderMessage( - lodashGet(newChatOptions, 'personalDetails', []).length + lodashGet(newChatOptions, 'recentReports', []).length !== 0, - Boolean(newChatOptions.userToInvite), + ((newChatOptions as Options)?.personalDetails ?? []).length + ((newChatOptions as Options)?.recentReports ?? []).length !== 0, + Boolean((newChatOptions as Options)?.userToInvite), debouncedSearchTerm.trim(), - lodashSome(participants, (participant) => participant.searchText.toLowerCase().includes(debouncedSearchTerm.trim().toLowerCase())), + participants.some((participant) => participant?.searchText?.toLowerCase().includes(debouncedSearchTerm.trim().toLowerCase())), ), [newChatOptions, participants, debouncedSearchTerm], ); @@ -266,17 +264,18 @@ 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 = lodashSome(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 const isAllowedToSplit = + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing (canUseP2PDistanceRequests || iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE) && - ![CONST.IOU.TYPE.PAY, CONST.IOU.TYPE.TRACK, CONST.IOU.TYPE.INVOICE].includes(iouType) && - ![CONST.IOU.ACTION.SHARE, CONST.IOU.ACTION.SUBMIT, CONST.IOU.ACTION.CATEGORIZE].includes(action); + ![CONST.IOU.TYPE.PAY, CONST.IOU.TYPE.TRACK, CONST.IOU.TYPE.INVOICE].some((option) => option === iouType) && + ![CONST.IOU.ACTION.SHARE, CONST.IOU.ACTION.SUBMIT, CONST.IOU.ACTION.CATEGORIZE].some((option) => option === action); const handleConfirmSelection = useCallback( - (keyEvent, option) => { + (keyEvent?: GestureResponderEvent | KeyboardEvent, option?: Participant) => { const shouldAddSingleParticipant = option && !participants.length; if (shouldShowSplitBillErrorMessage || (!participants.length && !option)) { return; @@ -348,15 +347,10 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF ); } -MoneyTemporaryForRefactorRequestParticipantsSelector.propTypes = propTypes; -MoneyTemporaryForRefactorRequestParticipantsSelector.defaultProps = defaultProps; -MoneyTemporaryForRefactorRequestParticipantsSelector.displayName = 'MoneyTemporaryForRefactorRequestParticipantsSelector'; +MoneyRequestParticipantsSelector.displayName = 'MoneyTemporaryForRefactorRequestParticipantsSelector'; export default memo( - MoneyTemporaryForRefactorRequestParticipantsSelector, + MoneyRequestParticipantsSelector, (prevProps, nextProps) => - lodashIsEqual(prevProps.participants, nextProps.participants) && - prevProps.iouRequestType === nextProps.iouRequestType && - prevProps.iouType === nextProps.iouType && - lodashIsEqual(prevProps.betas, nextProps.betas), + lodashIsEqual(prevProps.participants, nextProps.participants) && prevProps.iouRequestType === nextProps.iouRequestType && prevProps.iouType === nextProps.iouType, ); diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.tsx b/src/pages/iou/request/step/IOURequestStepParticipants.tsx index bf74b6dca9c8..45e644607d34 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.tsx +++ b/src/pages/iou/request/step/IOURequestStepParticipants.tsx @@ -8,7 +8,7 @@ import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as TransactionUtils from '@libs/TransactionUtils'; -import MoneyRequestParticipantsSelector from '@pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector'; +import MoneyRequestParticipantsSelector from '@pages/iou/request/MoneyRequestParticipantsSelector'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/types/onyx/IOU.ts b/src/types/onyx/IOU.ts index 726b94c5f6d3..82bf4d6efc04 100644 --- a/src/types/onyx/IOU.ts +++ b/src/types/onyx/IOU.ts @@ -23,6 +23,7 @@ type Participant = { isSelected?: boolean; isSelfDM?: boolean; isSender?: boolean; + iouType?: string; }; type Split = {