From 18d46518d7626e8f0966bc51c408aaa6392fbe09 Mon Sep 17 00:00:00 2001 From: s-alves10 Date: Wed, 22 Nov 2023 00:55:01 -0600 Subject: [PATCH 001/252] fix: check if the parent report is the archived room --- src/libs/actions/Task.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index c91f6d1a2eec..e59debd08a1d 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -877,6 +877,11 @@ function canModifyTask(taskReport, sessionAccountID) { return false; } + const parentReport = ReportUtils.getParentReport(taskReport); + if (ReportUtils.isArchivedRoom(parentReport)) { + return false; + } + if (sessionAccountID === getTaskOwnerAccountID(taskReport) || sessionAccountID === getTaskAssigneeAccountID(taskReport)) { return true; } @@ -884,7 +889,6 @@ function canModifyTask(taskReport, sessionAccountID) { // If you don't have access to the task report (maybe haven't opened it yet), check if you can access the parent report // - If the parent report is an #admins only room // - If you are a policy admin - const parentReport = ReportUtils.getParentReport(taskReport); return ReportUtils.isAllowedToComment(parentReport); } From 6fb66b91cafb483f4739f739816828fcaa01d455 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 1 Dec 2023 13:13:03 +0700 Subject: [PATCH 002/252] go back to correct page in referral page --- src/pages/ReferralDetailsPage.js | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/pages/ReferralDetailsPage.js b/src/pages/ReferralDetailsPage.js index 60b5d23b39da..2f773d16a8ff 100644 --- a/src/pages/ReferralDetailsPage.js +++ b/src/pages/ReferralDetailsPage.js @@ -17,7 +17,6 @@ import Navigation from '@libs/Navigation/Navigation'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; const propTypes = { /** Navigation route context info provided by react navigation */ @@ -58,27 +57,13 @@ function ReferralDetailsPage({route, account}) { return `${CONST.REFERRAL_PROGRAM.LINK}/?thanks=${encodeURIComponent(email)}`; } - function getFallbackRoute() { - const fallbackRoutes = { - [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST]: ROUTES.MONEY_REQUEST_PARTICIPANTS.getRoute(CONST.IOU.TYPE.REQUEST), - [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY]: ROUTES.MONEY_REQUEST_PARTICIPANTS.getRoute(CONST.IOU.TYPE.SEND), - [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT]: ROUTES.NEW_CHAT, - [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND]: ROUTES.SEARCH, - }; - - return fallbackRoutes[contentType]; - } - return ( - Navigation.goBack(getFallbackRoute())} - /> + Navigation.goBack(getFallbackRoute())} + onPress={() => Navigation.goBack()} pressOnEnter enterKeyEventListenerPriority={1} /> From 1f22bec902d8e96699377a9d10168752e4f8fc61 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 4 Dec 2023 13:46:19 +0700 Subject: [PATCH 003/252] add fallback route for confirm button --- src/pages/ReferralDetailsPage.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/pages/ReferralDetailsPage.js b/src/pages/ReferralDetailsPage.js index 2f773d16a8ff..2b6bc9249bc6 100644 --- a/src/pages/ReferralDetailsPage.js +++ b/src/pages/ReferralDetailsPage.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import React from 'react'; +import React, {useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -17,6 +17,7 @@ import Navigation from '@libs/Navigation/Navigation'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; const propTypes = { /** Navigation route context info provided by react navigation */ @@ -53,6 +54,17 @@ function ReferralDetailsPage({route, account}) { const shouldShowBody2 = isShareCode; const shouldShowClipboard = contentType === CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND || isShareCode; + const fallBackRoute = useMemo(() => { + const fallbackRoutes = { + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST]: ROUTES.MONEY_REQUEST_PARTICIPANTS.getRoute(CONST.IOU.TYPE.REQUEST), + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY]: ROUTES.MONEY_REQUEST_PARTICIPANTS.getRoute(CONST.IOU.TYPE.SEND), + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT]: ROUTES.NEW_CHAT, + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND]: ROUTES.SEARCH, + }; + + return fallbackRoutes[contentType]; + }, [contentType]); + function generateReferralURL(email) { return `${CONST.REFERRAL_PROGRAM.LINK}/?thanks=${encodeURIComponent(email)}`; } @@ -93,7 +105,7 @@ function ReferralDetailsPage({route, account}) { success style={[styles.w100]} text={translate('common.buttonConfirm')} - onPress={() => Navigation.goBack()} + onPress={() => Navigation.goBack(fallBackRoute)} pressOnEnter enterKeyEventListenerPriority={1} /> From 958bb7027f075cf71c6241434b9ced2cc6b8dc75 Mon Sep 17 00:00:00 2001 From: s-alves10 Date: Wed, 20 Dec 2023 04:54:23 -0600 Subject: [PATCH 004/252] fix: remove redundant code --- src/libs/actions/Task.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 03b94d5f82de..50172158efdb 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -882,8 +882,6 @@ function canModifyTask(taskReport, sessionAccountID, policyRole = '') { return true; } - const parentReport = ReportUtils.getParentReport(taskReport); - if (policyRole && (ReportUtils.isChatRoom(parentReport) || ReportUtils.isPolicyExpenseChat(parentReport)) && policyRole !== CONST.POLICY.ROLE.ADMIN) { return false; } From 2c2b068bf13be2ebef31382f42e2c830679c8814 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 25 Dec 2023 16:20:11 +0700 Subject: [PATCH 005/252] refactor referral route --- src/ROUTES.ts | 20 ++++++++++++++++ src/SCREENS.ts | 5 ++++ .../OptionsSelector/BaseOptionsSelector.js | 6 +++-- .../AppNavigator/ModalStackNavigators.tsx | 5 ++++ src/libs/Navigation/linkingConfig.ts | 5 ++++ src/pages/NewChatPage.js | 2 ++ src/pages/ReferralDetailsPage.js | 23 +++++++++++++++++-- src/pages/SearchPage.js | 2 ++ src/pages/ShareCodePage.js | 2 +- ...yForRefactorRequestParticipantsSelector.js | 14 +++++++++++ .../step/IOURequestStepParticipants.js | 2 ++ 11 files changed, 81 insertions(+), 5 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index ca1fe9f0e81a..64de8064ba81 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -463,6 +463,26 @@ const ROUTES = { route: 'referral/:contentType', getRoute: (contentType: string) => `referral/${contentType}` as const, }, + REFERRAL_DETAILS_MODAL_REQUEST: { + route: 'create/request/participants/:transactionID/:reportID/referral/:contentType', + getRoute: (transactionID: string, reportID: string, contentType: string) => `create/request/participants/${transactionID}/${reportID}/referral/${contentType}` as const, + }, + REFERRAL_DETAILS_MODAL_START_CHAT: { + route: 'new/referral/:contentType', + getRoute: (contentType: string) => `new/referral/${contentType}` as const, + }, + REFERRAL_DETAILS_MODAL_SEND_MONEY: { + route: 'send/new/participants/referral/:contentType', + getRoute: (contentType: string) => `send/new/participants/referral/${contentType}` as const, + }, + REFERRAL_DETAILS_MODAL_REFER_FRIEND: { + route: 'search/referral/:contentType', + getRoute: (contentType: string) => `search/referral/${contentType}` as const, + }, + REFERRAL_DETAILS_MODAL_SHARE_CODE: { + route: 'settings/shareCode/referral/:contentType', + getRoute: (contentType: string) => `settings/shareCode/referral/${contentType}` as const, + }, // These are some one-off routes that will be removed once they're no longer needed (see GH issues for details) SAASTR: 'saastr', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 2cd263237866..b8282ce2d8b7 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -235,6 +235,11 @@ const SCREENS = { REIMBURSEMENT_ACCOUNT: 'ReimbursementAccount', GET_ASSISTANCE: 'GetAssistance', REFERRAL_DETAILS: 'Referral_Details', + REFERRAL_DETAILS_MONEY_REQUEST: 'Referral_Details_Money_Request', + REFERRAL_DETAILS_START_CHAT: 'Referral_Details_Start_Chat', + REFERRAL_DETAILS_SEND_MONEY: 'Referral_Details_Send_Money', + REFERRAL_DETAILS_REFER_FRIEND: 'Referral_Details_Refer_Friend', + REFERRAL_DETAILS_SHARE_CODE: 'Referral_Details_Share_Code', KEYBOARD_SHORTCUTS: 'KeyboardShortcuts', } as const; diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 3c40b3cf1144..725d55cf5925 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -24,7 +24,6 @@ import KeyboardShortcut from '@libs/KeyboardShortcut'; import Navigation from '@libs/Navigation/Navigation'; import setSelection from '@libs/setSelection'; import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; import {defaultProps as optionsSelectorDefaultProps, propTypes as optionsSelectorPropTypes} from './optionsSelectorPropTypes'; const propTypes = { @@ -49,6 +48,9 @@ const propTypes = { /** Referral content type */ referralContentType: PropTypes.string, + /** Referral route */ + referralRoute: PropTypes.string, + ...optionsSelectorPropTypes, ...withLocalizePropTypes, ...withThemeStylesPropTypes, @@ -621,7 +623,7 @@ class BaseOptionsSelector extends Component { { - Navigation.navigate(ROUTES.REFERRAL_DETAILS_MODAL.getRoute(this.props.referralContentType)); + Navigation.navigate(this.props.referralRoute); }} style={[ this.props.themeStyles.p5, diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 256ea6d4eceb..37c751235086 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -277,6 +277,11 @@ const SignInModalStackNavigator = createModalStackNavigator({ [SCREENS.REFERRAL_DETAILS]: () => require('../../../pages/ReferralDetailsPage').default as React.ComponentType, + [SCREENS.REFERRAL_DETAILS_MONEY_REQUEST]: () => require('../../../pages/ReferralDetailsPage').default as React.ComponentType, + [SCREENS.REFERRAL_DETAILS_START_CHAT]: () => require('../../../pages/ReferralDetailsPage').default as React.ComponentType, + [SCREENS.REFERRAL_DETAILS_SEND_MONEY]: () => require('../../../pages/ReferralDetailsPage').default as React.ComponentType, + [SCREENS.REFERRAL_DETAILS_REFER_FRIEND]: () => require('../../../pages/ReferralDetailsPage').default as React.ComponentType, + [SCREENS.REFERRAL_DETAILS_SHARE_CODE]: () => require('../../../pages/ReferralDetailsPage').default as React.ComponentType, }); export { diff --git a/src/libs/Navigation/linkingConfig.ts b/src/libs/Navigation/linkingConfig.ts index 0383455a5946..e5979bb78075 100644 --- a/src/libs/Navigation/linkingConfig.ts +++ b/src/libs/Navigation/linkingConfig.ts @@ -487,6 +487,11 @@ const linkingConfig: LinkingOptions = { [SCREENS.RIGHT_MODAL.REFERRAL]: { screens: { [SCREENS.REFERRAL_DETAILS]: ROUTES.REFERRAL_DETAILS_MODAL.route, + [SCREENS.REFERRAL_DETAILS_MONEY_REQUEST]: ROUTES.REFERRAL_DETAILS_MODAL_REQUEST.route, + [SCREENS.REFERRAL_DETAILS_START_CHAT]: ROUTES.REFERRAL_DETAILS_MODAL_START_CHAT.route, + [SCREENS.REFERRAL_DETAILS_SEND_MONEY]: ROUTES.REFERRAL_DETAILS_MODAL_SEND_MONEY.route, + [SCREENS.REFERRAL_DETAILS_REFER_FRIEND]: ROUTES.REFERRAL_DETAILS_MODAL_REFER_FRIEND.route, + [SCREENS.REFERRAL_DETAILS_SHARE_CODE]: ROUTES.REFERRAL_DETAILS_MODAL_SHARE_CODE.route, }, }, }, diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index d7abbab6e93f..60f4d7c1334a 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -22,6 +22,7 @@ import variables from '@styles/variables'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import personalDetailsPropType from './personalDetailsPropType'; import reportPropTypes from './reportPropTypes'; @@ -253,6 +254,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i shouldShowConfirmButton shouldShowReferralCTA referralContentType={CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT} + referralRoute={ROUTES.REFERRAL_DETAILS_MODAL_START_CHAT.getRoute(CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT)} confirmButtonText={selectedOptions.length > 1 ? translate('newChatPage.createGroup') : translate('newChatPage.createChat')} textInputAlert={isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''} onConfirmSelection={createGroup} diff --git a/src/pages/ReferralDetailsPage.js b/src/pages/ReferralDetailsPage.js index fb02778db72d..49671a59a204 100644 --- a/src/pages/ReferralDetailsPage.js +++ b/src/pages/ReferralDetailsPage.js @@ -1,6 +1,5 @@ import PropTypes from 'prop-types'; -import React, {useMemo, useRef} from 'react'; -import {View} from 'react-native'; +import React, {useRef} from 'react'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import ContextMenuItem from '@components/ContextMenuItem'; @@ -15,9 +14,11 @@ import useSingleExecution from '@hooks/useSingleExecution'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Clipboard from '@libs/Clipboard'; +import Navigation from '@libs/Navigation/Navigation'; import * as Link from '@userActions/Link'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import * as ReportActionContextMenu from './home/report/ContextMenu/ReportActionContextMenu'; @@ -25,6 +26,10 @@ const propTypes = { /** Navigation route context info provided by react navigation */ route: PropTypes.shape({ params: PropTypes.shape({ + /** The ID of the transaction being configured */ + transactionID: PropTypes.string, + /** The report ID of the IOU */ + reportID: PropTypes.string, /** The type of the content from where CTA was called */ contentType: PropTypes.string, }), @@ -48,6 +53,7 @@ function ReferralDetailsPage({route, account}) { const popoverAnchor = useRef(null); const {isExecuting, singleExecution} = useSingleExecution(); let {contentType} = route.params; + const {transactionID, reportID} = route.params; if (!_.includes(_.values(CONST.REFERRAL_PROGRAM.CONTENT_TYPES), contentType)) { contentType = CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND; @@ -59,6 +65,18 @@ function ReferralDetailsPage({route, account}) { const shouldShowClipboard = contentType === CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND || isShareCode; const referralLink = `${CONST.REFERRAL_PROGRAM.LINK}/?thanks=${encodeURIComponent(account.primaryLogin)}`; + function getFallbackRoute() { + const fallbackRoutes = { + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST]: ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.getRoute(CONST.IOU.TYPE.REQUEST, transactionID, reportID), + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY]: ROUTES.MONEY_REQUEST_PARTICIPANTS.getRoute(CONST.IOU.TYPE.SEND), + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT]: ROUTES.NEW_CHAT, + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND]: ROUTES.SEARCH, + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SHARE_CODE]: ROUTES.SETTINGS_SHARE_CODE, + }; + + return fallbackRoutes[contentType]; + } + return ( Navigation.navigate(getFallbackRoute())} > {contentHeader} {contentBody} diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 061f43e73de8..da04fe70fba6 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -16,6 +16,7 @@ import * as Report from '@userActions/Report'; import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import personalDetailsPropType from './personalDetailsPropType'; import reportPropTypes from './reportPropTypes'; @@ -181,6 +182,7 @@ function SearchPage({betas, personalDetails, reports, isSearchingForReports}) { textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')} shouldShowReferralCTA referralContentType={CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND} + referralRoute={ROUTES.REFERRAL_DETAILS_MODAL_REFER_FRIEND.getRoute(CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND)} textInputAlert={isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''} onLayout={searchRendered} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} diff --git a/src/pages/ShareCodePage.js b/src/pages/ShareCodePage.js index 1f062a42f8bf..65c952016b5a 100644 --- a/src/pages/ShareCodePage.js +++ b/src/pages/ShareCodePage.js @@ -121,7 +121,7 @@ class ShareCodePage extends React.Component { Navigation.navigate(ROUTES.REFERRAL_DETAILS_MODAL.getRoute(CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SHARE_CODE))} + onPress={() => Navigation.navigate(ROUTES.REFERRAL_DETAILS_MODAL_SHARE_CODE.getRoute(CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SHARE_CODE))} /> diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 4db9c4ce3fb7..c8d94d247278 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -20,6 +20,7 @@ import personalDetailsPropType from '@pages/personalDetailsPropType'; import reportPropTypes from '@pages/reportPropTypes'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; const propTypes = { /** Beta features list */ @@ -63,6 +64,12 @@ const propTypes = { /** Whether we are searching for reports in the server */ isSearchingForReports: PropTypes.bool, + /** The report ID of the IOU */ + reportID: PropTypes.string.isRequired, + + /** The ID of the transaction being configured */ + transactionID: PropTypes.string.isRequired, + ...withLocalizePropTypes, }; @@ -89,6 +96,8 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ iouType, iouRequestType, isSearchingForReports, + reportID, + transactionID, }) { const styles = useThemeStyles(); const [searchTerm, setSearchTerm] = useState(''); @@ -321,6 +330,11 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ shouldShowOptions={isOptionsDataReady} shouldShowReferralCTA referralContentType={iouType === 'send' ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST} + referralRoute={ + iouType === 'send' + ? ROUTES.REFERRAL_DETAILS_MODAL_SEND_MONEY.getRoute(CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY) + : ROUTES.REFERRAL_DETAILS_MODAL_REQUEST.getRoute(transactionID, reportID, CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST) + } shouldPreventDefaultFocusOnSelectRow={!Browser.isMobile()} shouldDelayFocus footerContent={isAllowedToSplit && footerContent} diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index ec670b828146..4bb676cab7af 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -95,6 +95,8 @@ function IOURequestStepParticipants({ onParticipantsAdded={addParticipant} onFinish={goToNextStep} iouType={iouType} + reportID={reportID} + transactionID={transactionID} iouRequestType={iouRequestType} /> From 7c01d82bbc3e5b749cb45758b915af0254d0100b Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 10 Jan 2024 23:10:50 +0700 Subject: [PATCH 006/252] migrate to ts --- src/CONST.ts | 4 +- src/components/AddPaymentMethodMenu.js | 13 +- src/components/KYCWall/index.js | 3 +- src/libs/CardUtils.ts | 10 +- src/libs/PaymentUtils.ts | 2 +- src/libs/actions/BankAccounts.ts | 2 +- src/libs/actions/PaymentMethods.ts | 14 +- ...entMethodList.js => PaymentMethodList.tsx} | 214 +++++++----------- ...lletEmptyState.js => WalletEmptyState.tsx} | 10 +- .../{CardDetails.js => CardDetails.tsx} | 67 +++--- .../{WalletPage.js => WalletPage.tsx} | 163 +++++++------ .../{index.native.js => index.native.tsx} | 0 .../Wallet/WalletPage/{index.js => index.tsx} | 0 src/pages/settings/Wallet/WalletPage/types.ts | 31 +++ .../Wallet/WalletPage/walletPagePropTypes.js | 52 ----- src/types/onyx/AccountData.ts | 2 + src/types/onyx/Card.ts | 4 +- src/types/onyx/index.ts | 2 + 18 files changed, 271 insertions(+), 322 deletions(-) rename src/pages/settings/Wallet/{PaymentMethodList.js => PaymentMethodList.tsx} (73%) rename src/pages/settings/Wallet/{WalletEmptyState.js => WalletEmptyState.tsx} (87%) rename src/pages/settings/Wallet/WalletPage/{CardDetails.js => CardDetails.tsx} (75%) rename src/pages/settings/Wallet/WalletPage/{WalletPage.js => WalletPage.tsx} (87%) rename src/pages/settings/Wallet/WalletPage/{index.native.js => index.native.tsx} (100%) rename src/pages/settings/Wallet/WalletPage/{index.js => index.tsx} (100%) create mode 100644 src/pages/settings/Wallet/WalletPage/types.ts delete mode 100644 src/pages/settings/Wallet/WalletPage/walletPagePropTypes.js diff --git a/src/CONST.ts b/src/CONST.ts index c6849db630f2..c0eecf6858e3 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1350,7 +1350,9 @@ const CONST = { CLOSED: 6, STATE_SUSPENDED: 7, }, - ACTIVE_STATES: [2, 3, 4, 7], + get ACTIVE_STATES() { + return [2, 3, 4, 7]; + }, }, AVATAR_ROW_SIZE: { DEFAULT: 4, diff --git a/src/components/AddPaymentMethodMenu.js b/src/components/AddPaymentMethodMenu.js index 4abe5655e307..a936b0efdfa1 100644 --- a/src/components/AddPaymentMethodMenu.js +++ b/src/components/AddPaymentMethodMenu.js @@ -122,11 +122,8 @@ AddPaymentMethodMenu.propTypes = propTypes; AddPaymentMethodMenu.defaultProps = defaultProps; AddPaymentMethodMenu.displayName = 'AddPaymentMethodMenu'; -export default compose( - withWindowDimensions, - withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - }), -)(AddPaymentMethodMenu); +export default withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, +})(AddPaymentMethodMenu); diff --git a/src/components/KYCWall/index.js b/src/components/KYCWall/index.js index 49329c73d474..87f1496a494a 100644 --- a/src/components/KYCWall/index.js +++ b/src/components/KYCWall/index.js @@ -2,12 +2,13 @@ import React from 'react'; import BaseKYCWall from './BaseKYCWall'; import {defaultProps, propTypes} from './kycWallPropTypes'; -function KYCWall(props) { +function KYCWall({children, ...props}) { return ( ); } diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index d71ad9c2629a..bc18808886e2 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -29,7 +29,10 @@ function getMonthFromExpirationDateString(expirationDateString: string) { * @param cardID * @returns boolean */ -function isExpensifyCard(cardID: number) { +function isExpensifyCard(cardID?: number) { + if (!cardID) { + return false; + } const card = allCards[cardID]; if (!card) { return false; @@ -49,7 +52,10 @@ function isCorporateCard(cardID: number) { * @param cardID * @returns string in format % - %. */ -function getCardDescription(cardID: number) { +function getCardDescription(cardID?: number) { + if (!cardID) { + return ''; + } const card = allCards[cardID]; if (!card) { return ''; diff --git a/src/libs/PaymentUtils.ts b/src/libs/PaymentUtils.ts index dd35d0df5cfb..60dac04a09ac 100644 --- a/src/libs/PaymentUtils.ts +++ b/src/libs/PaymentUtils.ts @@ -40,7 +40,7 @@ function getPaymentMethodDescription(accountType: AccountType, account: BankAcco /** * Get the PaymentMethods list */ -function formatPaymentMethods(bankAccountList: Record, fundList: Record, styles: ThemeStyles): PaymentMethod[] { +function formatPaymentMethods(bankAccountList: Record, fundList: Record | Fund[], styles: ThemeStyles): PaymentMethod[] { const combinedPaymentMethods: PaymentMethod[] = []; Object.values(bankAccountList).forEach((bankAccount) => { diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index f7b7ec89c670..1ce6bc38191f 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -50,7 +50,7 @@ function setPlaidEvent(eventName: string) { /** * Open the personal bank account setup flow, with an optional exitReportID to redirect to once the flow is finished. */ -function openPersonalBankAccountSetupView(exitReportID: string) { +function openPersonalBankAccountSetupView(exitReportID?: string) { clearPlaid().then(() => { if (exitReportID) { Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {exitReportID}); diff --git a/src/libs/actions/PaymentMethods.ts b/src/libs/actions/PaymentMethods.ts index a7ae54f46416..6614d3516253 100644 --- a/src/libs/actions/PaymentMethods.ts +++ b/src/libs/actions/PaymentMethods.ts @@ -71,10 +71,10 @@ function openWalletPage() { } function getMakeDefaultPaymentOnyxData( - bankAccountID: number, - fundID: number, - previousPaymentMethod: PaymentMethod, - currentPaymentMethod: PaymentMethod, + bankAccountID: number | null, + fundID: number | null, + previousPaymentMethod?: PaymentMethod, + currentPaymentMethod?: PaymentMethod, isOptimisticData = true, ): OnyxUpdate[] { const onyxData: OnyxUpdate[] = [ @@ -130,10 +130,10 @@ function getMakeDefaultPaymentOnyxData( * Sets the default bank account or debit card for an Expensify Wallet * */ -function makeDefaultPaymentMethod(bankAccountID: number, fundID: number, previousPaymentMethod: PaymentMethod, currentPaymentMethod: PaymentMethod) { +function makeDefaultPaymentMethod(bankAccountID: number | null, fundID: number | null, previousPaymentMethod?: PaymentMethod, currentPaymentMethod?: PaymentMethod) { type MakeDefaultPaymentMethodParams = { - bankAccountID: number; - fundID: number; + bankAccountID: number | null; + fundID: number | null; }; const parameters: MakeDefaultPaymentMethodParams = { diff --git a/src/pages/settings/Wallet/PaymentMethodList.js b/src/pages/settings/Wallet/PaymentMethodList.tsx similarity index 73% rename from src/pages/settings/Wallet/PaymentMethodList.js rename to src/pages/settings/Wallet/PaymentMethodList.tsx index 06bd8afa6140..9cceaa60c10c 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.js +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -1,15 +1,17 @@ import {FlashList} from '@shopify/flash-list'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useMemo} from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import React, {ReactElement, Ref, useCallback, useMemo} from 'react'; +import {GestureResponderEvent, StyleProp, View, ViewStyle} from 'react-native'; +import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import {TupleToUnion, ValueOf} from 'type-fest'; import _ from 'underscore'; import bankAccountPropTypes from '@components/bankAccountPropTypes'; import Button from '@components/Button'; import cardPropTypes from '@components/cardPropTypes'; import FormAlertWrapper from '@components/FormAlertWrapper'; import getBankIcon from '@components/Icon/BankIcons'; +import {BankName} from '@components/Icon/BankIconsUtils'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; @@ -28,112 +30,91 @@ import * as PaymentMethods from '@userActions/PaymentMethods'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import assignedCardPropTypes from './assignedCardPropTypes'; +import {AccountData, BankAccountList, Card, CardList, FundList, UserWallet} from '@src/types/onyx'; +import PaymentMethod from '@src/types/onyx/PaymentMethod'; -const propTypes = { - /** What to do when a menu item is pressed */ - onPress: PropTypes.func.isRequired, +const FILTER_TYPES = [CONST.PAYMENT_METHODS.DEBIT_CARD, CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT, ''] as const; +const ACTION_PAYMENT_METHOD_TYPES = [...Object.values(CONST.PAYMENT_METHODS), ''] as const; +type PaymentMethodListOnyxProps = { /** List of bank accounts */ - bankAccountList: PropTypes.objectOf(bankAccountPropTypes), + bankAccountList: OnyxEntry; /** List of assigned cards */ - cardList: PropTypes.objectOf(assignedCardPropTypes), + cardList: OnyxEntry; /** List of user's cards */ - fundList: PropTypes.objectOf(cardPropTypes), - - /** Whether the add bank account button should be shown on the list */ - shouldShowAddBankAccount: PropTypes.bool, - - /** Whether the add Payment button be shown on the list */ - shouldShowAddPaymentMethodButton: PropTypes.bool, - - /** Whether the assigned cards should be shown on the list */ - shouldShowAssignedCards: PropTypes.bool, - - /** Whether the empty list message should be shown when the list is empty */ - shouldShowEmptyListMessage: PropTypes.bool, + fundList: OnyxEntry; /** Are we loading payment methods? */ - isLoadingPaymentMethods: PropTypes.bool, - - /** Type to filter the payment Method list */ - filterType: PropTypes.oneOf([CONST.PAYMENT_METHODS.DEBIT_CARD, CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT, '']), + isLoadingPaymentMethods: OnyxEntry; /** User wallet props */ - userWallet: PropTypes.shape({ - /** The ID of the linked account */ - walletLinkedAccountID: PropTypes.number, - - /** The type of the linked account (debitCard or bankAccount) */ - walletLinkedAccountType: PropTypes.string, - }), + userWallet: OnyxEntry; +}; +type PaymentMethodListProps = PaymentMethodListOnyxProps & { /** Type of active/highlighted payment method */ - actionPaymentMethodType: PropTypes.oneOf([..._.values(CONST.PAYMENT_METHODS), '']), + actionPaymentMethodType?: TupleToUnion; /** ID of active/highlighted payment method */ - activePaymentMethodID: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + activePaymentMethodID?: string | number; /** ID of selected payment method */ - selectedMethodID: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + selectedMethodID?: string | number; /** Content for the FlatList header component */ - listHeaderComponent: PropTypes.func, + listHeaderComponent?: ReactElement; /** Callback for whenever FlatList component size changes */ - onListContentSizeChange: PropTypes.func, + onListContentSizeChange?: () => void; /** Should menu items be selectable with a checkbox */ - shouldShowSelectedState: PropTypes.bool, + shouldShowSelectedState?: boolean; /** React ref being forwarded to the PaymentMethodList Button */ - buttonRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + buttonRef?: Ref; /** To enable/disable scrolling */ - shouldEnableScroll: PropTypes.bool, + shouldEnableScroll?: boolean; /** List container style */ - style: stylePropTypes, + style?: StyleProp; + + /** Type to filter the payment Method list */ + filterType?: TupleToUnion; + /** Whether the add bank account button should be shown on the list */ + shouldShowAddBankAccount?: boolean; + + /** Whether the add Payment button be shown on the list */ + shouldShowAddPaymentMethodButton?: boolean; + + /** Whether the assigned cards should be shown on the list */ + shouldShowAssignedCards?: boolean; + + /** Whether the empty list message should be shown when the list is empty */ + shouldShowEmptyListMessage?: boolean; + + /** What to do when a menu item is pressed */ + onPress: (event?: GestureResponderEvent | KeyboardEvent, accountType?: string, accountData?: AccountData, isDefault?: boolean, methodID?: number) => void | Promise; }; -const defaultProps = { - bankAccountList: {}, - cardList: {}, - fundList: null, - userWallet: { - walletLinkedAccountID: 0, - walletLinkedAccountType: '', - }, - isLoadingPaymentMethods: true, - shouldShowAddBankAccount: true, - shouldShowAddPaymentMethodButton: true, - shouldShowAssignedCards: false, - shouldShowEmptyListMessage: true, - filterType: '', - actionPaymentMethodType: '', - activePaymentMethodID: '', - selectedMethodID: '', - listHeaderComponent: null, - buttonRef: () => {}, - onListContentSizeChange: () => {}, - shouldEnableScroll: true, - style: {}, - shouldShowSelectedState: false, +type PaymentMethodItem = PaymentMethod & { + onPress?: (e: GestureResponderEvent | KeyboardEvent | undefined) => void; + canDismissError?: boolean; + disabled?: boolean; + shouldShowRightIcon?: boolean; + interactive?: boolean; + brickRoadIndicator?: ValueOf; }; -/** - * Dismisses the error on the payment method - * @param {Object} item - */ -function dismissError(item) { +function dismissError(item: PaymentMethod) { const isBankAccount = item.accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT; const paymentList = isBankAccount ? ONYXKEYS.BANK_ACCOUNT_LIST : ONYXKEYS.FUND_LIST; const paymentID = isBankAccount ? lodashGet(item, ['accountData', 'bankAccountID'], '') : lodashGet(item, ['accountData', 'fundID'], ''); if (!paymentID) { - Log.info('Unable to clear payment method error: ', item); + Log.info('Unable to clear payment method error: ', undefined, item); return; } @@ -150,12 +131,7 @@ function dismissError(item) { } } -/** - * @param {Array} filteredPaymentMethods - * @param {Boolean} isDefault - * @returns {Boolean} - */ -function shouldShowDefaultBadge(filteredPaymentMethods, isDefault = false) { +function shouldShowDefaultBadge(filteredPaymentMethods: PaymentMethod[], isDefault = false) { if (!isDefault) { return false; } @@ -167,45 +143,35 @@ function shouldShowDefaultBadge(filteredPaymentMethods, isDefault = false) { return defaultablePaymentMethodCount > 1; } -/** - * @param {String} actionPaymentMethodType - * @param {String|Number} activePaymentMethodID - * @param {String} paymentMethod - * @return {Boolean} - */ -function isPaymentMethodActive(actionPaymentMethodType, activePaymentMethodID, paymentMethod) { +function isPaymentMethodActive(actionPaymentMethodType: string, activePaymentMethodID: string | number, paymentMethod: PaymentMethod) { return paymentMethod.accountType === actionPaymentMethodType && paymentMethod.methodID === activePaymentMethodID; } -/** - * @param {Object} item - * @returns {String} - */ -function keyExtractor(item) { - return item.key; +function keyExtractor(item: PaymentMethod) { + return item.key || ''; } function PaymentMethodList({ - actionPaymentMethodType, - activePaymentMethodID, - bankAccountList, - buttonRef, - cardList, - fundList, - filterType, - isLoadingPaymentMethods, - onPress, - shouldShowSelectedState, - shouldShowAddPaymentMethodButton, - shouldShowAddBankAccount, - shouldShowEmptyListMessage, - shouldShowAssignedCards, - selectedMethodID, + actionPaymentMethodType = '', + activePaymentMethodID = '', + bankAccountList = {}, + buttonRef = () => {}, + cardList = {}, + fundList = {}, + filterType = '', listHeaderComponent, - onListContentSizeChange, - shouldEnableScroll, - style, -}) { + isLoadingPaymentMethods = true, + onPress, + shouldShowSelectedState = false, + shouldShowAddPaymentMethodButton = true, + shouldShowAddBankAccount = true, + shouldShowEmptyListMessage = true, + shouldShowAssignedCards = false, + selectedMethodID = '', + onListContentSizeChange = () => {}, + shouldEnableScroll = true, + style = {}, +}: PaymentMethodListProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); @@ -215,7 +181,7 @@ function PaymentMethodList({ if (shouldShowAssignedCards) { const assignedCards = _.chain(cardList) // Filter by physical, active cards associated with a domain - .filter((card) => !card.isVirtual && card.domainName && CONST.EXPENSIFY_CARD.ACTIVE_STATES.includes(card.state)) + .filter((card) => !card.isVirtual && card.domainName && CONST.EXPENSIFY_CARD.ACTIVE_STATES.includes(card.state ?? 0)) .sortBy((card) => !CardUtils.isExpensifyCard(card.cardID)) .value(); @@ -223,15 +189,15 @@ function PaymentMethodList({ return _.map(assignedCards, (card) => { const isExpensifyCard = CardUtils.isExpensifyCard(card.cardID); - const icon = getBankIcon({bankName: card.bank, isCard: true, styles}); + const icon = getBankIcon({bankName: card.bank as BankName, isCard: true, styles}); // In the case a user has been assigned multiple physical Expensify Cards under one domain, display the Card with PAN const expensifyCardDescription = numberPhysicalExpensifyCards > 1 ? CardUtils.getCardDescription(card.cardID) : translate('walletPage.expensifyCard'); return { - key: card.cardID, + key: card.cardID.toString(), title: isExpensifyCard ? expensifyCardDescription : card.cardName, description: card.domainName, - onPress: isExpensifyCard ? () => Navigation.navigate(ROUTES.SETTINGS_WALLET_DOMAINCARD.getRoute(card.domainName)) : () => {}, + onPress: isExpensifyCard ? () => Navigation.navigate(ROUTES.SETTINGS_WALLET_DOMAINCARD.getRoute(card.domainName ?? '')) : () => {}, shouldShowRightIcon: isExpensifyCard, interactive: isExpensifyCard, canDismissError: isExpensifyCard, @@ -245,8 +211,8 @@ function PaymentMethodList({ const paymentCardList = fundList || {}; // Hide any billing cards that are not P2P debit cards for now because you cannot make them your default method, or delete them - const filteredCardList = _.filter(paymentCardList, (card) => card.accountData.additionalData.isP2PDebitCard); - let combinedPaymentMethods = PaymentUtils.formatPaymentMethods(bankAccountList, filteredCardList, styles); + const filteredCardList = _.filter(paymentCardList, (card) => !!card.accountData?.additionalData?.isP2PDebitCard); + let combinedPaymentMethods = PaymentUtils.formatPaymentMethods(bankAccountList ?? {}, filteredCardList, styles); if (!_.isEmpty(filterType)) { combinedPaymentMethods = _.filter(combinedPaymentMethods, (paymentMethod) => paymentMethod.accountType === filterType); @@ -261,10 +227,9 @@ function PaymentMethodList({ combinedPaymentMethods = _.map(combinedPaymentMethods, (paymentMethod) => { const isMethodActive = isPaymentMethodActive(actionPaymentMethodType, activePaymentMethodID, paymentMethod); - return { ...paymentMethod, - onPress: (e) => onPress(e, paymentMethod.accountType, paymentMethod.accountData, paymentMethod.isDefault, paymentMethod.methodID), + onPress: (e: GestureResponderEvent) => onPress(e, paymentMethod.accountType, paymentMethod.accountData, paymentMethod.isDefault, paymentMethod.methodID), wrapperStyle: isMethodActive ? [StyleUtils.getButtonBackgroundColorStyle(CONST.BUTTON_STATES.PRESSED)] : null, disabled: paymentMethod.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, }; @@ -296,14 +261,9 @@ function PaymentMethodList({ /** * Create a menuItem for each passed paymentMethod - * - * @param {Object} params - * @param {Object} params.item - * - * @return {React.Component} */ const renderItem = useCallback( - ({item}) => ( + ({item}: {item: PaymentMethodItem}) => ( dismissError(item)} pendingAction={item.pendingAction} @@ -321,7 +281,7 @@ function PaymentMethodList({ iconHeight={item.iconHeight || item.iconSize} iconWidth={item.iconWidth || item.iconSize} iconStyles={item.iconStyles} - badgeText={shouldShowDefaultBadge(filteredPaymentMethods, item.isDefault) ? translate('paymentMethodList.defaultPaymentMethod') : null} + badgeText={shouldShowDefaultBadge(filteredPaymentMethods, item.isDefault) ? translate('paymentMethodList.defaultPaymentMethod') : undefined} wrapperStyle={styles.paymentMethod} shouldShowRightIcon={item.shouldShowRightIcon} shouldShowSelectedState={shouldShowSelectedState} @@ -340,7 +300,7 @@ function PaymentMethodList({ ({ bankAccountList: { key: ONYXKEYS.BANK_ACCOUNT_LIST, }, diff --git a/src/pages/settings/Wallet/WalletEmptyState.js b/src/pages/settings/Wallet/WalletEmptyState.tsx similarity index 87% rename from src/pages/settings/Wallet/WalletEmptyState.js rename to src/pages/settings/Wallet/WalletEmptyState.tsx index 7a3a9e9ce6b7..4ef9488368cb 100644 --- a/src/pages/settings/Wallet/WalletEmptyState.js +++ b/src/pages/settings/Wallet/WalletEmptyState.tsx @@ -11,9 +11,8 @@ import Navigation from '@libs/Navigation/Navigation'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; -const propTypes = { - /** The function that is called when a menu item is pressed */ - onAddPaymentMethod: PropTypes.func.isRequired, +type WalletEmptyStateProps = { + onAddPaymentMethod: () => void; }; const WALLET_FEATURES = [ @@ -31,7 +30,7 @@ const WALLET_FEATURES = [ }, ]; -function WalletEmptyState({onAddPaymentMethod}) { +function WalletEmptyState({onAddPaymentMethod}: WalletEmptyStateProps) { const theme = useTheme(); const {translate} = useLocalize(); return ( @@ -58,7 +57,4 @@ function WalletEmptyState({onAddPaymentMethod}) { ); } -WalletEmptyState.displayName = 'WalletEmptyState'; -WalletEmptyState.propTypes = propTypes; - export default WalletEmptyState; diff --git a/src/pages/settings/Wallet/WalletPage/CardDetails.js b/src/pages/settings/Wallet/WalletPage/CardDetails.tsx similarity index 75% rename from src/pages/settings/Wallet/WalletPage/CardDetails.js rename to src/pages/settings/Wallet/WalletPage/CardDetails.tsx index b51c34e89d17..09a7d937053b 100644 --- a/src/pages/settings/Wallet/WalletPage/CardDetails.js +++ b/src/pages/settings/Wallet/WalletPage/CardDetails.tsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import {OnyxEntry, withOnyx} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import PressableWithDelayToggle from '@components/Pressable/PressableWithDelayToggle'; @@ -14,50 +14,39 @@ import Navigation from '@libs/Navigation/Navigation'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import {PrivatePersonalDetails} from '@src/types/onyx'; -const propTypes = { +const defaultPrivatePersonalDetails = { + address: { + street: '', + street2: '', + city: '', + state: '', + zip: '', + country: '', + }, +}; + +type CardDetailsOnyxProps = { + /** User's private personal details */ + privatePersonalDetails: OnyxEntry; +}; + +type CardDetailsProps = CardDetailsOnyxProps & { /** Card number */ - pan: PropTypes.string, + pan?: string; /** Card expiration date */ - expiration: PropTypes.string, + expiration?: string; /** 3 digit code */ - cvv: PropTypes.string, - - /** User's private personal details */ - privatePersonalDetails: PropTypes.shape({ - /** User's home address */ - address: PropTypes.shape({ - street: PropTypes.string, - city: PropTypes.string, - state: PropTypes.string, - zip: PropTypes.string, - country: PropTypes.string, - }), - }), + cvv?: string; /** Domain name */ - domain: PropTypes.string.isRequired, -}; - -const defaultProps = { - pan: '', - expiration: '', - cvv: '', - privatePersonalDetails: { - address: { - street: '', - street2: '', - city: '', - state: '', - zip: '', - country: '', - }, - }, + domain: string; }; -function CardDetails({pan, expiration, cvv, privatePersonalDetails, domain}) { +function CardDetails({pan = '', expiration = '', cvv = '', privatePersonalDetails = defaultPrivatePersonalDetails, domain}: CardDetailsProps) { const styles = useThemeStyles(); usePrivatePersonalDetails(); const {translate} = useLocalize(); @@ -79,6 +68,8 @@ function CardDetails({pan, expiration, cvv, privatePersonalDetails, domain}) { tooltipTextChecked={translate('reportActionContextMenu.copied')} icon={Expensicons.Copy} onPress={handleCopyToClipboard} + accessible={false} + text="" /> } @@ -96,7 +87,7 @@ function CardDetails({pan, expiration, cvv, privatePersonalDetails, domain}) { /> ({ privatePersonalDetails: { key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS, }, diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.js b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx similarity index 87% rename from src/pages/settings/Wallet/WalletPage/WalletPage.js rename to src/pages/settings/Wallet/WalletPage/WalletPage.tsx index bf547bc4bd10..0463008cf7cb 100644 --- a/src/pages/settings/Wallet/WalletPage/WalletPage.js +++ b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx @@ -1,7 +1,9 @@ import lodashGet from 'lodash/get'; -import React, {useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react'; +import React, {Ref, useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react'; import {ActivityIndicator, Dimensions, ScrollView, View} from 'react-native'; +import {GestureResponderEvent} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import {TupleToUnion} from 'type-fest'; import _ from 'underscore'; import AddPaymentMethodMenu from '@components/AddPaymentMethodMenu'; import Button from '@components/Button'; @@ -20,6 +22,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import WalletSection from '@components/WalletSection'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; @@ -35,27 +38,46 @@ import * as PaymentMethods from '@userActions/PaymentMethods'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {defaultProps, propTypes} from './walletPagePropTypes'; +import {AccountData, BankAccount} from '@src/types/onyx'; +import PaymentMethod from '@src/types/onyx/PaymentMethod'; +import IconAsset from '@src/types/utils/IconAsset'; +import {WalletPageOnyxProps, WalletPageProps} from './types'; + +const ACTION_PAYMENT_METHOD_TYPES = [...Object.values(CONST.PAYMENT_METHODS), ''] as const; + +type PaymentMethodState = { + isSelectedPaymentMethodDefault: boolean; + selectedPaymentMethod: AccountData; + formattedSelectedPaymentMethod: { + title: string; + icon?: IconAsset; + description?: string; + type?: string; + }; + methodID: string | number; + selectedPaymentMethodType: string; +}; -function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethods, network, shouldListenForResize, userWallet, walletTerms}) { +function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadingPaymentMethods = true, shouldListenForResize = false, userWallet, walletTerms = {}}: WalletPageProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); + const network = useNetwork(); const {isSmallScreenWidth, windowWidth} = useWindowDimensions(); const [shouldShowAddPaymentMenu, setShouldShowAddPaymentMenu] = useState(false); const [shouldShowDefaultDeleteMenu, setShouldShowDefaultDeleteMenu] = useState(false); const [shouldShowLoadingSpinner, setShouldShowLoadingSpinner] = useState(false); - const [paymentMethod, setPaymentMethod] = useState({ + const [paymentMethod, setPaymentMethod] = useState({ isSelectedPaymentMethodDefault: false, selectedPaymentMethod: {}, formattedSelectedPaymentMethod: { title: '', }, - methodID: null, - selectedPaymentMethodType: null, + methodID: '', + selectedPaymentMethodType: '', }); const addPaymentMethodAnchorRef = useRef(null); - const paymentMethodButtonRef = useRef(null); + const paymentMethodButtonRef = useRef(null); const [anchorPosition, setAnchorPosition] = useState({ anchorPositionHorizontal: 0, anchorPositionVertical: 0, @@ -66,7 +88,7 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod const hasBankAccount = !_.isEmpty(bankAccountList) || !_.isEmpty(fundList); const hasWallet = !_.isEmpty(userWallet); - const hasActivatedWallet = _.contains([CONST.WALLET.TIER_NAME.GOLD, CONST.WALLET.TIER_NAME.PLATINUM], userWallet.tierName); + const hasActivatedWallet = _.contains([CONST.WALLET.TIER_NAME.GOLD, CONST.WALLET.TIER_NAME.PLATINUM], userWallet?.tierName); const hasAssignedCard = !_.isEmpty(cardList); const shouldShowEmptyState = !hasBankAccount && !hasWallet && !hasAssignedCard; @@ -77,7 +99,7 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod // In order to prevent a loop, only update state of the spinner if there is a change const showLoadingSpinner = isLoadingPaymentMethods || false; if (showLoadingSpinner !== shouldShowLoadingSpinner) { - setShouldShowLoadingSpinner(isLoadingPaymentMethods && !network.isOffline); + setShouldShowLoadingSpinner(!!isLoadingPaymentMethods && !network.isOffline); } }, [isLoadingPaymentMethods, network.isOffline, shouldShowLoadingSpinner]); @@ -121,8 +143,8 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod formattedSelectedPaymentMethod: { title: '', }, - methodID: null, - selectedPaymentMethodType: null, + methodID: '', + selectedPaymentMethodType: '', }); }, [setPaymentMethod]); @@ -135,7 +157,7 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod * @param {Boolean} isDefault * @param {String|Number} methodID */ - const paymentMethodPressed = (nativeEvent, accountType, account, isDefault, methodID) => { + const paymentMethodPressed = (nativeEvent?: GestureResponderEvent | KeyboardEvent, accountType?: string, account?: AccountData, isDefault?: boolean, methodID?: string | number) => { if (shouldShowAddPaymentMenu) { setShouldShowAddPaymentMenu(false); return; @@ -145,32 +167,34 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod setShouldShowDefaultDeleteMenu(false); return; } - paymentMethodButtonRef.current = nativeEvent.currentTarget; + paymentMethodButtonRef.current = nativeEvent?.currentTarget as HTMLElement; // The delete/default menu if (accountType) { - let formattedSelectedPaymentMethod; + let formattedSelectedPaymentMethod = { + title: '', + }; if (accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { formattedSelectedPaymentMethod = { - title: account.addressName, - icon: account.icon, + title: account?.addressName ?? '', + icon: account?.icon, description: PaymentUtils.getPaymentMethodDescription(accountType, account), type: CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT, }; } else if (accountType === CONST.PAYMENT_METHODS.DEBIT_CARD) { formattedSelectedPaymentMethod = { - title: account.addressName, - icon: account.icon, + title: account?.addressName ?? '', + icon: account?.icon, description: PaymentUtils.getPaymentMethodDescription(accountType, account), type: CONST.PAYMENT_METHODS.DEBIT_CARD, }; } setPaymentMethod({ - isSelectedPaymentMethodDefault: isDefault, - selectedPaymentMethod: account, + isSelectedPaymentMethodDefault: !!isDefault, + selectedPaymentMethod: account || {}, selectedPaymentMethodType: accountType, formattedSelectedPaymentMethod, - methodID, + methodID: methodID ?? '', }); setShouldShowDefaultDeleteMenu(true); setMenuPosition(); @@ -189,10 +213,8 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod /** * Navigate to the appropriate payment type addition screen - * - * @param {String} paymentType */ - const addPaymentMethodTypePressed = (paymentType) => { + const addPaymentMethodTypePressed = (paymentType: string) => { hideAddPaymentMenu(); if (paymentType === CONST.PAYMENT_METHODS.DEBIT_CARD) { @@ -220,14 +242,14 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod const makeDefaultPaymentMethod = useCallback(() => { const paymentCardList = fundList || {}; // Find the previous default payment method so we can revert if the MakeDefaultPaymentMethod command errors - const paymentMethods = PaymentUtils.formatPaymentMethods(bankAccountList, paymentCardList, styles); + const paymentMethods = PaymentUtils.formatPaymentMethods(bankAccountList || {}, paymentCardList, styles); - const previousPaymentMethod = _.find(paymentMethods, (method) => method.isDefault); + const previousPaymentMethod = _.find(paymentMethods, (method) => !!method.isDefault); const currentPaymentMethod = _.find(paymentMethods, (method) => method.methodID === paymentMethod.methodID); if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { - PaymentMethods.makeDefaultPaymentMethod(paymentMethod.selectedPaymentMethod.bankAccountID, null, previousPaymentMethod, currentPaymentMethod); + PaymentMethods.makeDefaultPaymentMethod(paymentMethod.selectedPaymentMethod.bankAccountID || null, null, previousPaymentMethod, currentPaymentMethod); } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { - PaymentMethods.makeDefaultPaymentMethod(null, paymentMethod.selectedPaymentMethod.fundID, previousPaymentMethod, currentPaymentMethod); + PaymentMethods.makeDefaultPaymentMethod(null, paymentMethod.selectedPaymentMethod.fundID || null, previousPaymentMethod, currentPaymentMethod); } }, [ paymentMethod.methodID, @@ -240,19 +262,19 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod ]); const deletePaymentMethod = useCallback(() => { - if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { - BankAccounts.deletePaymentBankAccount(paymentMethod.selectedPaymentMethod.bankAccountID); - } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { - PaymentMethods.deletePaymentCard(paymentMethod.selectedPaymentMethod.fundID); + const bankAccountID = paymentMethod.selectedPaymentMethod.bankAccountID; + const fundID = paymentMethod.selectedPaymentMethod.fundID; + if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT && bankAccountID) { + BankAccounts.deletePaymentBankAccount(bankAccountID); + } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD && fundID) { + PaymentMethods.deletePaymentCard(fundID); } }, [paymentMethod.selectedPaymentMethod.bankAccountID, paymentMethod.selectedPaymentMethod.fundID, paymentMethod.selectedPaymentMethodType]); /** * Navigate to the appropriate page after completing the KYC flow, depending on what initiated it - * - * @param {String} source */ - const navigateToWalletOrTransferBalancePage = (source) => { + const navigateToWalletOrTransferBalancePage = (source: string) => { Navigation.navigate(source === CONST.KYC_WALL_SOURCE.ENABLE_WALLET ? ROUTES.SETTINGS_WALLET : ROUTES.SETTINGS_WALLET_TRANSFER_BALANCE); }; @@ -306,9 +328,9 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod // We should reset selected payment method state values and close corresponding modals if the selected payment method is deleted let shouldResetPaymentMethodData = false; - if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT && _.isEmpty(bankAccountList[paymentMethod.methodID])) { + if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT && _.isEmpty(bankAccountList?.[paymentMethod.methodID])) { shouldResetPaymentMethodData = true; - } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD && _.isEmpty(fundList[paymentMethod.methodID])) { + } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD && _.isEmpty(fundList?.[paymentMethod.methodID])) { shouldResetPaymentMethodData = true; } if (shouldResetPaymentMethodData) { @@ -344,7 +366,7 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod style={styles.flex1} contentContainerStyle={styles.flex1} onClose={PaymentMethods.clearWalletError} - errors={userWallet.errors} + errors={userWallet?.errors} errorRowStyles={[styles.ph6]} > {hasWallet && ( @@ -363,7 +385,7 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod ) : ( navigateToWalletOrTransferBalancePage(source)} - onSelectPaymentMethod={(selectedPaymentMethod) => { + onSuccessfulKYC={(_iouPaymentType: string, source: string) => navigateToWalletOrTransferBalancePage(source)} + onSelectPaymentMethod={(selectedPaymentMethod: string) => { if (hasActivatedWallet || selectedPaymentMethod !== CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { return; } @@ -388,7 +410,7 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod source={hasActivatedWallet ? CONST.KYC_WALL_SOURCE.TRANSFER_BALANCE : CONST.KYC_WALL_SOURCE.ENABLE_WALLET} shouldIncludeDebitCard={hasActivatedWallet} > - {(triggerKYCFlow, buttonRef) => { + {(triggerKYCFlow: (e: GestureResponderEvent | KeyboardEvent | undefined) => void, buttonRef: Ref) => { if (shouldShowLoadingSpinner) { return null; } @@ -463,7 +485,6 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod shouldEnableScroll={false} onPress={paymentMethodPressed} style={styles.mt5} - isAddPaymentMenuActive={shouldShowAddPaymentMenu} actionPaymentMethodType={shouldShowDefaultDeleteMenu ? paymentMethod.selectedPaymentMethodType : ''} activePaymentMethodID={shouldShowDefaultDeleteMenu ? getSelectedPaymentMethodID() : ''} buttonRef={addPaymentMethodAnchorRef} @@ -480,7 +501,6 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod shouldShowAddPaymentMethodButton={false} shouldShowEmptyListMessage={false} onPress={paymentMethodPressed} - isAddPaymentMenuActive={shouldShowAddPaymentMenu} actionPaymentMethodType={shouldShowDefaultDeleteMenu ? paymentMethod.selectedPaymentMethodType : ''} activePaymentMethodID={shouldShowDefaultDeleteMenu ? getSelectedPaymentMethodID() : ''} buttonRef={addPaymentMethodAnchorRef} @@ -502,7 +522,7 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod anchorRef={paymentMethodButtonRef} > {!showConfirmDeleteModal && ( - + {isPopoverBottomMount && ( addPaymentMethodTypePressed(method)} + onItemSelected={(method: string) => addPaymentMethodTypePressed(method)} anchorRef={addPaymentMethodAnchorRef} /> ); } -WalletPage.propTypes = propTypes; -WalletPage.defaultProps = defaultProps; WalletPage.displayName = 'WalletPage'; -export default compose( - withNetwork(), - withOnyx({ - cardList: { - key: ONYXKEYS.CARD_LIST, - }, - walletTransfer: { - key: ONYXKEYS.WALLET_TRANSFER, - }, - userWallet: { - key: ONYXKEYS.USER_WALLET, - }, - bankAccountList: { - key: ONYXKEYS.BANK_ACCOUNT_LIST, - }, - fundList: { - key: ONYXKEYS.FUND_LIST, - }, - walletTerms: { - key: ONYXKEYS.WALLET_TERMS, - }, - isLoadingPaymentMethods: { - key: ONYXKEYS.IS_LOADING_PAYMENT_METHODS, - }, - }), -)(WalletPage); +export default withOnyx({ + cardList: { + key: ONYXKEYS.CARD_LIST, + }, + walletTransfer: { + key: ONYXKEYS.WALLET_TRANSFER, + }, + userWallet: { + key: ONYXKEYS.USER_WALLET, + }, + bankAccountList: { + key: ONYXKEYS.BANK_ACCOUNT_LIST, + }, + fundList: { + key: ONYXKEYS.FUND_LIST, + }, + walletTerms: { + key: ONYXKEYS.WALLET_TERMS, + }, + isLoadingPaymentMethods: { + key: ONYXKEYS.IS_LOADING_PAYMENT_METHODS, + }, +})(WalletPage); diff --git a/src/pages/settings/Wallet/WalletPage/index.native.js b/src/pages/settings/Wallet/WalletPage/index.native.tsx similarity index 100% rename from src/pages/settings/Wallet/WalletPage/index.native.js rename to src/pages/settings/Wallet/WalletPage/index.native.tsx diff --git a/src/pages/settings/Wallet/WalletPage/index.js b/src/pages/settings/Wallet/WalletPage/index.tsx similarity index 100% rename from src/pages/settings/Wallet/WalletPage/index.js rename to src/pages/settings/Wallet/WalletPage/index.tsx diff --git a/src/pages/settings/Wallet/WalletPage/types.ts b/src/pages/settings/Wallet/WalletPage/types.ts new file mode 100644 index 000000000000..731587f86f39 --- /dev/null +++ b/src/pages/settings/Wallet/WalletPage/types.ts @@ -0,0 +1,31 @@ +import {OnyxEntry} from 'react-native-onyx'; +import {BankAccountList, Beta, Card, CardList, Fund, FundList, Network, UserWallet, WalletTerms, WalletTransfer} from '@src/types/onyx'; + +type WalletPageOnyxProps = { + /** Wallet balance transfer props */ + walletTransfer: OnyxEntry; + + /** The user's wallet account */ + userWallet: OnyxEntry; + + /** List of bank accounts */ + bankAccountList: OnyxEntry; + + /** List of user's cards */ + fundList: OnyxEntry; + + /** Information about the user accepting the terms for payments */ + walletTerms: OnyxEntry; + + cardList: OnyxEntry; + + /** Are we loading payment methods? */ + isLoadingPaymentMethods: OnyxEntry; +}; + +type WalletPageProps = WalletPageOnyxProps & { + /** Listen for window resize event on web and desktop. */ + shouldListenForResize?: boolean; +}; + +export type {WalletPageOnyxProps, WalletPageProps}; diff --git a/src/pages/settings/Wallet/WalletPage/walletPagePropTypes.js b/src/pages/settings/Wallet/WalletPage/walletPagePropTypes.js deleted file mode 100644 index 23bdfe99b086..000000000000 --- a/src/pages/settings/Wallet/WalletPage/walletPagePropTypes.js +++ /dev/null @@ -1,52 +0,0 @@ -import PropTypes from 'prop-types'; -import bankAccountPropTypes from '@components/bankAccountPropTypes'; -import cardPropTypes from '@components/cardPropTypes'; -import networkPropTypes from '@components/networkPropTypes'; -import userWalletPropTypes from '@pages/EnablePayments/userWalletPropTypes'; -import walletTermsPropTypes from '@pages/EnablePayments/walletTermsPropTypes'; -import walletTransferPropTypes from '@pages/settings/Wallet/walletTransferPropTypes'; - -const propTypes = { - /** Wallet balance transfer props */ - walletTransfer: walletTransferPropTypes, - - /** List of betas available to current user */ - betas: PropTypes.arrayOf(PropTypes.string), - - /** Are we loading payment methods? */ - isLoadingPaymentMethods: PropTypes.bool, - - /** Listen for window resize event on web and desktop. */ - shouldListenForResize: PropTypes.bool, - - /** The user's wallet account */ - userWallet: userWalletPropTypes, - - /** Information about the network */ - network: networkPropTypes.isRequired, - - /** List of bank accounts */ - bankAccountList: PropTypes.objectOf(bankAccountPropTypes), - - /** List of user's cards */ - fundList: PropTypes.objectOf(cardPropTypes), - - /** Information about the user accepting the terms for payments */ - walletTerms: walletTermsPropTypes, -}; - -const defaultProps = { - walletTransfer: { - shouldShowSuccess: false, - }, - betas: [], - isLoadingPaymentMethods: true, - shouldListenForResize: false, - userWallet: {}, - bankAccountList: {}, - cardList: {}, - fundList: null, - walletTerms: {}, -}; - -export {propTypes, defaultProps}; diff --git a/src/types/onyx/AccountData.ts b/src/types/onyx/AccountData.ts index 88511eec6864..124a006ff57c 100644 --- a/src/types/onyx/AccountData.ts +++ b/src/types/onyx/AccountData.ts @@ -51,6 +51,8 @@ type AccountData = { /** Any error message to show */ errors?: OnyxCommon.Errors; + + fundID?: number; }; export default AccountData; diff --git a/src/types/onyx/Card.ts b/src/types/onyx/Card.ts index e3b025ff5a2f..68acd88aa120 100644 --- a/src/types/onyx/Card.ts +++ b/src/types/onyx/Card.ts @@ -33,5 +33,7 @@ type TCardDetails = { }; }; +type CardList = Record; + export default Card; -export type {TCardDetails}; +export type {TCardDetails, CardList}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 7bd9c321be5e..d881379f7e05 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -5,6 +5,7 @@ import type BankAccount from './BankAccount'; import type Beta from './Beta'; import type BlockedFromConcierge from './BlockedFromConcierge'; import type Card from './Card'; +import type {CardList} from './Card'; import type Credentials from './Credentials'; import type Currency from './Currency'; import type CustomStatusDraft from './CustomStatusDraft'; @@ -74,6 +75,7 @@ export type { Beta, BlockedFromConcierge, Card, + CardList, Credentials, Currency, CustomStatusDraft, From dba03e1ecc55504615c99ea610e83d0052a0e73b Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 11 Jan 2024 15:19:06 +0700 Subject: [PATCH 007/252] fix type error --- src/components/AddPaymentMethodMenu.js | 12 +- src/components/KYCWall/index.js | 5 +- src/components/Modal/index.android.tsx | 3 +- src/components/Modal/index.ios.tsx | 3 +- src/components/Modal/index.tsx | 3 +- src/components/Modal/types.ts | 74 ++++++------ src/components/Popover/index.tsx | 12 +- src/components/Popover/types.ts | 13 +-- src/components/PopoverProvider/types.ts | 2 +- src/components/PopoverWithoutOverlay/types.ts | 2 +- src/libs/actions/PaymentMethods.ts | 8 +- .../settings/Wallet/PaymentMethodList.tsx | 92 +++++++-------- .../settings/Wallet/WalletEmptyState.tsx | 1 - .../Wallet/WalletPage/CardDetails.tsx | 10 +- .../settings/Wallet/WalletPage/WalletPage.tsx | 107 +++++++++--------- src/pages/settings/Wallet/WalletPage/types.ts | 4 +- 16 files changed, 166 insertions(+), 185 deletions(-) diff --git a/src/components/AddPaymentMethodMenu.js b/src/components/AddPaymentMethodMenu.js index a936b0efdfa1..033fb8765b6f 100644 --- a/src/components/AddPaymentMethodMenu.js +++ b/src/components/AddPaymentMethodMenu.js @@ -1,19 +1,17 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React from 'react'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; -import compose from '@libs/compose'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import iouReportPropTypes from '@pages/iouReportPropTypes'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import lodashGet from 'lodash/get'; +import PropTypes from 'prop-types'; +import React from 'react'; +import { withOnyx } from 'react-native-onyx'; +import _ from 'underscore'; import * as Expensicons from './Icon/Expensicons'; import PopoverMenu from './PopoverMenu'; import refPropTypes from './refPropTypes'; -import withWindowDimensions from './withWindowDimensions'; const propTypes = { /** Should the component be visible? */ diff --git a/src/components/KYCWall/index.js b/src/components/KYCWall/index.js index 87f1496a494a..e3a0d5e82cdc 100644 --- a/src/components/KYCWall/index.js +++ b/src/components/KYCWall/index.js @@ -8,8 +8,9 @@ function KYCWall({children, ...props}) { // eslint-disable-next-line react/jsx-props-no-spreading {...props} shouldListenForResize - children={children} - /> + > + {children} + ); } diff --git a/src/components/Modal/index.android.tsx b/src/components/Modal/index.android.tsx index 4d7ae128a114..86a1fd272185 100644 --- a/src/components/Modal/index.android.tsx +++ b/src/components/Modal/index.android.tsx @@ -1,6 +1,5 @@ import React from 'react'; import {AppState} from 'react-native'; -import withWindowDimensions from '@components/withWindowDimensions'; import ComposerFocusManager from '@libs/ComposerFocusManager'; import BaseModal from './BaseModal'; import type BaseModalProps from './types'; @@ -28,4 +27,4 @@ function Modal({useNativeDriver = true, ...rest}: BaseModalProps) { } Modal.displayName = 'Modal'; -export default withWindowDimensions(Modal); +export default Modal; diff --git a/src/components/Modal/index.ios.tsx b/src/components/Modal/index.ios.tsx index cbe58a071d7d..b26ba6cd0f89 100644 --- a/src/components/Modal/index.ios.tsx +++ b/src/components/Modal/index.ios.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import withWindowDimensions from '@components/withWindowDimensions'; import BaseModal from './BaseModal'; import type BaseModalProps from './types'; @@ -15,4 +14,4 @@ function Modal({children, ...rest}: BaseModalProps) { } Modal.displayName = 'Modal'; -export default withWindowDimensions(Modal); +export default Modal; diff --git a/src/components/Modal/index.tsx b/src/components/Modal/index.tsx index 56f3c76a8879..71c0fe47ffca 100644 --- a/src/components/Modal/index.tsx +++ b/src/components/Modal/index.tsx @@ -1,5 +1,4 @@ import React, {useState} from 'react'; -import withWindowDimensions from '@components/withWindowDimensions'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import StatusBar from '@libs/StatusBar'; @@ -55,4 +54,4 @@ function Modal({fullscreen = true, onModalHide = () => {}, type, onModalShow = ( } Modal.displayName = 'Modal'; -export default withWindowDimensions(Modal); +export default Modal; diff --git a/src/components/Modal/types.ts b/src/components/Modal/types.ts index 0fed37ffea8b..caba945cad30 100644 --- a/src/components/Modal/types.ts +++ b/src/components/Modal/types.ts @@ -1,7 +1,6 @@ import type {ViewStyle} from 'react-native'; import type {ModalProps} from 'react-native-modal'; import type {ValueOf} from 'type-fest'; -import type {WindowDimensionsProps} from '@components/withWindowDimensions/types'; import type CONST from '@src/CONST'; type PopoverAnchorPosition = { @@ -11,57 +10,56 @@ type PopoverAnchorPosition = { left?: number; }; -type BaseModalProps = WindowDimensionsProps & - Partial & { - /** Decides whether the modal should cover fullscreen. FullScreen modal has backdrop */ - fullscreen?: boolean; +type BaseModalProps = Partial & { + /** Decides whether the modal should cover fullscreen. FullScreen modal has backdrop */ + fullscreen?: boolean; - /** Should we close modal on outside click */ - shouldCloseOnOutsideClick?: boolean; + /** Should we close modal on outside click */ + shouldCloseOnOutsideClick?: boolean; - /** Should we announce the Modal visibility changes? */ - shouldSetModalVisibility?: boolean; + /** Should we announce the Modal visibility changes? */ + shouldSetModalVisibility?: boolean; - /** Callback method fired when the user requests to close the modal */ - onClose: (ref?: React.RefObject) => void; + /** Callback method fired when the user requests to close the modal */ + onClose: (ref?: React.RefObject) => void; - /** State that determines whether to display the modal or not */ - isVisible: boolean; + /** State that determines whether to display the modal or not */ + isVisible: boolean; - /** Callback method fired when the user requests to submit the modal content. */ - onSubmit?: () => void; + /** Callback method fired when the user requests to submit the modal content. */ + onSubmit?: () => void; - /** Callback method fired when the modal is hidden */ - onModalHide?: () => void; + /** Callback method fired when the modal is hidden */ + onModalHide?: () => void; - /** Callback method fired when the modal is shown */ - onModalShow?: () => void; + /** Callback method fired when the modal is shown */ + onModalShow?: () => void; - /** Style of modal to display */ - type?: ValueOf; + /** Style of modal to display */ + type?: ValueOf; - /** The anchor position of a popover modal. Has no effect on other modal types. */ - popoverAnchorPosition?: PopoverAnchorPosition; + /** The anchor position of a popover modal. Has no effect on other modal types. */ + popoverAnchorPosition?: PopoverAnchorPosition; - outerStyle?: ViewStyle; + outerStyle?: ViewStyle; - /** Whether the modal should go under the system statusbar */ - statusBarTranslucent?: boolean; + /** Whether the modal should go under the system statusbar */ + statusBarTranslucent?: boolean; - /** Whether the modal should avoid the keyboard */ - avoidKeyboard?: boolean; + /** Whether the modal should avoid the keyboard */ + avoidKeyboard?: boolean; - /** Modal container styles */ - innerContainerStyle?: ViewStyle; + /** Modal container styles */ + innerContainerStyle?: ViewStyle; - /** - * 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?: boolean; - }; + /** + * 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?: boolean; +}; export default BaseModalProps; export type {PopoverAnchorPosition}; diff --git a/src/components/Popover/index.tsx b/src/components/Popover/index.tsx index 762e79fab63c..e1cd18ba4767 100644 --- a/src/components/Popover/index.tsx +++ b/src/components/Popover/index.tsx @@ -3,32 +3,32 @@ import {createPortal} from 'react-dom'; import Modal from '@components/Modal'; import {PopoverContext} from '@components/PopoverProvider'; import PopoverWithoutOverlay from '@components/PopoverWithoutOverlay'; -import withWindowDimensions from '@components/withWindowDimensions'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import CONST from '@src/CONST'; -import type {PopoverWithWindowDimensionsProps} from './types'; +import type {PopoverProps} from './types'; /* * This is a convenience wrapper around the Modal component for a responsive Popover. * On small screen widths, it uses BottomDocked modal type, and a Popover type on wide screen widths. */ -function Popover(props: PopoverWithWindowDimensionsProps) { +function Popover(props: PopoverProps) { const { isVisible, onClose, - isSmallScreenWidth, fullscreen, animationInTiming = CONST.ANIMATED_TRANSITION, onLayout, animationOutTiming, disableAnimation = true, - withoutOverlay, + withoutOverlay = false, anchorPosition = {}, anchorRef = () => {}, animationIn = 'fadeIn', animationOut = 'fadeOut', } = props; + const {isSmallScreenWidth} = useWindowDimensions(); const withoutOverlayRef = useRef(null); const {close, popover} = React.useContext(PopoverContext); @@ -106,4 +106,4 @@ function Popover(props: PopoverWithWindowDimensionsProps) { Popover.displayName = 'Popover'; -export default withWindowDimensions(Popover); +export default Popover; diff --git a/src/components/Popover/types.ts b/src/components/Popover/types.ts index 3d1f95822e6a..551756ed706c 100644 --- a/src/components/Popover/types.ts +++ b/src/components/Popover/types.ts @@ -1,7 +1,6 @@ import type {ValueOf} from 'type-fest'; import type {PopoverAnchorPosition} from '@components/Modal/types'; import type BaseModalProps from '@components/Modal/types'; -import type {WindowDimensionsProps} from '@components/withWindowDimensions/types'; import type CONST from '@src/CONST'; type AnchorAlignment = { @@ -22,16 +21,16 @@ type PopoverProps = BaseModalProps & { anchorPosition?: PopoverAnchorPosition; /** The anchor alignment of the popover */ - anchorAlignment: AnchorAlignment; + anchorAlignment?: AnchorAlignment; /** The anchor ref of the popover */ - anchorRef: React.RefObject; + anchorRef?: React.RefObject; /** Whether disable the animations */ - disableAnimation: boolean; + disableAnimation?: boolean; /** Whether we don't want to show overlay */ - withoutOverlay: boolean; + withoutOverlay?: boolean; /** The dimensions of the popover */ popoverDimensions?: PopoverDimensions; @@ -46,6 +45,4 @@ type PopoverProps = BaseModalProps & { children: React.ReactNode; }; -type PopoverWithWindowDimensionsProps = PopoverProps & WindowDimensionsProps; - -export type {PopoverProps, PopoverWithWindowDimensionsProps, AnchorAlignment}; +export type {PopoverProps, AnchorAlignment}; diff --git a/src/components/PopoverProvider/types.ts b/src/components/PopoverProvider/types.ts index ffd0087cd5ff..acc01fd74427 100644 --- a/src/components/PopoverProvider/types.ts +++ b/src/components/PopoverProvider/types.ts @@ -12,7 +12,7 @@ type PopoverContextValue = { type AnchorRef = { ref: React.RefObject; close: (anchorRef?: React.RefObject) => void; - anchorRef: React.RefObject; + anchorRef?: React.RefObject; onOpenCallback?: () => void; onCloseCallback?: () => void; }; diff --git a/src/components/PopoverWithoutOverlay/types.ts b/src/components/PopoverWithoutOverlay/types.ts index ff4f73fd4114..fd42bf7d8e42 100644 --- a/src/components/PopoverWithoutOverlay/types.ts +++ b/src/components/PopoverWithoutOverlay/types.ts @@ -13,7 +13,7 @@ type PopoverWithoutOverlayProps = ChildrenProps & }; /** The anchor ref of the popover */ - anchorRef: React.RefObject; + anchorRef?: React.RefObject; /** A react-native-animatable animation timing for the modal display animation */ animationInTiming?: number; diff --git a/src/libs/actions/PaymentMethods.ts b/src/libs/actions/PaymentMethods.ts index 6614d3516253..c152a968f030 100644 --- a/src/libs/actions/PaymentMethods.ts +++ b/src/libs/actions/PaymentMethods.ts @@ -83,7 +83,7 @@ function getMakeDefaultPaymentOnyxData( onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.USER_WALLET, value: { - walletLinkedAccountID: bankAccountID || fundID, + walletLinkedAccountID: bankAccountID ?? fundID, walletLinkedAccountType: bankAccountID ? CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT : CONST.PAYMENT_METHODS.DEBIT_CARD, // Only clear the error if this is optimistic data. If this is failure data, we do not want to clear the error that came from the server. errors: null, @@ -93,7 +93,7 @@ function getMakeDefaultPaymentOnyxData( onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.USER_WALLET, value: { - walletLinkedAccountID: bankAccountID || fundID, + walletLinkedAccountID: bankAccountID ?? fundID, walletLinkedAccountType: bankAccountID ? CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT : CONST.PAYMENT_METHODS.DEBIT_CARD, }, }, @@ -320,7 +320,7 @@ type PaymentListKey = typeof ONYXKEYS.BANK_ACCOUNT_LIST | typeof ONYXKEYS.FUND_L * @param paymentListKey The onyx key for the provided payment method * @param paymentMethodID */ -function clearDeletePaymentMethodError(paymentListKey: PaymentListKey, paymentMethodID: string) { +function clearDeletePaymentMethodError(paymentListKey: PaymentListKey, paymentMethodID: number) { Onyx.merge(paymentListKey, { [paymentMethodID]: { pendingAction: null, @@ -334,7 +334,7 @@ function clearDeletePaymentMethodError(paymentListKey: PaymentListKey, paymentMe * @param paymentListKey The onyx key for the provided payment method * @param paymentMethodID */ -function clearAddPaymentMethodError(paymentListKey: PaymentListKey, paymentMethodID: string) { +function clearAddPaymentMethodError(paymentListKey: PaymentListKey, paymentMethodID: number) { Onyx.merge(paymentListKey, { [paymentMethodID]: null, }); diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index 9cceaa60c10c..e7c6ba37ed9f 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -1,17 +1,7 @@ -import {FlashList} from '@shopify/flash-list'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {ReactElement, Ref, useCallback, useMemo} from 'react'; -import {GestureResponderEvent, StyleProp, View, ViewStyle} from 'react-native'; -import {OnyxEntry, withOnyx} from 'react-native-onyx'; -import {TupleToUnion, ValueOf} from 'type-fest'; -import _ from 'underscore'; -import bankAccountPropTypes from '@components/bankAccountPropTypes'; import Button from '@components/Button'; -import cardPropTypes from '@components/cardPropTypes'; import FormAlertWrapper from '@components/FormAlertWrapper'; import getBankIcon from '@components/Icon/BankIcons'; -import {BankName} from '@components/Icon/BankIconsUtils'; +import type { BankName } from '@components/Icon/BankIconsUtils'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; @@ -24,17 +14,26 @@ import * as CardUtils from '@libs/CardUtils'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import * as PaymentUtils from '@libs/PaymentUtils'; -import stylePropTypes from '@styles/stylePropTypes'; -import variables from '@styles/variables'; -import * as PaymentMethods from '@userActions/PaymentMethods'; +import { FlashList } from '@shopify/flash-list'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {AccountData, BankAccountList, Card, CardList, FundList, UserWallet} from '@src/types/onyx'; -import PaymentMethod from '@src/types/onyx/PaymentMethod'; +import type { AccountData, BankAccountList, CardList, FundList } from '@src/types/onyx'; +import type PaymentMethod from '@src/types/onyx/PaymentMethod'; +import type IconAsset from '@src/types/utils/IconAsset'; +import variables from '@styles/variables'; +import * as PaymentMethods from '@userActions/PaymentMethods'; +import type { ReactElement, Ref } from 'react'; +import React, { useCallback, useMemo } from 'react'; +import type { GestureResponderEvent, StyleProp, ViewStyle } from 'react-native'; +import { View } from 'react-native'; +import type { OnyxEntry } from 'react-native-onyx'; +import { withOnyx } from 'react-native-onyx'; +import type { TupleToUnion, ValueOf } from 'type-fest'; +import _ from 'lodash'; +import type { RenderSuggestionMenuItemProps } from '@components/AutoCompleteSuggestions/types'; const FILTER_TYPES = [CONST.PAYMENT_METHODS.DEBIT_CARD, CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT, ''] as const; -const ACTION_PAYMENT_METHOD_TYPES = [...Object.values(CONST.PAYMENT_METHODS), ''] as const; type PaymentMethodListOnyxProps = { /** List of bank accounts */ @@ -48,14 +47,11 @@ type PaymentMethodListOnyxProps = { /** Are we loading payment methods? */ isLoadingPaymentMethods: OnyxEntry; - - /** User wallet props */ - userWallet: OnyxEntry; }; type PaymentMethodListProps = PaymentMethodListOnyxProps & { /** Type of active/highlighted payment method */ - actionPaymentMethodType?: TupleToUnion; + actionPaymentMethodType?: string; /** ID of active/highlighted payment method */ activePaymentMethodID?: string | number; @@ -96,7 +92,14 @@ type PaymentMethodListProps = PaymentMethodListOnyxProps & { shouldShowEmptyListMessage?: boolean; /** What to do when a menu item is pressed */ - onPress: (event?: GestureResponderEvent | KeyboardEvent, accountType?: string, accountData?: AccountData, isDefault?: boolean, methodID?: number) => void | Promise; + onPress: ( + event?: GestureResponderEvent | KeyboardEvent, + accountType?: string, + accountData?: AccountData, + icon?: IconAsset, + isDefault?: boolean, + methodID?: number, + ) => void; }; type PaymentMethodItem = PaymentMethod & { @@ -111,7 +114,7 @@ type PaymentMethodItem = PaymentMethod & { function dismissError(item: PaymentMethod) { const isBankAccount = item.accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT; const paymentList = isBankAccount ? ONYXKEYS.BANK_ACCOUNT_LIST : ONYXKEYS.FUND_LIST; - const paymentID = isBankAccount ? lodashGet(item, ['accountData', 'bankAccountID'], '') : lodashGet(item, ['accountData', 'fundID'], ''); + const paymentID = isBankAccount ? item.accountData?.bankAccountID ?? '' : item.accountData?.fundID ?? ''; if (!paymentID) { Log.info('Unable to clear payment method error: ', undefined, item); @@ -136,9 +139,8 @@ function shouldShowDefaultBadge(filteredPaymentMethods: PaymentMethod[], isDefau return false; } - const defaultablePaymentMethodCount = _.filter( - filteredPaymentMethods, - (method) => method.accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT || method.accountType === CONST.PAYMENT_METHODS.DEBIT_CARD, + const defaultablePaymentMethodCount = filteredPaymentMethods.filter( + (method) => method.accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT ?? method.accountType === CONST.PAYMENT_METHODS.DEBIT_CARD, ).length; return defaultablePaymentMethodCount > 1; } @@ -148,7 +150,7 @@ function isPaymentMethodActive(actionPaymentMethodType: string, activePaymentMet } function keyExtractor(item: PaymentMethod) { - return item.key || ''; + return item.key ?? ''; } function PaymentMethodList({ @@ -181,13 +183,13 @@ function PaymentMethodList({ if (shouldShowAssignedCards) { const assignedCards = _.chain(cardList) // Filter by physical, active cards associated with a domain - .filter((card) => !card.isVirtual && card.domainName && CONST.EXPENSIFY_CARD.ACTIVE_STATES.includes(card.state ?? 0)) + .filter((card) => !card.isVirtual && !!card.domainName && CONST.EXPENSIFY_CARD.ACTIVE_STATES.includes(card.state ?? 0)) .sortBy((card) => !CardUtils.isExpensifyCard(card.cardID)) .value(); - const numberPhysicalExpensifyCards = _.filter(assignedCards, (card) => CardUtils.isExpensifyCard(card.cardID)).length; + const numberPhysicalExpensifyCards = assignedCards.filter((card) => CardUtils.isExpensifyCard(card.cardID)).length; - return _.map(assignedCards, (card) => { + return assignedCards.map((card) => { const isExpensifyCard = CardUtils.isExpensifyCard(card.cardID); const icon = getBankIcon({bankName: card.bank as BankName, isCard: true, styles}); @@ -202,34 +204,33 @@ function PaymentMethodList({ interactive: isExpensifyCard, canDismissError: isExpensifyCard, errors: card.errors, - brickRoadIndicator: card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN || card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL ? 'error' : null, + brickRoadIndicator: card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN ?? card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL ? 'error' : null, ...icon, }; }); } - const paymentCardList = fundList || {}; + const paymentCardList = fundList ?? {}; // Hide any billing cards that are not P2P debit cards for now because you cannot make them your default method, or delete them - const filteredCardList = _.filter(paymentCardList, (card) => !!card.accountData?.additionalData?.isP2PDebitCard); + const filteredCardList = Object.values(paymentCardList).filter((card) => !!card.accountData?.additionalData?.isP2PDebitCard); let combinedPaymentMethods = PaymentUtils.formatPaymentMethods(bankAccountList ?? {}, filteredCardList, styles); if (!_.isEmpty(filterType)) { - combinedPaymentMethods = _.filter(combinedPaymentMethods, (paymentMethod) => paymentMethod.accountType === filterType); + combinedPaymentMethods = combinedPaymentMethods.filter((paymentMethod) => paymentMethod.accountType === filterType); } if (!isOffline) { - combinedPaymentMethods = _.filter( - combinedPaymentMethods, - (paymentMethod) => paymentMethod.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !_.isEmpty(paymentMethod.errors), + combinedPaymentMethods = combinedPaymentMethods.filter( + (paymentMethod) => paymentMethod.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ?? !_.isEmpty(paymentMethod.errors), ); } - combinedPaymentMethods = _.map(combinedPaymentMethods, (paymentMethod) => { + combinedPaymentMethods = combinedPaymentMethods.map((paymentMethod) => { const isMethodActive = isPaymentMethodActive(actionPaymentMethodType, activePaymentMethodID, paymentMethod); return { ...paymentMethod, - onPress: (e: GestureResponderEvent) => onPress(e, paymentMethod.accountType, paymentMethod.accountData, paymentMethod.isDefault, paymentMethod.methodID), + onPress: (e: GestureResponderEvent) => onPress(e, paymentMethod.accountType, paymentMethod.accountData, paymentMethod.icon, paymentMethod.isDefault, paymentMethod.methodID), wrapperStyle: isMethodActive ? [StyleUtils.getButtonBackgroundColorStyle(CONST.BUTTON_STATES.PRESSED)] : null, disabled: paymentMethod.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, }; @@ -240,8 +241,6 @@ function PaymentMethodList({ /** * Render placeholder when there are no payments methods - * - * @return {React.Component} */ const renderListEmptyComponent = () => {translate('paymentMethodList.addFirstPaymentMethod')}; @@ -263,7 +262,7 @@ function PaymentMethodList({ * Create a menuItem for each passed paymentMethod */ const renderItem = useCallback( - ({item}: {item: PaymentMethodItem}) => ( + ({item}: RenderSuggestionMenuItemProps) => ( dismissError(item)} pendingAction={item.pendingAction} @@ -278,8 +277,8 @@ function PaymentMethodList({ icon={item.icon} disabled={item.disabled} displayInDefaultIconColor - iconHeight={item.iconHeight || item.iconSize} - iconWidth={item.iconWidth || item.iconSize} + iconHeight={item.iconHeight ?? item.iconSize} + iconWidth={item.iconWidth ?? item.iconSize} iconStyles={item.iconStyles} badgeText={shouldShowDefaultBadge(filteredPaymentMethods, item.isDefault) ? translate('paymentMethodList.defaultPaymentMethod') : undefined} wrapperStyle={styles.paymentMethod} @@ -317,7 +316,7 @@ function PaymentMethodList({ text={translate('paymentMethodList.addPaymentMethod')} icon={Expensicons.CreditCard} onPress={onPress} - isDisabled={isLoadingPaymentMethods || isFormOffline} + isDisabled={isLoadingPaymentMethods ?? isFormOffline} style={[styles.mh4, styles.buttonCTA]} iconStyles={[styles.buttonCTAIcon]} key="addPaymentMethodButton" @@ -348,7 +347,4 @@ export default withOnyx({ isLoadingPaymentMethods: { key: ONYXKEYS.IS_LOADING_PAYMENT_METHODS, }, - userWallet: { - key: ONYXKEYS.USER_WALLET, - }, })(PaymentMethodList); diff --git a/src/pages/settings/Wallet/WalletEmptyState.tsx b/src/pages/settings/Wallet/WalletEmptyState.tsx index 4ef9488368cb..4060a3695b92 100644 --- a/src/pages/settings/Wallet/WalletEmptyState.tsx +++ b/src/pages/settings/Wallet/WalletEmptyState.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React from 'react'; import Button from '@components/Button'; import FeatureList from '@components/FeatureList'; diff --git a/src/pages/settings/Wallet/WalletPage/CardDetails.tsx b/src/pages/settings/Wallet/WalletPage/CardDetails.tsx index 09a7d937053b..a21172da2832 100644 --- a/src/pages/settings/Wallet/WalletPage/CardDetails.tsx +++ b/src/pages/settings/Wallet/WalletPage/CardDetails.tsx @@ -1,7 +1,7 @@ -import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; -import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import { withOnyx} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import PressableWithDelayToggle from '@components/Pressable/PressableWithDelayToggle'; @@ -14,7 +14,7 @@ import Navigation from '@libs/Navigation/Navigation'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {PrivatePersonalDetails} from '@src/types/onyx'; +import type {PrivatePersonalDetails} from '@src/types/onyx'; const defaultPrivatePersonalDetails = { address: { @@ -46,7 +46,7 @@ type CardDetailsProps = CardDetailsOnyxProps & { domain: string; }; -function CardDetails({pan = '', expiration = '', cvv = '', privatePersonalDetails = defaultPrivatePersonalDetails, domain}: CardDetailsProps) { +function CardDetails({pan = '', expiration = '', cvv = '', privatePersonalDetails, domain}: CardDetailsProps) { const styles = useThemeStyles(); usePrivatePersonalDetails(); const {translate} = useLocalize(); @@ -87,7 +87,7 @@ function CardDetails({pan = '', expiration = '', cvv = '', privatePersonalDetail /> { // In order to prevent a loop, only update state of the spinner if there is a change - const showLoadingSpinner = isLoadingPaymentMethods || false; + const showLoadingSpinner = isLoadingPaymentMethods ?? false; if (showLoadingSpinner !== shouldShowLoadingSpinner) { setShouldShowLoadingSpinner(!!isLoadingPaymentMethods && !network.isOffline); } @@ -107,8 +103,6 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi /** * Set position of the payment menu - * - * @param {Object} position */ const setMenuPosition = useCallback(() => { if (!paymentMethodButtonRef.current) { @@ -150,14 +144,15 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi /** * Display the delete/default menu, or the add payment method menu - * - * @param {Object} nativeEvent - * @param {String} accountType - * @param {String} account - * @param {Boolean} isDefault - * @param {String|Number} methodID */ - const paymentMethodPressed = (nativeEvent?: GestureResponderEvent | KeyboardEvent, accountType?: string, account?: AccountData, isDefault?: boolean, methodID?: string | number) => { + const paymentMethodPressed = ( + nativeEvent?: GestureResponderEvent | KeyboardEvent, + accountType?: string, + account?: AccountData, + icon?: IconAsset, + isDefault?: boolean, + methodID?: string | number, + ) => { if (shouldShowAddPaymentMenu) { setShouldShowAddPaymentMenu(false); return; @@ -171,27 +166,27 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi // The delete/default menu if (accountType) { - let formattedSelectedPaymentMethod = { + let formattedSelectedPaymentMethod: FormattedSelectedPaymentMethod = { title: '', }; if (accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { formattedSelectedPaymentMethod = { title: account?.addressName ?? '', - icon: account?.icon, + icon, description: PaymentUtils.getPaymentMethodDescription(accountType, account), type: CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT, }; } else if (accountType === CONST.PAYMENT_METHODS.DEBIT_CARD) { formattedSelectedPaymentMethod = { title: account?.addressName ?? '', - icon: account?.icon, + icon, description: PaymentUtils.getPaymentMethodDescription(accountType, account), type: CONST.PAYMENT_METHODS.DEBIT_CARD, }; } setPaymentMethod({ isSelectedPaymentMethodDefault: !!isDefault, - selectedPaymentMethod: account || {}, + selectedPaymentMethod: account ?? {}, selectedPaymentMethodType: accountType, formattedSelectedPaymentMethod, methodID: methodID ?? '', @@ -222,7 +217,7 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi return; } - if (paymentType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT || paymentType === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT) { + if (paymentType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT ?? paymentType === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT) { BankAccounts.openPersonalBankAccountSetupView(); return; } @@ -232,7 +227,6 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi /** * Hide the default / delete modal - * @param {boolean} shouldClearSelectedData - Clear selected payment method data if true */ const hideDefaultDeleteMenu = useCallback(() => { setShouldShowDefaultDeleteMenu(false); @@ -240,16 +234,16 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi }, [setShouldShowDefaultDeleteMenu, setShowConfirmDeleteModal]); const makeDefaultPaymentMethod = useCallback(() => { - const paymentCardList = fundList || {}; + const paymentCardList = fundList ?? {}; // Find the previous default payment method so we can revert if the MakeDefaultPaymentMethod command errors - const paymentMethods = PaymentUtils.formatPaymentMethods(bankAccountList || {}, paymentCardList, styles); + const paymentMethods = PaymentUtils.formatPaymentMethods(bankAccountList ?? {}, paymentCardList, styles); - const previousPaymentMethod = _.find(paymentMethods, (method) => !!method.isDefault); - const currentPaymentMethod = _.find(paymentMethods, (method) => method.methodID === paymentMethod.methodID); + const previousPaymentMethod = paymentMethods.find((method) => !!method.isDefault); + const currentPaymentMethod = paymentMethods.find((method) => method.methodID === paymentMethod.methodID); if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { - PaymentMethods.makeDefaultPaymentMethod(paymentMethod.selectedPaymentMethod.bankAccountID || null, null, previousPaymentMethod, currentPaymentMethod); + PaymentMethods.makeDefaultPaymentMethod(paymentMethod.selectedPaymentMethod.bankAccountID ?? null, null, previousPaymentMethod, currentPaymentMethod); } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { - PaymentMethods.makeDefaultPaymentMethod(null, paymentMethod.selectedPaymentMethod.fundID || null, previousPaymentMethod, currentPaymentMethod); + PaymentMethods.makeDefaultPaymentMethod(null, paymentMethod.selectedPaymentMethod.fundID ?? null, previousPaymentMethod, currentPaymentMethod); } }, [ paymentMethod.methodID, @@ -346,7 +340,7 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi !(paymentMethod.formattedSelectedPaymentMethod.type === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT && paymentMethod.selectedPaymentMethod.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS); // Determines whether or not the modal popup is mounted from the bottom of the screen instead of the side mount on Web or Desktop screens - const isPopoverBottomMount = anchorPosition.anchorPositionTop === 0 || isSmallScreenWidth; + const isPopoverBottomMount = anchorPosition.anchorPositionTop === 0 ?? isSmallScreenWidth; const alertTextStyle = [styles.inlineSystemMessage, styles.flexShrink1]; const alertViewStyle = [styles.flexRow, styles.alignItemsCenter, styles.w100, styles.ph5]; @@ -395,9 +389,10 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi )} navigateToWalletOrTransferBalancePage(source)} onSelectPaymentMethod={(selectedPaymentMethod: string) => { - if (hasActivatedWallet || selectedPaymentMethod !== CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { + if (hasActivatedWallet ?? selectedPaymentMethod !== CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { return; } // To allow upgrading to a gold wallet, continue with the KYC flow after adding a bank account @@ -488,7 +483,7 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi actionPaymentMethodType={shouldShowDefaultDeleteMenu ? paymentMethod.selectedPaymentMethodType : ''} activePaymentMethodID={shouldShowDefaultDeleteMenu ? getSelectedPaymentMethodID() : ''} buttonRef={addPaymentMethodAnchorRef} - onListContentSizeChange={shouldShowAddPaymentMenu || shouldShowDefaultDeleteMenu ? setMenuPosition : () => {}} + onListContentSizeChange={shouldShowAddPaymentMenu ?? shouldShowDefaultDeleteMenu ? setMenuPosition : () => {}} /> ) : null} @@ -504,7 +499,7 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi actionPaymentMethodType={shouldShowDefaultDeleteMenu ? paymentMethod.selectedPaymentMethodType : ''} activePaymentMethodID={shouldShowDefaultDeleteMenu ? getSelectedPaymentMethodID() : ''} buttonRef={addPaymentMethodAnchorRef} - onListContentSizeChange={shouldShowAddPaymentMenu || shouldShowDefaultDeleteMenu ? setMenuPosition : () => {}} + onListContentSizeChange={shouldShowAddPaymentMenu ?? shouldShowDefaultDeleteMenu ? setMenuPosition : () => {}} shouldEnableScroll={false} style={styles.mt5} /> @@ -525,7 +520,7 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi {isPopoverBottomMount && ( Date: Thu, 11 Jan 2024 15:24:39 +0700 Subject: [PATCH 008/252] resolve conflict --- src/pages/settings/Wallet/PaymentMethodList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index f318b77f8a0a..71c995363666 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -296,7 +296,7 @@ function PaymentMethodList({ return ( <> - + Date: Thu, 11 Jan 2024 15:29:21 +0700 Subject: [PATCH 009/252] lint --- src/components/AddPaymentMethodMenu.js | 10 ++--- .../settings/Wallet/PaymentMethodList.tsx | 39 ++++++++----------- .../Wallet/WalletPage/CardDetails.tsx | 2 +- .../settings/Wallet/WalletPage/WalletPage.tsx | 28 ++++++------- 4 files changed, 36 insertions(+), 43 deletions(-) diff --git a/src/components/AddPaymentMethodMenu.js b/src/components/AddPaymentMethodMenu.js index 033fb8765b6f..803b7f2cdabe 100644 --- a/src/components/AddPaymentMethodMenu.js +++ b/src/components/AddPaymentMethodMenu.js @@ -1,14 +1,14 @@ +import lodashGet from 'lodash/get'; +import PropTypes from 'prop-types'; +import React from 'react'; +import {withOnyx} from 'react-native-onyx'; +import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import iouReportPropTypes from '@pages/iouReportPropTypes'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React from 'react'; -import { withOnyx } from 'react-native-onyx'; -import _ from 'underscore'; import * as Expensicons from './Icon/Expensicons'; import PopoverMenu from './PopoverMenu'; import refPropTypes from './refPropTypes'; diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index 71c995363666..c8dcee62988e 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -1,7 +1,17 @@ +import {FlashList} from '@shopify/flash-list'; +import _ from 'lodash'; +import type {ReactElement, Ref} from 'react'; +import React, {useCallback, useMemo} from 'react'; +import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import type {TupleToUnion, ValueOf} from 'type-fest'; +import type {RenderSuggestionMenuItemProps} from '@components/AutoCompleteSuggestions/types'; import Button from '@components/Button'; import FormAlertWrapper from '@components/FormAlertWrapper'; import getBankIcon from '@components/Icon/BankIcons'; -import type { BankName } from '@components/Icon/BankIconsUtils'; +import type {BankName} from '@components/Icon/BankIconsUtils'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; @@ -14,24 +24,14 @@ import * as CardUtils from '@libs/CardUtils'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import * as PaymentUtils from '@libs/PaymentUtils'; -import { FlashList } from '@shopify/flash-list'; +import variables from '@styles/variables'; +import * as PaymentMethods from '@userActions/PaymentMethods'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type { AccountData, BankAccountList, CardList, FundList } from '@src/types/onyx'; +import type {AccountData, BankAccountList, CardList, FundList} from '@src/types/onyx'; import type PaymentMethod from '@src/types/onyx/PaymentMethod'; import type IconAsset from '@src/types/utils/IconAsset'; -import variables from '@styles/variables'; -import * as PaymentMethods from '@userActions/PaymentMethods'; -import type { ReactElement, Ref } from 'react'; -import React, { useCallback, useMemo } from 'react'; -import type { GestureResponderEvent, StyleProp, ViewStyle } from 'react-native'; -import { View } from 'react-native'; -import type { OnyxEntry } from 'react-native-onyx'; -import { withOnyx } from 'react-native-onyx'; -import type { TupleToUnion, ValueOf } from 'type-fest'; -import _ from 'lodash'; -import type { RenderSuggestionMenuItemProps } from '@components/AutoCompleteSuggestions/types'; const FILTER_TYPES = [CONST.PAYMENT_METHODS.DEBIT_CARD, CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT, ''] as const; @@ -92,14 +92,7 @@ type PaymentMethodListProps = PaymentMethodListOnyxProps & { shouldShowEmptyListMessage?: boolean; /** What to do when a menu item is pressed */ - onPress: ( - event?: GestureResponderEvent | KeyboardEvent, - accountType?: string, - accountData?: AccountData, - icon?: IconAsset, - isDefault?: boolean, - methodID?: number, - ) => void; + onPress: (event?: GestureResponderEvent | KeyboardEvent, accountType?: string, accountData?: AccountData, icon?: IconAsset, isDefault?: boolean, methodID?: number) => void; }; type PaymentMethodItem = PaymentMethod & { @@ -296,7 +289,7 @@ function PaymentMethodList({ return ( <> - + { // In order to prevent a loop, only update state of the spinner if there is a change From ec74f831b88083175fa147d3ebb69cea73d4f842 Mon Sep 17 00:00:00 2001 From: Vadym Date: Mon, 15 Jan 2024 02:10:23 +0000 Subject: [PATCH 010/252] chore: migrates reactionPropTypes, BaseReactionList and HeaderReactionList to TS --- ...seReactionList.js => BaseReactionList.tsx} | 53 +++++++++---------- .../report/ReactionList/HeaderReactionList.js | 48 ----------------- .../ReactionList/HeaderReactionList.tsx | 48 +++++++++++++++++ .../report/ReactionList/reactionPropTypes.js | 17 ------ .../report/ReactionList/reactionPropTypes.ts | 13 +++++ 5 files changed, 86 insertions(+), 93 deletions(-) rename src/pages/home/report/ReactionList/{BaseReactionList.js => BaseReactionList.tsx} (69%) delete mode 100644 src/pages/home/report/ReactionList/HeaderReactionList.js create mode 100644 src/pages/home/report/ReactionList/HeaderReactionList.tsx delete mode 100644 src/pages/home/report/ReactionList/reactionPropTypes.js create mode 100644 src/pages/home/report/ReactionList/reactionPropTypes.ts diff --git a/src/pages/home/report/ReactionList/BaseReactionList.js b/src/pages/home/report/ReactionList/BaseReactionList.tsx similarity index 69% rename from src/pages/home/report/ReactionList/BaseReactionList.js rename to src/pages/home/report/ReactionList/BaseReactionList.tsx index 2d881d080c31..82541b28c9c8 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.js +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -1,36 +1,34 @@ /* eslint-disable rulesdir/onyx-props-must-have-default */ import Str from 'expensify-common/lib/str'; -import PropTypes from 'prop-types'; import React from 'react'; -import {FlatList} from 'react-native'; +import {FlatList, type FlatListProps} from 'react-native'; import OptionRow from '@components/OptionRow'; -import participantPropTypes from '@components/participantPropTypes'; -import withWindowDimensions from '@components/withWindowDimensions'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import Navigation from '@libs/Navigation/Navigation'; import * as UserUtils from '@libs/UserUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; +import type {PersonalDetails} from '@src/types/onyx'; import HeaderReactionList from './HeaderReactionList'; -import reactionPropTypes from './reactionPropTypes'; +import type {ReactionListProps} from './reactionPropTypes'; -const propTypes = { +type BaseReactionListProps = ReactionListProps & { /** * Array of personal detail objects */ - users: PropTypes.arrayOf(participantPropTypes).isRequired, + users: PersonalDetails[]; /** * Returns true if the current account has reacted to the report action (with the given skin tone). */ - hasUserReacted: PropTypes.bool, + hasUserReacted: boolean; - ...reactionPropTypes, -}; - -const defaultProps = { - hasUserReacted: false, + /** + * Returns true if the reaction list is visible + */ + isVisible: boolean; }; /** @@ -39,7 +37,7 @@ const defaultProps = { * @param {Number} index * @return {String} */ -const keyExtractor = (item, index) => `${item.login}+${index}`; +const keyExtractor: FlatListProps['keyExtractor'] = (item, index) => `${item.login}+${index}`; /** * This function will be used with FlatList getItemLayout property for optimization purpose that allows skipping @@ -51,14 +49,15 @@ const keyExtractor = (item, index) => `${item.login}+${index}`; * @param {Number} index row index * @returns {Object} */ -const getItemLayout = (_, index) => ({ +const getItemLayout = (_: any, index: number): {length: number; offset: number; index: number} => ({ index, length: variables.listItemHeightNormal, offset: variables.listItemHeightNormal * index, }); -function BaseReactionList(props) { - const styles = useThemeStyles(); +function BaseReactionList(props: BaseReactionListProps) { + const {isSmallScreenWidth} = useWindowDimensions(); + const {hoveredComponentBG, reactionListContainer, reactionListContainerFixedWidth, pv2} = useThemeStyles(); if (!props.isVisible) { return null; } @@ -72,25 +71,25 @@ function BaseReactionList(props) { * @param {Object} params.item * @return {React.Component} */ - const renderItem = ({item}) => ( + const renderItem: FlatListProps['renderItem'] = ({item}) => ( { - props.onClose(); + props.onClose && props.onClose(); Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID)); }} option={{ - text: Str.removeSMSDomain(item.displayName), + reportID: String(item.accountID), + text: Str.removeSMSDomain(item.displayName || ''), alternateText: Str.removeSMSDomain(item.login || ''), participantsList: [item], icons: [ { id: item.accountID, source: UserUtils.getAvatar(item.avatar, item.accountID), - name: item.login, + name: item.login || '', type: CONST.ICON_TYPE_AVATAR, }, ], @@ -113,15 +112,13 @@ function BaseReactionList(props) { renderItem={renderItem} keyExtractor={keyExtractor} getItemLayout={getItemLayout} - contentContainerStyle={styles.pv2} - style={[styles.reactionListContainer, !props.isSmallScreenWidth && styles.reactionListContainerFixedWidth]} + contentContainerStyle={pv2} + style={[reactionListContainer, !isSmallScreenWidth && reactionListContainerFixedWidth]} /> ); } -BaseReactionList.propTypes = propTypes; -BaseReactionList.defaultProps = defaultProps; BaseReactionList.displayName = 'BaseReactionList'; -export default withWindowDimensions(BaseReactionList); +export default BaseReactionList; diff --git a/src/pages/home/report/ReactionList/HeaderReactionList.js b/src/pages/home/report/ReactionList/HeaderReactionList.js deleted file mode 100644 index 04b124f969a9..000000000000 --- a/src/pages/home/report/ReactionList/HeaderReactionList.js +++ /dev/null @@ -1,48 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {View} from 'react-native'; -import Text from '@components/Text'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; -import useStyleUtils from '@hooks/useStyleUtils'; -import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; -import * as EmojiUtils from '@libs/EmojiUtils'; -import reactionPropTypes from './reactionPropTypes'; - -const propTypes = { - ...reactionPropTypes, - ...withLocalizePropTypes, - ...windowDimensionsPropTypes, - - /** - * Returns true if the current account has reacted to the report action (with the given skin tone). - */ - hasUserReacted: PropTypes.bool, -}; - -const defaultProps = { - hasUserReacted: false, -}; - -function HeaderReactionList(props) { - const styles = useThemeStyles(); - const StyleUtils = useStyleUtils(); - return ( - - - - {props.emojiCodes.join('')} - {props.emojiCount} - - {`:${EmojiUtils.getLocalizedEmojiName(props.emojiName, props.preferredLocale)}:`} - - - ); -} - -HeaderReactionList.propTypes = propTypes; -HeaderReactionList.defaultProps = defaultProps; -HeaderReactionList.displayName = 'HeaderReactionList'; - -export default compose(withWindowDimensions, withLocalize)(HeaderReactionList); diff --git a/src/pages/home/report/ReactionList/HeaderReactionList.tsx b/src/pages/home/report/ReactionList/HeaderReactionList.tsx new file mode 100644 index 000000000000..551059b697e6 --- /dev/null +++ b/src/pages/home/report/ReactionList/HeaderReactionList.tsx @@ -0,0 +1,48 @@ +import {View} from 'react-native'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import * as EmojiUtils from '@libs/EmojiUtils'; +import type {ReactionListProps} from './reactionPropTypes'; + +type HeaderReactionListProps = ReactionListProps & { + /** + * Returns true if the current account has reacted to the report action (with the given skin tone). + */ + hasUserReacted: boolean; +}; + +function HeaderReactionList(props: HeaderReactionListProps) { + const { + flexRow, + justifyContentBetween, + alignItemsCenter, + emojiReactionListHeader, + pt4, + emojiReactionListHeaderBubble, + miniQuickEmojiReactionText, + reactionCounterText, + reactionListHeaderText, + } = useThemeStyles(); + const {getEmojiReactionBubbleStyle, getEmojiReactionBubbleTextStyle, getEmojiReactionCounterTextStyle} = useStyleUtils(); + const {preferredLocale} = useLocalize(); + const {isSmallScreenWidth} = useWindowDimensions(); + + return ( + + + + {props.emojiCodes.join('')} + {props.emojiCount} + + {`:${EmojiUtils.getLocalizedEmojiName(props.emojiName, preferredLocale)}:`} + + + ); +} + +HeaderReactionList.displayName = 'HeaderReactionList'; + +export default HeaderReactionList; diff --git a/src/pages/home/report/ReactionList/reactionPropTypes.js b/src/pages/home/report/ReactionList/reactionPropTypes.js deleted file mode 100644 index 3421af230399..000000000000 --- a/src/pages/home/report/ReactionList/reactionPropTypes.js +++ /dev/null @@ -1,17 +0,0 @@ -import PropTypes from 'prop-types'; - -const propTypes = { - /** Hide the ReactionList modal popover */ - onClose: PropTypes.func, - - /** The emoji codes */ - emojiCodes: PropTypes.arrayOf(PropTypes.string).isRequired, - - /** The name of the emoji */ - emojiName: PropTypes.string.isRequired, - - /** Count of the emoji */ - emojiCount: PropTypes.number.isRequired, -}; - -export default propTypes; diff --git a/src/pages/home/report/ReactionList/reactionPropTypes.ts b/src/pages/home/report/ReactionList/reactionPropTypes.ts new file mode 100644 index 000000000000..d88316754600 --- /dev/null +++ b/src/pages/home/report/ReactionList/reactionPropTypes.ts @@ -0,0 +1,13 @@ +export type ReactionListProps = { + /** Hide the ReactionList modal popover */ + onClose?: () => void; + + /** The emoji codes */ + emojiCodes: string[]; + + /** The name of the emoji */ + emojiName: string; + + /** Count of the emoji */ + emojiCount: number; +}; From cb70663a543eaee30f6f0a0302e740b8f5a70d5f Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 15 Jan 2024 16:51:24 +0700 Subject: [PATCH 011/252] change ?? to || --- src/libs/actions/PaymentMethods.ts | 6 +++-- .../settings/Wallet/PaymentMethodList.tsx | 18 ++++++++++----- .../settings/Wallet/WalletEmptyState.tsx | 3 +++ .../Wallet/WalletPage/CardDetails.tsx | 3 ++- .../settings/Wallet/WalletPage/WalletPage.tsx | 23 +++++++++++-------- 5 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/libs/actions/PaymentMethods.ts b/src/libs/actions/PaymentMethods.ts index c152a968f030..24899087c46d 100644 --- a/src/libs/actions/PaymentMethods.ts +++ b/src/libs/actions/PaymentMethods.ts @@ -83,7 +83,8 @@ function getMakeDefaultPaymentOnyxData( onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.USER_WALLET, value: { - walletLinkedAccountID: bankAccountID ?? fundID, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + walletLinkedAccountID: bankAccountID || fundID, walletLinkedAccountType: bankAccountID ? CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT : CONST.PAYMENT_METHODS.DEBIT_CARD, // Only clear the error if this is optimistic data. If this is failure data, we do not want to clear the error that came from the server. errors: null, @@ -93,7 +94,8 @@ function getMakeDefaultPaymentOnyxData( onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.USER_WALLET, value: { - walletLinkedAccountID: bankAccountID ?? fundID, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + walletLinkedAccountID: bankAccountID || fundID, walletLinkedAccountType: bankAccountID ? CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT : CONST.PAYMENT_METHODS.DEBIT_CARD, }, }, diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index c8dcee62988e..db04bfb9f183 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -133,7 +133,8 @@ function shouldShowDefaultBadge(filteredPaymentMethods: PaymentMethod[], isDefau } const defaultablePaymentMethodCount = filteredPaymentMethods.filter( - (method) => method.accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT ?? method.accountType === CONST.PAYMENT_METHODS.DEBIT_CARD, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + (method) => method.accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT || method.accountType === CONST.PAYMENT_METHODS.DEBIT_CARD, ).length; return defaultablePaymentMethodCount > 1; } @@ -197,7 +198,8 @@ function PaymentMethodList({ interactive: isExpensifyCard, canDismissError: isExpensifyCard, errors: card.errors, - brickRoadIndicator: card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN ?? card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL ? 'error' : null, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + brickRoadIndicator: card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN || card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL ? 'error' : null, ...icon, }; }); @@ -215,7 +217,8 @@ function PaymentMethodList({ if (!isOffline) { combinedPaymentMethods = combinedPaymentMethods.filter( - (paymentMethod) => paymentMethod.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ?? !_.isEmpty(paymentMethod.errors), + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + (paymentMethod) => paymentMethod.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !_.isEmpty(paymentMethod.errors), ); } @@ -270,8 +273,10 @@ function PaymentMethodList({ icon={item.icon} disabled={item.disabled} displayInDefaultIconColor - iconHeight={item.iconHeight ?? item.iconSize} - iconWidth={item.iconWidth ?? item.iconSize} + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + iconHeight={item.iconHeight || item.iconSize} + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + iconWidth={item.iconWidth || item.iconSize} iconStyles={item.iconStyles} badgeText={shouldShowDefaultBadge(filteredPaymentMethods, item.isDefault) ? translate('paymentMethodList.defaultPaymentMethod') : undefined} wrapperStyle={styles.paymentMethod} @@ -309,7 +314,8 @@ function PaymentMethodList({ text={translate('paymentMethodList.addPaymentMethod')} icon={Expensicons.CreditCard} onPress={onPress} - isDisabled={isLoadingPaymentMethods ?? isFormOffline} + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + isDisabled={isLoadingPaymentMethods || isFormOffline} style={[styles.mh4, styles.buttonCTA]} iconStyles={[styles.buttonCTAIcon]} key="addPaymentMethodButton" diff --git a/src/pages/settings/Wallet/WalletEmptyState.tsx b/src/pages/settings/Wallet/WalletEmptyState.tsx index 4060a3695b92..bfec7fa6314b 100644 --- a/src/pages/settings/Wallet/WalletEmptyState.tsx +++ b/src/pages/settings/Wallet/WalletEmptyState.tsx @@ -11,6 +11,7 @@ import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; type WalletEmptyStateProps = { + /** The function that is called when a menu item is pressed */ onAddPaymentMethod: () => void; }; @@ -56,4 +57,6 @@ function WalletEmptyState({onAddPaymentMethod}: WalletEmptyStateProps) { ); } +WalletEmptyState.displayName = 'WalletEmptyState'; + export default WalletEmptyState; diff --git a/src/pages/settings/Wallet/WalletPage/CardDetails.tsx b/src/pages/settings/Wallet/WalletPage/CardDetails.tsx index 2732e817ae47..ce2dbc6b2cb5 100644 --- a/src/pages/settings/Wallet/WalletPage/CardDetails.tsx +++ b/src/pages/settings/Wallet/WalletPage/CardDetails.tsx @@ -87,7 +87,8 @@ function CardDetails({pan = '', expiration = '', cvv = '', privatePersonalDetail /> navigateToWalletOrTransferBalancePage(source)} onSelectPaymentMethod={(selectedPaymentMethod: string) => { - if (hasActivatedWallet ?? selectedPaymentMethod !== CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + if (hasActivatedWallet || selectedPaymentMethod !== CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { return; } // To allow upgrading to a gold wallet, continue with the KYC flow after adding a bank account @@ -483,7 +486,8 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi actionPaymentMethodType={shouldShowDefaultDeleteMenu ? paymentMethod.selectedPaymentMethodType : ''} activePaymentMethodID={shouldShowDefaultDeleteMenu ? getSelectedPaymentMethodID() : ''} buttonRef={addPaymentMethodAnchorRef} - onListContentSizeChange={shouldShowAddPaymentMenu ?? shouldShowDefaultDeleteMenu ? setMenuPosition : () => {}} + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + onListContentSizeChange={shouldShowAddPaymentMenu || shouldShowDefaultDeleteMenu ? setMenuPosition : () => {}} /> ) : null} @@ -499,7 +503,8 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi actionPaymentMethodType={shouldShowDefaultDeleteMenu ? paymentMethod.selectedPaymentMethodType : ''} activePaymentMethodID={shouldShowDefaultDeleteMenu ? getSelectedPaymentMethodID() : ''} buttonRef={addPaymentMethodAnchorRef} - onListContentSizeChange={shouldShowAddPaymentMenu ?? shouldShowDefaultDeleteMenu ? setMenuPosition : () => {}} + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + onListContentSizeChange={shouldShowAddPaymentMenu || shouldShowDefaultDeleteMenu ? setMenuPosition : () => {}} shouldEnableScroll={false} style={styles.mt5} /> @@ -520,7 +525,7 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi {isPopoverBottomMount && ( Date: Tue, 16 Jan 2024 03:11:39 +0100 Subject: [PATCH 012/252] chore: migrates BasePopoverReactionList and ReactionList to TS --- ...ionList.js => BasePopoverReactionList.tsx} | 104 +++++++++++++----- .../ReactionList/PopoverReactionList/index.js | 67 ----------- .../PopoverReactionList/index.tsx | 60 ++++++++++ 3 files changed, 136 insertions(+), 95 deletions(-) rename src/pages/home/report/ReactionList/PopoverReactionList/{BasePopoverReactionList.js => BasePopoverReactionList.tsx} (65%) delete mode 100644 src/pages/home/report/ReactionList/PopoverReactionList/index.js create mode 100644 src/pages/home/report/ReactionList/PopoverReactionList/index.tsx diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.js b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx similarity index 65% rename from src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.js rename to src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx index a06ed18de957..8f532253fc43 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.js +++ b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx @@ -1,36 +1,63 @@ import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React from 'react'; import {Dimensions} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import _ from 'underscore'; import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; -import EmojiReactionsPropTypes from '@components/Reactions/EmojiReactionsPropTypes'; -import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import withCurrentUserPersonalDetails, {type WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import withLocalize, {type WithLocalizeProps} from '@components/withLocalize'; import compose from '@libs/compose'; import * as EmojiUtils from '@libs/EmojiUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import BaseReactionList from '@pages/home/report/ReactionList/BaseReactionList'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {ReportActionReactions} from '@src/types/onyx'; +import {ReportActionReaction} from '@src/types/onyx/ReportActionReactions'; -const propTypes = { - reportActionID: PropTypes.string, - emojiName: PropTypes.string, - emojiReactions: EmojiReactionsPropTypes, +type BasePopoverReactionListOnyxProps = { + /** The reactions for the report action */ + emojiReactions: OnyxEntry; +}; + +type BasePopoverReactionListProps = { + /** The ID of the report action */ + reportActionID: string; + + /** The emoji name */ + emojiName: string; - ...withLocalizePropTypes, + /** The ref of the action */ + ref: React.Ref; }; -const defaultProps = { - reportActionID: '', - emojiName: '', - emojiReactions: {}, +type BasePopoverReactionListWithLocalizeProps = WithLocalizeProps & WithCurrentUserPersonalDetailsProps; + +type BasePopoverReactionListPropsWithLocalWithOnyx = BasePopoverReactionListWithLocalizeProps & BasePopoverReactionListOnyxProps & BasePopoverReactionListProps; +type BasePopoverReactionListState = { + /** Whether the popover is visible */ + isPopoverVisible: boolean; + + /** The horizontal and vertical position (relative to the screen) where the popover will display. */ + popoverAnchorPosition: { + horizontal: number; + vertical: number; + }; + + /** The horizontal and vertical position (relative to the screen) where the cursor is. */ + cursorRelativePosition: { + horizontal: number; + vertical: number; + }; }; -class BasePopoverReactionList extends React.Component { - constructor(props) { +class BasePopoverReactionList extends React.Component { + reactionListAnchor: React.RefObject; + dimensionsEventListener: { + remove: () => void; + } | null; + constructor(props: BasePopoverReactionListPropsWithLocalWithOnyx) { super(props); this.state = { @@ -60,8 +87,9 @@ class BasePopoverReactionList extends React.Component { this.dimensionsEventListener = Dimensions.addEventListener('change', this.measureReactionListPosition); } - shouldComponentUpdate(nextProps, nextState) { + shouldComponentUpdate(nextProps: BasePopoverReactionListPropsWithLocalWithOnyx, nextState: BasePopoverReactionListState) { if (!this.state.isPopoverVisible && !nextState.isPopoverVisible) { + // If the popover is not visible, we don't need to update the component return false; } @@ -81,11 +109,13 @@ class BasePopoverReactionList extends React.Component { componentDidUpdate() { if (!this.state.isPopoverVisible) { + // If the popover is not visible, we don't need to update the component return; } // Hide the list when all reactions are removed - const isEmptyList = !_.some(lodashGet(this.props.emojiReactions, [this.props.emojiName, 'users'])); + const emojiReactions = lodashGet(this.props.emojiReactions, [this.props.emojiName, 'users']); + const isEmptyList = !emojiReactions || !_.some(emojiReactions); if (!isEmptyList) { return; } @@ -94,6 +124,7 @@ class BasePopoverReactionList extends React.Component { } componentWillUnmount() { + // Remove the event listener if (!this.dimensionsEventListener) { return; } @@ -106,11 +137,13 @@ class BasePopoverReactionList extends React.Component { * * @returns {Promise} */ - getReactionListMeasuredLocation() { + getReactionListMeasuredLocation(): Promise<{x: number; y: number}> { return new Promise((resolve) => { - if (this.reactionListAnchor.current) { - this.reactionListAnchor.current.measureInWindow((x, y) => resolve({x, y})); + const reactionListAnchor = this.reactionListAnchor.current as HTMLElement & {measureInWindow: (callback: (x: number, y: number) => void) => void}; + if (reactionListAnchor) { + reactionListAnchor.measureInWindow((x, y) => resolve({x, y})); } else { + // If the anchor is not available, we return 0, 0 resolve({x: 0, y: 0}); } }); @@ -123,8 +156,9 @@ class BasePopoverReactionList extends React.Component { * @param {String} emojiName * @returns {Object} */ - getReactionInformation(selectedReaction, emojiName) { + getReactionInformation(selectedReaction: ReportActionReaction | null | undefined, emojiName: string) { if (!selectedReaction) { + // If there is no reaction, we return default values return { emojiName: '', reactionCount: 0, @@ -152,9 +186,18 @@ class BasePopoverReactionList extends React.Component { * @param {Object} [event] - A press event. * @param {Element} reactionListAnchor - reactionListAnchor */ - showReactionList(event, reactionListAnchor) { + showReactionList( + event: { + nativeEvent: { + pageX: number; + pageY: number; + }; + }, + reactionListAnchor: HTMLElement, + ) { + // We get the cursor coordinates and the reactionListAnchor coordinates to calculate the popover position const nativeEvent = event.nativeEvent || {}; - this.reactionListAnchor.current = reactionListAnchor; + this.reactionListAnchor = {current: reactionListAnchor}; this.getReactionListMeasuredLocation().then(({x, y}) => { this.setState({ cursorRelativePosition: { @@ -175,6 +218,7 @@ class BasePopoverReactionList extends React.Component { */ measureReactionListPosition() { if (!this.state.isPopoverVisible) { + // If the popover is not visible, we don't need to update the component return; } this.getReactionListMeasuredLocation().then(({x, y}) => { @@ -200,7 +244,10 @@ class BasePopoverReactionList extends React.Component { } render() { + // Get the selected reaction const selectedReaction = this.state.isPopoverVisible ? lodashGet(this.props.emojiReactions, [this.props.emojiName]) : null; + + // Get the reaction information const {emojiName, emojiCodes, reactionCount, hasUserReacted, users} = this.getReactionInformation(selectedReaction, this.props.emojiName); return ( @@ -215,9 +262,12 @@ class BasePopoverReactionList extends React.Component { fullscreen withoutOverlay anchorRef={this.reactionListAnchor} + anchorAlignment={{ + horizontal: 'left', + vertical: 'top', + }} > ({ emojiReactions: { key: ({reportActionID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, }, diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.js b/src/pages/home/report/ReactionList/PopoverReactionList/index.js deleted file mode 100644 index 9f1e7b3113fc..000000000000 --- a/src/pages/home/report/ReactionList/PopoverReactionList/index.js +++ /dev/null @@ -1,67 +0,0 @@ -import PropTypes from 'prop-types'; -import React, {forwardRef, useImperativeHandle, useRef, useState} from 'react'; -import BasePopoverReactionList from './BasePopoverReactionList'; - -const propTypes = { - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), -}; - -const defaultProps = { - innerRef: () => {}, -}; - -function PopoverReactionList(props) { - const innerReactionListRef = useRef(); - const [reactionListReportActionID, setReactionListReportActionID] = useState(''); - const [reactionListEmojiName, setReactionListEmojiName] = useState(''); - - /** - * Show the ReactionList modal popover. - * - * @param {Object} [event] - A press event. - * @param {Element} reactionListAnchor - reactionListAnchor - * @param {String} emojiName - Name of emoji - * @param {String} reportActionID - ID of the report action - */ - const showReactionList = (event, reactionListAnchor, emojiName, reportActionID) => { - setReactionListReportActionID(reportActionID); - setReactionListEmojiName(emojiName); - innerReactionListRef.current.showReactionList(event, reactionListAnchor); - }; - - const hideReactionList = () => { - innerReactionListRef.current.hideReactionList(); - }; - - /** - * Whether PopoverReactionList is active for the Report Action. - * - * @param {Number|String} actionID - * @return {Boolean} - */ - const isActiveReportAction = (actionID) => Boolean(actionID) && reactionListReportActionID === actionID; - - useImperativeHandle(props.innerRef, () => ({showReactionList, hideReactionList, isActiveReportAction})); - - return ( - - ); -} - -PopoverReactionList.propTypes = propTypes; -PopoverReactionList.defaultProps = defaultProps; -PopoverReactionList.displayName = 'PopoverReactionList'; - -export default React.memo( - forwardRef((props, ref) => ( - - )), -); diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx new file mode 100644 index 000000000000..e4a7aa42791b --- /dev/null +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx @@ -0,0 +1,60 @@ +import React, {forwardRef, Ref, useImperativeHandle, useRef, useState} from 'react'; +import BasePopoverReactionList from './BasePopoverReactionList'; + +type PopoverReactionListProps = { + innerRef: Ref; +}; + +type InnerReactionListRefType = { + showReactionList: ( + event: { + nativeEvent: { + pageX: number; + pageY: number; + }; + }, + reactionListAnchor: HTMLElement, + ) => void; + hideReactionList: () => void; + isActiveReportAction: (actionID: number | string) => boolean; +}; + +const PopoverReactionList = (props: PopoverReactionListProps) => { + const innerReactionListRef = useRef(null); + const [reactionListReportActionID, setReactionListReportActionID] = useState(''); + const [reactionListEmojiName, setReactionListEmojiName] = useState(''); + + const showReactionList = (event: React.MouseEvent, reactionListAnchor: HTMLElement, emojiName: string, reportActionID: string) => { + setReactionListReportActionID(reportActionID); + setReactionListEmojiName(emojiName); + innerReactionListRef.current?.showReactionList(event, reactionListAnchor); + }; + + const hideReactionList = () => { + innerReactionListRef.current?.hideReactionList(); + }; + + const isActiveReportAction = (actionID: number | string) => Boolean(actionID) && reactionListReportActionID === actionID; + + useImperativeHandle(props.innerRef, () => ({showReactionList, hideReactionList, isActiveReportAction})); + + return ( + + ); +}; + +PopoverReactionList.displayName = 'PopoverReactionList'; + +export default React.memo( + forwardRef((props, ref) => ( + + )), +); From 28dfadc15e19eb23a9c361e336c698c4da4bf586 Mon Sep 17 00:00:00 2001 From: vadymbokatov <138146362+vadymbokatov@users.noreply.github.com> Date: Tue, 16 Jan 2024 10:07:10 +0100 Subject: [PATCH 013/252] Update src/pages/home/report/ReactionList/HeaderReactionList.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Błażej Kustra <46095609+blazejkustra@users.noreply.github.com> --- src/pages/home/report/ReactionList/HeaderReactionList.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/home/report/ReactionList/HeaderReactionList.tsx b/src/pages/home/report/ReactionList/HeaderReactionList.tsx index 551059b697e6..869dd95baf1e 100644 --- a/src/pages/home/report/ReactionList/HeaderReactionList.tsx +++ b/src/pages/home/report/ReactionList/HeaderReactionList.tsx @@ -8,9 +8,7 @@ import * as EmojiUtils from '@libs/EmojiUtils'; import type {ReactionListProps} from './reactionPropTypes'; type HeaderReactionListProps = ReactionListProps & { - /** - * Returns true if the current account has reacted to the report action (with the given skin tone). - */ + /** Returns true if the current account has reacted to the report action (with the given skin tone). */ hasUserReacted: boolean; }; From 952c5232776b082eecb746119bf0e5b3e399eac3 Mon Sep 17 00:00:00 2001 From: Vadym Date: Tue, 16 Jan 2024 15:14:47 +0100 Subject: [PATCH 014/252] chore: renames the types file and fixes its linting errors --- src/pages/home/report/ReactionList/BaseReactionList.tsx | 2 +- src/pages/home/report/ReactionList/HeaderReactionList.tsx | 2 +- .../report/ReactionList/{reactionPropTypes.ts => types.ts} | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) rename src/pages/home/report/ReactionList/{reactionPropTypes.ts => types.ts} (79%) diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index 82541b28c9c8..5d41d2f6656f 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -12,7 +12,7 @@ import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {PersonalDetails} from '@src/types/onyx'; import HeaderReactionList from './HeaderReactionList'; -import type {ReactionListProps} from './reactionPropTypes'; +import type {ReactionListProps} from './types'; type BaseReactionListProps = ReactionListProps & { /** diff --git a/src/pages/home/report/ReactionList/HeaderReactionList.tsx b/src/pages/home/report/ReactionList/HeaderReactionList.tsx index 869dd95baf1e..e715f6010fc3 100644 --- a/src/pages/home/report/ReactionList/HeaderReactionList.tsx +++ b/src/pages/home/report/ReactionList/HeaderReactionList.tsx @@ -5,7 +5,7 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as EmojiUtils from '@libs/EmojiUtils'; -import type {ReactionListProps} from './reactionPropTypes'; +import type {ReactionListProps} from './types'; type HeaderReactionListProps = ReactionListProps & { /** Returns true if the current account has reacted to the report action (with the given skin tone). */ diff --git a/src/pages/home/report/ReactionList/reactionPropTypes.ts b/src/pages/home/report/ReactionList/types.ts similarity index 79% rename from src/pages/home/report/ReactionList/reactionPropTypes.ts rename to src/pages/home/report/ReactionList/types.ts index d88316754600..ec03b141080d 100644 --- a/src/pages/home/report/ReactionList/reactionPropTypes.ts +++ b/src/pages/home/report/ReactionList/types.ts @@ -1,4 +1,4 @@ -export type ReactionListProps = { +type ReactionListProps = { /** Hide the ReactionList modal popover */ onClose?: () => void; @@ -11,3 +11,5 @@ export type ReactionListProps = { /** Count of the emoji */ emojiCount: number; }; + +export type {ReactionListProps}; From efe9d72aaa7f9079b0657c66619ebdfbd857b194 Mon Sep 17 00:00:00 2001 From: Vadym Date: Tue, 16 Jan 2024 16:52:08 +0100 Subject: [PATCH 015/252] chore: removes any typed and fixes linting errors on the index file --- .../BasePopoverReactionList.tsx | 2 ++ .../PopoverReactionList/index.tsx | 31 ++++++++----------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx index 8f532253fc43..f2c226d7856a 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx @@ -54,9 +54,11 @@ type BasePopoverReactionListState = { class BasePopoverReactionList extends React.Component { reactionListAnchor: React.RefObject; + dimensionsEventListener: { remove: () => void; } | null; + constructor(props: BasePopoverReactionListPropsWithLocalWithOnyx) { super(props); diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx index e4a7aa42791b..8ac89cef6656 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx @@ -1,33 +1,28 @@ -import React, {forwardRef, Ref, useImperativeHandle, useRef, useState} from 'react'; +import React, {forwardRef, useImperativeHandle, useRef, useState} from 'react'; +import type {ForwardedRef} from 'react'; import BasePopoverReactionList from './BasePopoverReactionList'; type PopoverReactionListProps = { - innerRef: Ref; + ref: ForwardedRef; }; +type ShowReactionList = (event: React.MouseEvent, reactionListAnchor: HTMLElement, emojiName: string, reportActionID: string) => void; + type InnerReactionListRefType = { - showReactionList: ( - event: { - nativeEvent: { - pageX: number; - pageY: number; - }; - }, - reactionListAnchor: HTMLElement, - ) => void; + showReactionList: ShowReactionList; hideReactionList: () => void; isActiveReportAction: (actionID: number | string) => boolean; }; -const PopoverReactionList = (props: PopoverReactionListProps) => { +function PopoverReactionList(props: PopoverReactionListProps) { const innerReactionListRef = useRef(null); const [reactionListReportActionID, setReactionListReportActionID] = useState(''); const [reactionListEmojiName, setReactionListEmojiName] = useState(''); - const showReactionList = (event: React.MouseEvent, reactionListAnchor: HTMLElement, emojiName: string, reportActionID: string) => { + const showReactionList: ShowReactionList = (event, reactionListAnchor, emojiName, reportActionID) => { setReactionListReportActionID(reportActionID); setReactionListEmojiName(emojiName); - innerReactionListRef.current?.showReactionList(event, reactionListAnchor); + innerReactionListRef.current?.showReactionList(event, reactionListAnchor, emojiName, reportActionID); }; const hideReactionList = () => { @@ -36,7 +31,7 @@ const PopoverReactionList = (props: PopoverReactionListProps) => { const isActiveReportAction = (actionID: number | string) => Boolean(actionID) && reactionListReportActionID === actionID; - useImperativeHandle(props.innerRef, () => ({showReactionList, hideReactionList, isActiveReportAction})); + useImperativeHandle(props.ref, () => ({showReactionList, hideReactionList, isActiveReportAction})); return ( { emojiName={reactionListEmojiName} /> ); -}; +} PopoverReactionList.displayName = 'PopoverReactionList'; export default React.memo( - forwardRef((props, ref) => ( + forwardRef((props, ref) => ( )), ); From a303a5e6f55cef79a27cc54eec24d650b4e20bdb Mon Sep 17 00:00:00 2001 From: Vadym Date: Tue, 16 Jan 2024 17:22:07 +0100 Subject: [PATCH 016/252] fix: some linting errors and any types --- .../report/ReactionList/BaseReactionList.tsx | 36 ++++++++++--------- .../BasePopoverReactionList.tsx | 10 +++--- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index 5d41d2f6656f..80cbc834f344 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -1,7 +1,8 @@ /* eslint-disable rulesdir/onyx-props-must-have-default */ import Str from 'expensify-common/lib/str'; import React from 'react'; -import {FlatList, type FlatListProps} from 'react-native'; +import {FlatList} from 'react-native'; +import type {FlatListProps} from 'react-native'; import OptionRow from '@components/OptionRow'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; @@ -33,9 +34,9 @@ type BaseReactionListProps = ReactionListProps & { /** * Create a unique key for each action in the FlatList. - * @param {Object} item - * @param {Number} index - * @return {String} + * @param item object + * @param index number + * @return string */ const keyExtractor: FlatListProps['keyExtractor'] = (item, index) => `${item.login}+${index}`; @@ -45,11 +46,11 @@ const keyExtractor: FlatListProps['keyExtractor'] = (item, inde * Generate and return an object with properties length(height of each individual row), * offset(distance of the current row from the top of the FlatList), index(current row index) * - * @param {*} _ FlatList item - * @param {Number} index row index - * @returns {Object} + * @param data FlatList item + * @param index number - row index + * @returns object */ -const getItemLayout = (_: any, index: number): {length: number; offset: number; index: number} => ({ +const getItemLayout = (data: ArrayLike | null | undefined, index: number): {length: number; offset: number; index: number} => ({ index, length: variables.listItemHeightNormal, offset: variables.listItemHeightNormal * index, @@ -67,9 +68,9 @@ function BaseReactionList(props: BaseReactionListProps) { * Items with the code "SPACER" return nothing and are used to fill rows up to 8 * so that the sticky headers function properly * - * @param {Object} params - * @param {Object} params.item - * @return {React.Component} + * @param params object + * @param params.item object + * @return React.Component */ const renderItem: FlatListProps['renderItem'] = ({item}) => ( { - props.onClose && props.onClose(); + if (props.onClose) { + props.onClose(); + } + Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID)); }} option={{ reportID: String(item.accountID), - text: Str.removeSMSDomain(item.displayName || ''), - alternateText: Str.removeSMSDomain(item.login || ''), + text: Str.removeSMSDomain(item.displayName ?? ''), + alternateText: Str.removeSMSDomain(item.login ?? ''), participantsList: [item], icons: [ { id: item.accountID, source: UserUtils.getAvatar(item.avatar, item.accountID), - name: item.login || '', + name: item.login ?? '', type: CONST.ICON_TYPE_AVATAR, }, ], - keyForList: item.login || String(item.accountID), + keyForList: item.login ?? String(item.accountID), }} /> ); diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx index f2c226d7856a..4bee2e8d1d3c 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx @@ -5,8 +5,10 @@ import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import _ from 'underscore'; import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; -import withCurrentUserPersonalDetails, {type WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; -import withLocalize, {type WithLocalizeProps} from '@components/withLocalize'; +import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import withLocalize from '@components/withLocalize'; +import type {WithLocalizeProps} from '@components/withLocalize'; import compose from '@libs/compose'; import * as EmojiUtils from '@libs/EmojiUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; @@ -14,7 +16,7 @@ import BaseReactionList from '@pages/home/report/ReactionList/BaseReactionList'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ReportActionReactions} from '@src/types/onyx'; -import {ReportActionReaction} from '@src/types/onyx/ReportActionReactions'; +import type {ReportActionReaction} from '@src/types/onyx/ReportActionReactions'; type BasePopoverReactionListOnyxProps = { /** The reactions for the report action */ @@ -29,7 +31,7 @@ type BasePopoverReactionListProps = { emojiName: string; /** The ref of the action */ - ref: React.Ref; + ref: React.Ref; }; type BasePopoverReactionListWithLocalizeProps = WithLocalizeProps & WithCurrentUserPersonalDetailsProps; From bc0a36a17f0080492ff63db5ec49b8a94de6467a Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Wed, 17 Jan 2024 17:01:05 +0100 Subject: [PATCH 017/252] ValuePicker migration --- ...electorModal.js => ValueSelectorModal.tsx} | 53 ++------ src/components/ValuePicker/index.js | 119 ------------------ src/components/ValuePicker/index.tsx | 64 ++++++++++ src/components/ValuePicker/types.ts | 58 +++++++++ 4 files changed, 135 insertions(+), 159 deletions(-) rename src/components/ValuePicker/{ValueSelectorModal.js => ValueSelectorModal.tsx} (51%) delete mode 100644 src/components/ValuePicker/index.js create mode 100644 src/components/ValuePicker/index.tsx create mode 100644 src/components/ValuePicker/types.ts diff --git a/src/components/ValuePicker/ValueSelectorModal.js b/src/components/ValuePicker/ValueSelectorModal.tsx similarity index 51% rename from src/components/ValuePicker/ValueSelectorModal.js rename to src/components/ValuePicker/ValueSelectorModal.tsx index e45ba873d8a3..61588b9f8e37 100644 --- a/src/components/ValuePicker/ValueSelectorModal.js +++ b/src/components/ValuePicker/ValueSelectorModal.tsx @@ -1,5 +1,3 @@ -import _ from 'lodash'; -import PropTypes from 'prop-types'; import React, {useEffect, useState} from 'react'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Modal from '@components/Modal'; @@ -7,45 +5,22 @@ import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; +import type {ValuePickerItem, ValueSelectorModalProps} from './types'; -const propTypes = { - /** Whether the modal is visible */ - isVisible: PropTypes.bool.isRequired, - - /** Items to pick from */ - items: PropTypes.arrayOf(PropTypes.shape({value: PropTypes.string, label: PropTypes.string})), - - /** The selected item */ - selectedItem: PropTypes.shape({value: PropTypes.string, label: PropTypes.string}), - - /** Label for values */ - label: PropTypes.string, - - /** Function to call when the user selects a item */ - onItemSelected: PropTypes.func, - - /** Function to call when the user closes the modal */ - onClose: PropTypes.func, - - /** Whether to show the toolip text */ - shouldShowTooltips: PropTypes.bool, -}; - -const defaultProps = { - items: [], - selectedItem: {}, - label: '', - onClose: () => {}, - onItemSelected: () => {}, - shouldShowTooltips: true, -}; - -function ValueSelectorModal({items, selectedItem, label, isVisible, onClose, onItemSelected, shouldShowTooltips}) { +function ValueSelectorModal({items = [], selectedItem, label = '', isVisible, onClose, onItemSelected, shouldShowTooltips}: ValueSelectorModalProps) { const styles = useThemeStyles(); - const [sectionsData, setSectionsData] = useState([]); + const [sectionsData, setSectionsData] = useState([]); useEffect(() => { - const itemsData = _.map(items, (item) => ({value: item.value, alternateText: item.description, keyForList: item.value, text: item.label, isSelected: item === selectedItem})); + const itemsData = items.map((item, index) => ({ + value: item?.value, + alternateText: item?.description, + keyForList: item.value ?? '', + text: item?.label ?? '', + isSelected: item === selectedItem, + sectionIndex: 0, + index, + })); setSectionsData(itemsData); }, [items, selectedItem]); @@ -71,7 +46,7 @@ function ValueSelectorModal({items, selectedItem, label, isVisible, onClose, onI @@ -80,8 +55,6 @@ function ValueSelectorModal({items, selectedItem, label, isVisible, onClose, onI ); } -ValueSelectorModal.propTypes = propTypes; -ValueSelectorModal.defaultProps = defaultProps; ValueSelectorModal.displayName = 'ValueSelectorModal'; export default ValueSelectorModal; diff --git a/src/components/ValuePicker/index.js b/src/components/ValuePicker/index.js deleted file mode 100644 index d90529114af4..000000000000 --- a/src/components/ValuePicker/index.js +++ /dev/null @@ -1,119 +0,0 @@ -import _ from 'lodash'; -import PropTypes from 'prop-types'; -import React, {useState} from 'react'; -import {View} from 'react-native'; -import FormHelpMessage from '@components/FormHelpMessage'; -import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; -import refPropTypes from '@components/refPropTypes'; -import useStyleUtils from '@hooks/useStyleUtils'; -import useThemeStyles from '@hooks/useThemeStyles'; -import variables from '@styles/variables'; -import ValueSelectorModal from './ValueSelectorModal'; - -const propTypes = { - /** Form Error description */ - errorText: PropTypes.string, - - /** Item to display */ - value: PropTypes.string, - - /** A placeholder value to display */ - placeholder: PropTypes.string, - - /** Items to pick from */ - items: PropTypes.arrayOf(PropTypes.shape({value: PropTypes.string, label: PropTypes.string})), - - /** Label of picker */ - label: PropTypes.string, - - /** Callback to call when the input changes */ - onInputChange: PropTypes.func, - - /** Text to display under the main menu item */ - furtherDetails: PropTypes.string, - - /** A ref to forward to MenuItemWithTopDescription */ - forwardedRef: refPropTypes, - - /** Whether to show the toolip text */ - shouldShowTooltips: PropTypes.bool, -}; - -const defaultProps = { - value: undefined, - label: undefined, - placeholder: '', - items: {}, - forwardedRef: undefined, - errorText: '', - furtherDetails: undefined, - onInputChange: () => {}, - shouldShowTooltips: true, -}; - -function ValuePicker({value, label, items, placeholder, errorText, onInputChange, furtherDetails, shouldShowTooltips, forwardedRef}) { - const styles = useThemeStyles(); - const StyleUtils = useStyleUtils(); - const [isPickerVisible, setIsPickerVisible] = useState(false); - - const showPickerModal = () => { - setIsPickerVisible(true); - }; - - const hidePickerModal = () => { - setIsPickerVisible(false); - }; - - const updateInput = (item) => { - if (item.value !== value) { - onInputChange(item.value); - } - hidePickerModal(); - }; - - const descStyle = !value || value.length === 0 ? StyleUtils.getFontSizeStyle(variables.fontSizeLabel) : null; - const selectedItem = _.find(items, {value}); - const selectedLabel = selectedItem ? selectedItem.label : ''; - - return ( - - - - - - - - ); -} - -ValuePicker.propTypes = propTypes; -ValuePicker.defaultProps = defaultProps; -ValuePicker.displayName = 'ValuePicker'; - -const ValuePickerWithRef = React.forwardRef((props, ref) => ( - -)); - -ValuePickerWithRef.displayName = 'ValuePickerWithRef'; - -export default ValuePickerWithRef; diff --git a/src/components/ValuePicker/index.tsx b/src/components/ValuePicker/index.tsx new file mode 100644 index 000000000000..1137a3a00d81 --- /dev/null +++ b/src/components/ValuePicker/index.tsx @@ -0,0 +1,64 @@ +import React, {forwardRef, useState} from 'react'; +import type {ForwardedRef} from 'react'; +import {View} from 'react-native'; +import FormHelpMessage from '@components/FormHelpMessage'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; +import variables from '@styles/variables'; +import type {ValuePickerItem, ValuePickerProps} from './types'; +import ValueSelectorModal from './ValueSelectorModal'; + +function ValuePicker({value, label, items, placeholder = '', errorText = '', onInputChange, furtherDetails, shouldShowTooltips = true}: ValuePickerProps, forwardedRef: ForwardedRef) { + const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); + const [isPickerVisible, setIsPickerVisible] = useState(false); + + const showPickerModal = () => { + setIsPickerVisible(true); + }; + + const hidePickerModal = () => { + setIsPickerVisible(false); + }; + + const updateInput = (item: ValuePickerItem) => { + if (item?.value && item.value !== value) { + onInputChange?.(item.value); + } + hidePickerModal(); + }; + + const descStyle = value?.length === 0 ? StyleUtils.getFontSizeStyle(variables.fontSizeLabel) : null; + const selectedItem = items?.find((item) => item.value === value); + + return ( + + + + + + + + ); +} + +ValuePicker.displayName = 'ValuePicker'; + +export default forwardRef(ValuePicker); diff --git a/src/components/ValuePicker/types.ts b/src/components/ValuePicker/types.ts new file mode 100644 index 000000000000..70bdfbe1f302 --- /dev/null +++ b/src/components/ValuePicker/types.ts @@ -0,0 +1,58 @@ +import type {RadioItem} from '@components/SelectionList/types'; + +type ValuePickerItem = RadioItem & { + value?: string; + label?: string; + description?: string; +}; + +type ValueSelectorModalProps = { + /** Whether the modal is visible */ + isVisible: boolean; + + /** Items to pick from */ + items?: ValuePickerItem[]; + + /** The selected item */ + selectedItem?: ValuePickerItem; + + /** Label for values */ + label?: string; + + /** Function to call when the user selects a item */ + onItemSelected: (item: ValuePickerItem) => void; + + /** Function to call when the user closes the modal */ + onClose: () => void; + + /** Whether to show the toolip text */ + shouldShowTooltips?: boolean; +}; + +type ValuePickerProps = { + /** Item to display */ + value?: string; + + /** Label of picker */ + label?: string; + + /** Items to pick from */ + items?: ValuePickerItem[]; + + /** A placeholder value to display */ + placeholder?: string; + + /** Form Error description */ + errorText?: string; + + /** Callback to call when the input changes */ + onInputChange?: (value: string) => void; + + /** Text to display under the main menu item */ + furtherDetails?: string; + + /** Whether to show the toolip text */ + shouldShowTooltips?: boolean; +}; + +export type {ValuePickerItem, ValueSelectorModalProps, ValuePickerProps}; From 8045ca6f214867395c2d0af3a99fcfcfba4b241e Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Wed, 17 Jan 2024 19:17:49 +0100 Subject: [PATCH 018/252] Add optional mark to props --- src/components/ValuePicker/ValueSelectorModal.tsx | 4 ++-- src/components/ValuePicker/types.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ValuePicker/ValueSelectorModal.tsx b/src/components/ValuePicker/ValueSelectorModal.tsx index 61588b9f8e37..a7b5f494aa67 100644 --- a/src/components/ValuePicker/ValueSelectorModal.tsx +++ b/src/components/ValuePicker/ValueSelectorModal.tsx @@ -28,7 +28,7 @@ function ValueSelectorModal({items = [], selectedItem, label = '', isVisible, on onClose?.()} onModalHide={onClose} hideModalContentWhileAnimating useNativeDriver @@ -45,7 +45,7 @@ function ValueSelectorModal({items = [], selectedItem, label = '', isVisible, on /> onItemSelected?.(item)} initiallyFocusedOptionKey={selectedItem?.value} shouldStopPropagation shouldShowTooltips={shouldShowTooltips} diff --git a/src/components/ValuePicker/types.ts b/src/components/ValuePicker/types.ts index 70bdfbe1f302..285299bc0101 100644 --- a/src/components/ValuePicker/types.ts +++ b/src/components/ValuePicker/types.ts @@ -20,10 +20,10 @@ type ValueSelectorModalProps = { label?: string; /** Function to call when the user selects a item */ - onItemSelected: (item: ValuePickerItem) => void; + onItemSelected?: (item: ValuePickerItem) => void; /** Function to call when the user closes the modal */ - onClose: () => void; + onClose?: () => void; /** Whether to show the toolip text */ shouldShowTooltips?: boolean; From 00e958e057588cdb6bff53dc254e324546d21800 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 18 Jan 2024 18:33:45 +0700 Subject: [PATCH 019/252] fix: Pin & Delete request appear on Receipt image when quickly tap on receipt and 3-Dot menu --- src/components/Modal/BaseModal.tsx | 9 +++--- src/components/Modal/types.ts | 3 ++ src/components/Popover/index.tsx | 2 ++ .../PopoverWithoutOverlay/index.tsx | 2 +- src/components/ThreeDotsMenu/index.js | 30 ++++++++++++++++--- src/libs/actions/Modal.ts | 4 +-- src/types/onyx/Modal.ts | 2 ++ 7 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index 6e5b4eddae9e..4bec71b5a2dd 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -38,6 +38,7 @@ function BaseModal( onLayout, avoidKeyboard = false, children, + isPopover = false, }: BaseModalProps, ref: React.ForwardedRef, ) { @@ -57,7 +58,7 @@ function BaseModal( */ const hideModal = useCallback( (callHideCallback = true) => { - Modal.willAlertModalBecomeVisible(false); + Modal.willAlertModalBecomeVisible(false, isPopover); if (shouldSetModalVisibility) { Modal.setModalVisibility(false); } @@ -69,14 +70,14 @@ function BaseModal( ComposerFocusManager.setReadyToFocus(); } }, - [shouldSetModalVisibility, onModalHide, fullscreen], + [shouldSetModalVisibility, onModalHide, fullscreen, isPopover], ); useEffect(() => { isVisibleRef.current = isVisible; let removeOnCloseListener: () => void; if (isVisible) { - Modal.willAlertModalBecomeVisible(true); + Modal.willAlertModalBecomeVisible(true, isPopover); // To handle closing any modal already visible when this modal is mounted, i.e. PopoverReportActionContextMenu removeOnCloseListener = Modal.setCloseModal(onClose); } @@ -87,7 +88,7 @@ function BaseModal( } removeOnCloseListener(); }; - }, [isVisible, wasVisible, onClose]); + }, [isVisible, wasVisible, onClose, isPopover]); useEffect( () => () => { diff --git a/src/components/Modal/types.ts b/src/components/Modal/types.ts index 0773f0741233..d6fdd4cabea8 100644 --- a/src/components/Modal/types.ts +++ b/src/components/Modal/types.ts @@ -59,6 +59,9 @@ type BaseModalProps = Partial & { * See: https://github.com/react-native-modal/react-native-modal/pull/116 * */ hideModalContentWhileAnimating?: boolean; + + /** Whether the modal is popover or not */ + isPopover?: boolean; }; export default BaseModalProps; diff --git a/src/components/Popover/index.tsx b/src/components/Popover/index.tsx index 762e79fab63c..246286abdcbc 100644 --- a/src/components/Popover/index.tsx +++ b/src/components/Popover/index.tsx @@ -69,6 +69,7 @@ function Popover(props: PopoverWithWindowDimensionsProps) { onLayout={onLayout} animationIn={animationIn} animationOut={animationOut} + isPopover />, document.body, ); @@ -100,6 +101,7 @@ function Popover(props: PopoverWithWindowDimensionsProps) { onLayout={onLayout} animationIn={animationIn} animationOut={animationOut} + isPopover /> ); } diff --git a/src/components/PopoverWithoutOverlay/index.tsx b/src/components/PopoverWithoutOverlay/index.tsx index 58d022ef9d65..437fb4946f86 100644 --- a/src/components/PopoverWithoutOverlay/index.tsx +++ b/src/components/PopoverWithoutOverlay/index.tsx @@ -59,7 +59,7 @@ function PopoverWithoutOverlay( close(anchorRef); Modal.onModalDidClose(); } - Modal.willAlertModalBecomeVisible(isVisible); + Modal.willAlertModalBecomeVisible(isVisible, true); return () => { if (!removeOnClose) { diff --git a/src/components/ThreeDotsMenu/index.js b/src/components/ThreeDotsMenu/index.js index 150487b2aa57..cc90067a2e70 100644 --- a/src/components/ThreeDotsMenu/index.js +++ b/src/components/ThreeDotsMenu/index.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; -import React, {useRef, useState} from 'react'; +import React, {useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -13,6 +14,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Browser from '@libs/Browser'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ThreeDotsMenuItemPropTypes from './ThreeDotsMenuItemPropTypes'; const propTypes = { @@ -57,6 +59,13 @@ const propTypes = { /** Should we announce the Modal visibility changes? */ shouldSetModalVisibility: PropTypes.bool, + + /** Details about any modals being used */ + modal: PropTypes.shape({ + isVisible: PropTypes.bool, + willAlertModalBecomeVisible: PropTypes.bool, + isPopover: PropTypes.bool, + }), }; const defaultProps = { @@ -72,14 +81,16 @@ const defaultProps = { }, shouldOverlay: false, shouldSetModalVisibility: true, + modal: {}, }; -function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, menuItems, anchorPosition, anchorAlignment, shouldOverlay, shouldSetModalVisibility, disabled}) { +function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, menuItems, anchorPosition, anchorAlignment, shouldOverlay, shouldSetModalVisibility, disabled, modal}) { const theme = useTheme(); const styles = useThemeStyles(); const [isPopupMenuVisible, setPopupMenuVisible] = useState(false); const buttonRef = useRef(null); const {translate} = useLocalize(); + const isBehindModal = modal.willAlertModalBecomeVisible && !modal.isPopover && !shouldOverlay; const showPopoverMenu = () => { setPopupMenuVisible(true); @@ -89,6 +100,13 @@ function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, me setPopupMenuVisible(false); }; + useEffect(() => { + if (!isBehindModal || !isPopupMenuVisible) { + return; + } + hidePopoverMenu(); + }, [isBehindModal, isPopupMenuVisible]); + return ( <> @@ -126,7 +144,7 @@ function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, me Date: Fri, 19 Jan 2024 16:02:16 +0700 Subject: [PATCH 020/252] clean code --- src/pages/settings/Wallet/PaymentMethodList.tsx | 1 + src/pages/settings/Wallet/WalletPage/WalletPage.tsx | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index db04bfb9f183..48f6e1b671f4 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -79,6 +79,7 @@ type PaymentMethodListProps = PaymentMethodListOnyxProps & { /** Type to filter the payment Method list */ filterType?: TupleToUnion; + /** Whether the add bank account button should be shown on the list */ shouldShowAddBankAccount?: boolean; diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx index 0a24e7da9d81..230ad920171d 100644 --- a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx +++ b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx @@ -13,6 +13,7 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import KYCWall from '@components/KYCWall'; +import {Source, TransferMethod} from '@components/KYCWall/types'; import MenuItem from '@components/MenuItem'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import Popover from '@components/Popover'; @@ -38,7 +39,6 @@ import ROUTES from '@src/ROUTES'; import type {AccountData} from '@src/types/onyx'; import type IconAsset from '@src/types/utils/IconAsset'; import type {WalletPageOnyxProps, WalletPageProps} from './types'; -import { Source, TransferMethod } from '@components/KYCWall/types'; type FormattedSelectedPaymentMethod = { title: string; @@ -408,7 +408,10 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi source={hasActivatedWallet ? CONST.KYC_WALL_SOURCE.TRANSFER_BALANCE : CONST.KYC_WALL_SOURCE.ENABLE_WALLET} shouldIncludeDebitCard={hasActivatedWallet} > - {(triggerKYCFlow: (event: SyntheticEvent, iouPaymentType: TransferMethod) => void, buttonRef: ForwardedRef) => { + {( + triggerKYCFlow: (event: SyntheticEvent, iouPaymentType: TransferMethod) => void, + buttonRef: ForwardedRef, + ) => { if (shouldShowLoadingSpinner) { return null; } From 5f6ee9c528d133f78b5e50fdc2b2c13d9862d045 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 22 Jan 2024 16:26:12 +0700 Subject: [PATCH 021/252] change BaseKYCWall type --- src/components/KYCWall/BaseKYCWall.tsx | 5 +++-- src/components/KYCWall/types.ts | 6 +++--- src/components/Popover/types.ts | 2 +- src/pages/settings/Wallet/PaymentMethodList.tsx | 2 +- src/pages/settings/Wallet/WalletPage/WalletPage.tsx | 10 +++++----- src/types/onyx/WalletTerms.ts | 5 ++--- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/components/KYCWall/BaseKYCWall.tsx b/src/components/KYCWall/BaseKYCWall.tsx index 04c8397bc33b..15f9b295325e 100644 --- a/src/components/KYCWall/BaseKYCWall.tsx +++ b/src/components/KYCWall/BaseKYCWall.tsx @@ -2,6 +2,7 @@ import React, {useCallback, useEffect, useRef, useState} from 'react'; import type {SyntheticEvent} from 'react'; import {Dimensions} from 'react-native'; import type {EmitterSubscription, NativeTouchEvent} from 'react-native'; +import type {GestureResponderEvent} from 'react-native-modal'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import AddPaymentMethodMenu from '@components/AddPaymentMethodMenu'; @@ -146,7 +147,7 @@ function KYCWall({ * */ const continueAction = useCallback( - (event?: SyntheticEvent, iouPaymentType?: TransferMethod) => { + (event?: GestureResponderEvent | KeyboardEvent | SyntheticEvent, iouPaymentType?: TransferMethod) => { const currentSource = walletTerms?.source ?? source; /** @@ -161,7 +162,7 @@ function KYCWall({ } // Use event target as fallback if anchorRef is null for safety - const targetElement = anchorRef.current ?? (event?.nativeEvent.target as HTMLDivElement); + const targetElement = anchorRef.current ?? ((event as SyntheticEvent)?.nativeEvent.target as HTMLDivElement); transferBalanceButtonRef.current = targetElement; diff --git a/src/components/KYCWall/types.ts b/src/components/KYCWall/types.ts index 8a654cfc25d8..68374834e254 100644 --- a/src/components/KYCWall/types.ts +++ b/src/components/KYCWall/types.ts @@ -1,5 +1,5 @@ -import type {ForwardedRef, SyntheticEvent} from 'react'; -import type {NativeTouchEvent} from 'react-native'; +import type {ForwardedRef} from 'react'; +import type {GestureResponderEvent} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; @@ -67,7 +67,7 @@ type KYCWallProps = { onSuccessfulKYC: (currentSource?: Source, iouPaymentType?: TransferMethod) => void; /** Children to build the KYC */ - children: (continueAction: (event: SyntheticEvent, method: TransferMethod) => void, anchorRef: ForwardedRef) => void; + children: (continueAction: (event?: GestureResponderEvent | KeyboardEvent, method?: TransferMethod) => void, anchorRef: ForwardedRef) => void; }; export type {AnchorPosition, KYCWallProps, PaymentMethod, TransferMethod, DomRect, Source}; diff --git a/src/components/Popover/types.ts b/src/components/Popover/types.ts index bdc5ae493771..7a8b8d6a7f1f 100644 --- a/src/components/Popover/types.ts +++ b/src/components/Popover/types.ts @@ -34,7 +34,7 @@ type PopoverProps = BaseModalProps & disableAnimation?: boolean; /** Whether we don't want to show overlay */ - withoutOverlay: boolean; + withoutOverlay?: boolean; /** The dimensions of the popover */ popoverDimensions?: PopoverDimensions; diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index 48f6e1b671f4..c1fb1bbd8253 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -128,7 +128,7 @@ function dismissError(item: PaymentMethod) { } } -function shouldShowDefaultBadge(filteredPaymentMethods: PaymentMethod[], isDefault = false) { +function shouldShowDefaultBadge(filteredPaymentMethods: PaymentMethod[], isDefault = false): boolean { if (!isDefault) { return false; } diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx index 230ad920171d..d1823beddef4 100644 --- a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx +++ b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx @@ -1,7 +1,7 @@ import _ from 'lodash'; -import type {ForwardedRef, Ref, RefObject, SyntheticEvent} from 'react'; +import type {ForwardedRef, RefObject} from 'react'; import React, {useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react'; -import type {GestureResponderEvent, NativeTouchEvent} from 'react-native'; +import type {GestureResponderEvent} from 'react-native'; import {ActivityIndicator, Dimensions, ScrollView, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import AddPaymentMethodMenu from '@components/AddPaymentMethodMenu'; @@ -13,7 +13,7 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import KYCWall from '@components/KYCWall'; -import {Source, TransferMethod} from '@components/KYCWall/types'; +import type {Source, TransferMethod} from '@components/KYCWall/types'; import MenuItem from '@components/MenuItem'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import Popover from '@components/Popover'; @@ -409,7 +409,7 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi shouldIncludeDebitCard={hasActivatedWallet} > {( - triggerKYCFlow: (event: SyntheticEvent, iouPaymentType: TransferMethod) => void, + triggerKYCFlow: (event?: GestureResponderEvent | KeyboardEvent, iouPaymentType?: TransferMethod) => void, buttonRef: ForwardedRef, ) => { if (shouldShowLoadingSpinner) { @@ -522,7 +522,7 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi top: anchorPosition.anchorPositionTop, right: anchorPosition.anchorPositionRight, }} - anchorRef={paymentMethodButtonRef} + anchorRef={paymentMethodButtonRef as RefObject} > {!showConfirmDeleteModal && ( diff --git a/src/types/onyx/WalletTerms.ts b/src/types/onyx/WalletTerms.ts index f0563310859a..c2653cae0f97 100644 --- a/src/types/onyx/WalletTerms.ts +++ b/src/types/onyx/WalletTerms.ts @@ -1,5 +1,4 @@ -import type {ValueOf} from 'type-fest'; -import type CONST from '@src/CONST'; +import type {Source} from '@components/KYCWall/types'; import type * as OnyxCommon from './OnyxCommon'; type WalletTerms = { @@ -10,7 +9,7 @@ type WalletTerms = { chatReportID?: string; /** The source that triggered the KYC wall */ - source?: ValueOf; + source?: Source; /** Loading state to provide feedback when we are waiting for a request to finish */ isLoading?: boolean; From 0129e58af57e4a81de7fb8eaed1e53543be24a93 Mon Sep 17 00:00:00 2001 From: Vadym Date: Tue, 23 Jan 2024 16:11:22 +0100 Subject: [PATCH 022/252] chore: converts BasePopoverReactionList to functional component --- src/hooks/useBasePopoverReactionList/index.ts | 134 +++++++ src/hooks/useBasePopoverReactionList/types.ts | 65 ++++ src/pages/home/ReportScreenContext.ts | 4 +- .../BasePopoverReactionList.tsx | 334 +++--------------- .../PopoverReactionList/index.tsx | 18 +- 5 files changed, 256 insertions(+), 299 deletions(-) create mode 100644 src/hooks/useBasePopoverReactionList/index.ts create mode 100644 src/hooks/useBasePopoverReactionList/types.ts diff --git a/src/hooks/useBasePopoverReactionList/index.ts b/src/hooks/useBasePopoverReactionList/index.ts new file mode 100644 index 000000000000..cccb8a99b526 --- /dev/null +++ b/src/hooks/useBasePopoverReactionList/index.ts @@ -0,0 +1,134 @@ +import {useEffect, useMemo, useRef, useState} from 'react'; +import type {SyntheticEvent} from 'react'; +import {Dimensions} from 'react-native'; +import * as EmojiUtils from '@libs/EmojiUtils'; +import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; +import type {BasePopoverReactionListHookProps, ReactionListAnchor, ShowReactionList} from './types'; + +export default function useBasePopoverReactionList({emojiName, emojiReactions, accountID, reportActionID, preferredLocale}: BasePopoverReactionListHookProps) { + const [isPopoverVisible, setIsPopoverVisible] = useState(false); + const [cursorRelativePosition, setCursorRelativePosition] = useState({horizontal: 0, vertical: 0}); + const [popoverAnchorPosition, setPopoverAnchorPosition] = useState({horizontal: 0, vertical: 0}); + const reactionListRef = useRef(null); + + // Get the selected reaction + const selectedReaction = useMemo(() => (isPopoverVisible ? emojiReactions?.emojiName : null), [isPopoverVisible, emojiReactions]); + + // custom methods + function getReactionInformation() { + if (!selectedReaction) { + // If there is no reaction, we return default values + return { + emojiName: '', + reactionCount: 0, + emojiCodes: [], + hasUserReacted: false, + users: [], + }; + } + + const {emojiCodes, reactionCount, hasUserReacted, userAccountIDs} = EmojiUtils.getEmojiReactionDetails(emojiName, selectedReaction, accountID); + + const users = PersonalDetailsUtils.getPersonalDetailsByIDs(userAccountIDs, accountID, true); + return { + emojiName, + emojiCodes, + reactionCount, + hasUserReacted, + users, + }; + } + + /** + * Get the BasePopoverReactionList anchor position + * We calculate the achor coordinates from measureInWindow async method + * + * @returns promise + */ + function getReactionListMeasuredLocation(): Promise<{x: number; y: number}> { + return new Promise((resolve) => { + const reactionListAnchor = reactionListRef.current; + if (reactionListAnchor && 'measureInWindow' in reactionListAnchor) { + reactionListAnchor.measureInWindow((x, y) => resolve({x, y})); + } else { + // If the anchor is not available or does not have the measureInWindow method, we return 0, 0 + resolve({x: 0, y: 0}); + } + }); + } + + /** + * Show the ReactionList modal popover. + * + * @param event - Object - A press event. + * @param reactionListAnchor - Element - reactionListAnchor + */ + const showReactionList: ShowReactionList = (event, reactionListAnchor) => { + // We get the cursor coordinates and the reactionListAnchor coordinates to calculate the popover position + const nativeEvent = (event as SyntheticEvent)?.nativeEvent || {}; + reactionListRef.current = reactionListAnchor; + getReactionListMeasuredLocation().then(({x, y}) => { + setCursorRelativePosition({horizontal: nativeEvent.pageX - x, vertical: nativeEvent.pageY - y}); + setPopoverAnchorPosition({ + horizontal: nativeEvent.pageX, + vertical: nativeEvent.pageY, + }); + setIsPopoverVisible(true); + }); + }; + + /** + * Hide the ReactionList modal popover. + */ + function hideReactionList() { + setIsPopoverVisible(false); + } + + useEffect(() => { + const dimensionsEventListener = Dimensions.addEventListener('change', () => { + if (!isPopoverVisible) { + // If the popover is not visible, we don't need to update the component + return; + } + getReactionListMeasuredLocation().then(({x, y}) => { + if (!x || !y) { + return; + } + setPopoverAnchorPosition({ + horizontal: cursorRelativePosition.horizontal + x, + vertical: cursorRelativePosition.vertical + y, + }); + }); + }); + + return () => { + dimensionsEventListener.remove(); + }; + }, [ + isPopoverVisible, + reportActionID, + preferredLocale, + cursorRelativePosition.horizontal, + cursorRelativePosition.vertical, + popoverAnchorPosition.horizontal, + popoverAnchorPosition.vertical, + ]); + + useEffect(() => { + if (!isPopoverVisible) { + // If the popover is not visible, we don't need to update the component + return; + } + + // Hide the list when all reactions are removed + const emojiReactionsList = emojiReactions?.emojiName.users; + const isEmptyList = Array.isArray(emojiReactionsList) && !emojiReactionsList.some((emojiReaction) => emojiReaction); + if (!isEmptyList) { + return; + } + + hideReactionList(); + }); + + return {isPopoverVisible, cursorRelativePosition, popoverAnchorPosition, getReactionInformation, hideReactionList, reactionListRef, showReactionList}; +} diff --git a/src/hooks/useBasePopoverReactionList/types.ts b/src/hooks/useBasePopoverReactionList/types.ts new file mode 100644 index 000000000000..e4ba9263b41b --- /dev/null +++ b/src/hooks/useBasePopoverReactionList/types.ts @@ -0,0 +1,65 @@ +import type {OnyxEntry} from 'react-native-onyx'; +import type {LocaleContextProps} from '@components/LocaleContextProvider'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import type {ReactionListAnchor, ReactionListEvent} from '@pages/home/ReportScreenContext'; +import type {ReportActionReactions} from '@src/types/onyx'; + +type BasePopoverReactionListOnyxProps = { + /** The reactions for the report action */ + emojiReactions: OnyxEntry; +}; + +type BasePopoverReactionListProps = { + /** The ID of the report action */ + reportActionID: string; + + /** The emoji name */ + emojiName: string; +}; + +type BasePopoverReactionListHookProps = BasePopoverReactionListProps & { + /** The reactions for the report action */ + emojiReactions: OnyxEntry; + + /** The current user's account ID */ + accountID: WithCurrentUserPersonalDetailsProps['currentUserPersonalDetails']['accountID']; + + preferredLocale: LocaleContextProps['preferredLocale']; +}; + +type BasePopoverReactionListPropsWithLocalWithOnyx = WithCurrentUserPersonalDetailsProps & BasePopoverReactionListOnyxProps & BasePopoverReactionListProps; +type BasePopoverReactionListState = { + /** Whether the popover is visible */ + isPopoverVisible: boolean; + + /** The horizontal and vertical position (relative to the screen) where the popover will display. */ + popoverAnchorPosition: { + horizontal: number; + vertical: number; + }; + + /** The horizontal and vertical position (relative to the screen) where the cursor is. */ + cursorRelativePosition: { + horizontal: number; + vertical: number; + }; +}; + +type ShowReactionList = (event: ReactionListEvent | undefined, reactionListAnchor: ReactionListAnchor) => void; + +type InnerReactionListRefType = { + showReactionList: ShowReactionList; + hideReactionList: () => void; + isActiveReportAction: (actionID: number | string) => boolean; +}; + +export type { + BasePopoverReactionListProps, + BasePopoverReactionListHookProps, + BasePopoverReactionListPropsWithLocalWithOnyx, + BasePopoverReactionListState, + BasePopoverReactionListOnyxProps, + ShowReactionList, + ReactionListAnchor, + InnerReactionListRefType, +}; diff --git a/src/pages/home/ReportScreenContext.ts b/src/pages/home/ReportScreenContext.ts index 3b4e574e01a1..e9440ab932d6 100644 --- a/src/pages/home/ReportScreenContext.ts +++ b/src/pages/home/ReportScreenContext.ts @@ -1,10 +1,10 @@ -import type {RefObject} from 'react'; +import type {RefObject, SyntheticEvent} from 'react'; import {createContext} from 'react'; import type {FlatList, GestureResponderEvent, View} from 'react-native'; type ReactionListAnchor = View | HTMLDivElement | null; -type ReactionListEvent = GestureResponderEvent | MouseEvent; +type ReactionListEvent = GestureResponderEvent | MouseEvent | SyntheticEvent; type ReactionListRef = { showReactionList: (event: ReactionListEvent | undefined, reactionListAnchor: ReactionListAnchor, emojiName: string, reportActionID: string) => void; diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx index 4bee2e8d1d3c..3f5fdeb6428f 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx @@ -1,297 +1,61 @@ -import lodashGet from 'lodash/get'; import React from 'react'; -import {Dimensions} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx'; -import _ from 'underscore'; import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; -import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; -import withLocalize from '@components/withLocalize'; -import type {WithLocalizeProps} from '@components/withLocalize'; -import compose from '@libs/compose'; -import * as EmojiUtils from '@libs/EmojiUtils'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; +import useBasePopoverReactionList from '@hooks/useBasePopoverReactionList'; +import type {BasePopoverReactionListOnyxProps, BasePopoverReactionListPropsWithLocalWithOnyx} from '@hooks/useBasePopoverReactionList/types'; +import useLocalize from '@hooks/useLocalize'; import BaseReactionList from '@pages/home/report/ReactionList/BaseReactionList'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {ReportActionReactions} from '@src/types/onyx'; -import type {ReportActionReaction} from '@src/types/onyx/ReportActionReactions'; -type BasePopoverReactionListOnyxProps = { - /** The reactions for the report action */ - emojiReactions: OnyxEntry; -}; - -type BasePopoverReactionListProps = { - /** The ID of the report action */ - reportActionID: string; - - /** The emoji name */ - emojiName: string; - - /** The ref of the action */ - ref: React.Ref; -}; - -type BasePopoverReactionListWithLocalizeProps = WithLocalizeProps & WithCurrentUserPersonalDetailsProps; - -type BasePopoverReactionListPropsWithLocalWithOnyx = BasePopoverReactionListWithLocalizeProps & BasePopoverReactionListOnyxProps & BasePopoverReactionListProps; -type BasePopoverReactionListState = { - /** Whether the popover is visible */ - isPopoverVisible: boolean; - - /** The horizontal and vertical position (relative to the screen) where the popover will display. */ - popoverAnchorPosition: { - horizontal: number; - vertical: number; - }; - - /** The horizontal and vertical position (relative to the screen) where the cursor is. */ - cursorRelativePosition: { - horizontal: number; - vertical: number; - }; -}; - -class BasePopoverReactionList extends React.Component { - reactionListAnchor: React.RefObject; - - dimensionsEventListener: { - remove: () => void; - } | null; - - constructor(props: BasePopoverReactionListPropsWithLocalWithOnyx) { - super(props); - - this.state = { - isPopoverVisible: false, - cursorRelativePosition: { - horizontal: 0, - vertical: 0, - }, - - // The horizontal and vertical position (relative to the screen) where the popover will display. - popoverAnchorPosition: { - horizontal: 0, - vertical: 0, - }, - }; - - this.reactionListAnchor = React.createRef(); - this.showReactionList = this.showReactionList.bind(this); - this.hideReactionList = this.hideReactionList.bind(this); - this.measureReactionListPosition = this.measureReactionListPosition.bind(this); - this.getReactionListMeasuredLocation = this.getReactionListMeasuredLocation.bind(this); - this.getReactionInformation = this.getReactionInformation.bind(this); - this.dimensionsEventListener = null; - } - - componentDidMount() { - this.dimensionsEventListener = Dimensions.addEventListener('change', this.measureReactionListPosition); - } - - shouldComponentUpdate(nextProps: BasePopoverReactionListPropsWithLocalWithOnyx, nextState: BasePopoverReactionListState) { - if (!this.state.isPopoverVisible && !nextState.isPopoverVisible) { - // If the popover is not visible, we don't need to update the component - return false; - } - - const previousLocale = lodashGet(this.props, 'preferredLocale', CONST.LOCALES.DEFAULT); - const nextLocale = lodashGet(nextProps, 'preferredLocale', CONST.LOCALES.DEFAULT); - const prevReaction = lodashGet(this.props.emojiReactions, this.props.emojiName); - const nextReaction = lodashGet(nextProps.emojiReactions, nextProps.emojiName); - - return ( - this.props.reportActionID !== nextProps.reportActionID || - this.props.emojiName !== nextProps.emojiName || - !_.isEqual(prevReaction, nextReaction) || - !_.isEqual(this.state, nextState) || - previousLocale !== nextLocale - ); - } - - componentDidUpdate() { - if (!this.state.isPopoverVisible) { - // If the popover is not visible, we don't need to update the component - return; - } - - // Hide the list when all reactions are removed - const emojiReactions = lodashGet(this.props.emojiReactions, [this.props.emojiName, 'users']); - const isEmptyList = !emojiReactions || !_.some(emojiReactions); - if (!isEmptyList) { - return; - } - - this.hideReactionList(); - } - - componentWillUnmount() { - // Remove the event listener - if (!this.dimensionsEventListener) { - return; - } - this.dimensionsEventListener.remove(); - } - - /** - * Get the BasePopoverReactionList anchor position - * We calculate the achor coordinates from measureInWindow async method - * - * @returns {Promise} - */ - getReactionListMeasuredLocation(): Promise<{x: number; y: number}> { - return new Promise((resolve) => { - const reactionListAnchor = this.reactionListAnchor.current as HTMLElement & {measureInWindow: (callback: (x: number, y: number) => void) => void}; - if (reactionListAnchor) { - reactionListAnchor.measureInWindow((x, y) => resolve({x, y})); - } else { - // If the anchor is not available, we return 0, 0 - resolve({x: 0, y: 0}); - } - }); - } - - /** - * Get the reaction information. - * - * @param {Object} selectedReaction - * @param {String} emojiName - * @returns {Object} - */ - getReactionInformation(selectedReaction: ReportActionReaction | null | undefined, emojiName: string) { - if (!selectedReaction) { - // If there is no reaction, we return default values - return { - emojiName: '', - reactionCount: 0, - emojiCodes: [], - hasUserReacted: false, - users: [], - }; - } - - const {emojiCodes, reactionCount, hasUserReacted, userAccountIDs} = EmojiUtils.getEmojiReactionDetails(emojiName, selectedReaction, this.props.currentUserPersonalDetails.accountID); - - const users = PersonalDetailsUtils.getPersonalDetailsByIDs(userAccountIDs, this.props.currentUserPersonalDetails.accountID, true); - return { - emojiName, - emojiCodes, - reactionCount, - hasUserReacted, - users, - }; - } - - /** - * Show the ReactionList modal popover. - * - * @param {Object} [event] - A press event. - * @param {Element} reactionListAnchor - reactionListAnchor - */ - showReactionList( - event: { - nativeEvent: { - pageX: number; - pageY: number; - }; - }, - reactionListAnchor: HTMLElement, - ) { - // We get the cursor coordinates and the reactionListAnchor coordinates to calculate the popover position - const nativeEvent = event.nativeEvent || {}; - this.reactionListAnchor = {current: reactionListAnchor}; - this.getReactionListMeasuredLocation().then(({x, y}) => { - this.setState({ - cursorRelativePosition: { - horizontal: nativeEvent.pageX - x, - vertical: nativeEvent.pageY - y, - }, - popoverAnchorPosition: { - horizontal: nativeEvent.pageX, - vertical: nativeEvent.pageY, - }, - isPopoverVisible: true, - }); - }); - } - - /** - * This gets called on Dimensions change to find the anchor coordinates for the action BasePopoverReactionList. - */ - measureReactionListPosition() { - if (!this.state.isPopoverVisible) { - // If the popover is not visible, we don't need to update the component - return; - } - this.getReactionListMeasuredLocation().then(({x, y}) => { - if (!x || !y) { - return; - } - this.setState((prev) => ({ - popoverAnchorPosition: { - horizontal: prev.cursorRelativePosition.horizontal + x, - vertical: prev.cursorRelativePosition.vertical + y, - }, - })); - }); - } - - /** - * Hide the ReactionList modal popover. - */ - hideReactionList() { - this.setState({ - isPopoverVisible: false, - }); - } - - render() { - // Get the selected reaction - const selectedReaction = this.state.isPopoverVisible ? lodashGet(this.props.emojiReactions, [this.props.emojiName]) : null; - - // Get the reaction information - const {emojiName, emojiCodes, reactionCount, hasUserReacted, users} = this.getReactionInformation(selectedReaction, this.props.emojiName); - - return ( - - - - ); - } +function BasePopoverReactionList(props: BasePopoverReactionListPropsWithLocalWithOnyx) { + // hooks + const {emojiReactions, emojiName, reportActionID, currentUserPersonalDetails} = props; + const {preferredLocale} = useLocalize(); + const {isPopoverVisible, hideReactionList, popoverAnchorPosition, reactionListRef, getReactionInformation} = useBasePopoverReactionList({ + emojiName, + emojiReactions, + accountID: currentUserPersonalDetails.accountID, + reportActionID, + preferredLocale, + }); + // Get the reaction information + const {emojiCodes, reactionCount, hasUserReacted, users} = getReactionInformation(); + + return ( + + + + ); } -export default compose( - // @ts-ignore TODO: Fix this when the type is fixed - withLocalize, - withCurrentUserPersonalDetails, - withOnyx({ +export default withCurrentUserPersonalDetails( + withOnyx({ emojiReactions: { key: ({reportActionID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, }, - }), -)(BasePopoverReactionList); + })(BasePopoverReactionList), +); diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx index 8ac89cef6656..610146559786 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx @@ -1,17 +1,11 @@ import React, {forwardRef, useImperativeHandle, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; +import type {InnerReactionListRefType} from '@hooks/useBasePopoverReactionList/types'; +import type {ReactionListRef} from '@pages/home/ReportScreenContext'; import BasePopoverReactionList from './BasePopoverReactionList'; type PopoverReactionListProps = { - ref: ForwardedRef; -}; - -type ShowReactionList = (event: React.MouseEvent, reactionListAnchor: HTMLElement, emojiName: string, reportActionID: string) => void; - -type InnerReactionListRefType = { - showReactionList: ShowReactionList; - hideReactionList: () => void; - isActiveReportAction: (actionID: number | string) => boolean; + ref: ForwardedRef; }; function PopoverReactionList(props: PopoverReactionListProps) { @@ -19,10 +13,10 @@ function PopoverReactionList(props: PopoverReactionListProps) { const [reactionListReportActionID, setReactionListReportActionID] = useState(''); const [reactionListEmojiName, setReactionListEmojiName] = useState(''); - const showReactionList: ShowReactionList = (event, reactionListAnchor, emojiName, reportActionID) => { + const showReactionList: ReactionListRef['showReactionList'] = (event, reactionListAnchor, emojiName, reportActionID) => { setReactionListReportActionID(reportActionID); setReactionListEmojiName(emojiName); - innerReactionListRef.current?.showReactionList(event, reactionListAnchor, emojiName, reportActionID); + innerReactionListRef.current?.showReactionList(event, reactionListAnchor); }; const hideReactionList = () => { @@ -45,7 +39,7 @@ function PopoverReactionList(props: PopoverReactionListProps) { PopoverReactionList.displayName = 'PopoverReactionList'; export default React.memo( - forwardRef((props, ref) => ( + forwardRef((props, ref) => ( Date: Wed, 24 Jan 2024 09:40:09 +0100 Subject: [PATCH 023/252] chore: fixes the linting error regarding the named export --- src/pages/home/report/ReactionList/BaseReactionList.tsx | 2 +- src/pages/home/report/ReactionList/HeaderReactionList.tsx | 2 +- src/pages/home/report/ReactionList/types.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index 80cbc834f344..f1002bddcddc 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -13,7 +13,7 @@ import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {PersonalDetails} from '@src/types/onyx'; import HeaderReactionList from './HeaderReactionList'; -import type {ReactionListProps} from './types'; +import type ReactionListProps from './types'; type BaseReactionListProps = ReactionListProps & { /** diff --git a/src/pages/home/report/ReactionList/HeaderReactionList.tsx b/src/pages/home/report/ReactionList/HeaderReactionList.tsx index e715f6010fc3..bf6c7c1192a5 100644 --- a/src/pages/home/report/ReactionList/HeaderReactionList.tsx +++ b/src/pages/home/report/ReactionList/HeaderReactionList.tsx @@ -5,7 +5,7 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as EmojiUtils from '@libs/EmojiUtils'; -import type {ReactionListProps} from './types'; +import type ReactionListProps from './types'; type HeaderReactionListProps = ReactionListProps & { /** Returns true if the current account has reacted to the report action (with the given skin tone). */ diff --git a/src/pages/home/report/ReactionList/types.ts b/src/pages/home/report/ReactionList/types.ts index ec03b141080d..eb5dfe1d998e 100644 --- a/src/pages/home/report/ReactionList/types.ts +++ b/src/pages/home/report/ReactionList/types.ts @@ -12,4 +12,4 @@ type ReactionListProps = { emojiCount: number; }; -export type {ReactionListProps}; +export default ReactionListProps; From d700d8db166245a5057f8d935dafa8606badbc35 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 24 Jan 2024 17:13:57 +0700 Subject: [PATCH 024/252] resolve conflict --- src/components/KYCWall/types.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/KYCWall/types.ts b/src/components/KYCWall/types.ts index 2dc12b32ead4..dc038e998694 100644 --- a/src/components/KYCWall/types.ts +++ b/src/components/KYCWall/types.ts @@ -67,11 +67,7 @@ type KYCWallProps = { onSuccessfulKYC: (iouPaymentType?: TransferMethod, currentSource?: Source) => void; /** Children to build the KYC */ -<<<<<<< HEAD - children: (continueAction: (event?: GestureResponderEvent | KeyboardEvent, method?: TransferMethod) => void, anchorRef: ForwardedRef) => void; -======= children: (continueAction: (event: GestureResponderEvent | KeyboardEvent | undefined, method: TransferMethod) => void, anchorRef: ForwardedRef) => void; ->>>>>>> main }; export type {AnchorPosition, KYCWallProps, PaymentMethod, TransferMethod, DomRect, Source}; From 7deab6a55225f700aede985b0edb3532add49664 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 24 Jan 2024 17:41:13 +0700 Subject: [PATCH 025/252] clean code --- src/CONST.ts | 7 ++++--- src/components/KYCWall/types.ts | 2 +- src/pages/settings/Wallet/WalletPage/WalletPage.tsx | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 264c10b79921..7e5efb2a07a8 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -37,6 +37,9 @@ const keyInputRightArrow = KeyCommand?.constants?.keyInputRightArrow ?? 'keyInpu // describes if a shortcut key can cause navigation const KEYBOARD_SHORTCUT_NAVIGATION_TYPE = 'NAVIGATION_SHORTCUT'; +// Explicit type annotation is required +const cardActiveStates: number[] = [2, 3, 4, 7] + const CONST = { ANDROID_PACKAGE_NAME, ANIMATED_TRANSITION: 300, @@ -1377,9 +1380,7 @@ const CONST = { CLOSED: 6, STATE_SUSPENDED: 7, }, - get ACTIVE_STATES() { - return [2, 3, 4, 7]; - }, + ACTIVE_STATES: cardActiveStates }, AVATAR_ROW_SIZE: { DEFAULT: 4, diff --git a/src/components/KYCWall/types.ts b/src/components/KYCWall/types.ts index dc038e998694..5c4b78edd2b1 100644 --- a/src/components/KYCWall/types.ts +++ b/src/components/KYCWall/types.ts @@ -67,7 +67,7 @@ type KYCWallProps = { onSuccessfulKYC: (iouPaymentType?: TransferMethod, currentSource?: Source) => void; /** Children to build the KYC */ - children: (continueAction: (event: GestureResponderEvent | KeyboardEvent | undefined, method: TransferMethod) => void, anchorRef: ForwardedRef) => void; + children: (continueAction: (event?: GestureResponderEvent | KeyboardEvent | undefined, method?: TransferMethod) => void, anchorRef: ForwardedRef) => void; }; export type {AnchorPosition, KYCWallProps, PaymentMethod, TransferMethod, DomRect, Source}; diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx index d1823beddef4..09f847ddf490 100644 --- a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx +++ b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx @@ -393,7 +393,7 @@ function WalletPage({bankAccountList = {}, cardList = {}, fundList = {}, isLoadi navigateToWalletOrTransferBalancePage(source)} + onSuccessfulKYC={(_iouPaymentType?: TransferMethod, source?: Source) => navigateToWalletOrTransferBalancePage(source)} onSelectPaymentMethod={(selectedPaymentMethod: string) => { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if (hasActivatedWallet || selectedPaymentMethod !== CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { From d33d249b09a30ec98242241872de8ac718d24903 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 24 Jan 2024 17:42:34 +0700 Subject: [PATCH 026/252] type error --- src/CONST.ts | 2 +- src/components/KYCWall/BaseKYCWall.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 7e5efb2a07a8..f8ce9689a494 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -38,7 +38,7 @@ const keyInputRightArrow = KeyCommand?.constants?.keyInputRightArrow ?? 'keyInpu const KEYBOARD_SHORTCUT_NAVIGATION_TYPE = 'NAVIGATION_SHORTCUT'; // Explicit type annotation is required -const cardActiveStates: number[] = [2, 3, 4, 7] +const cardActiveStates: number[] = [2, 3, 4, 7]; const CONST = { ANDROID_PACKAGE_NAME, diff --git a/src/components/KYCWall/BaseKYCWall.tsx b/src/components/KYCWall/BaseKYCWall.tsx index 89cceadc0fb0..b2d979a33fcb 100644 --- a/src/components/KYCWall/BaseKYCWall.tsx +++ b/src/components/KYCWall/BaseKYCWall.tsx @@ -243,7 +243,6 @@ function KYCWall({ return ( <> setShouldShowAddPaymentMenu(false)} From ffe26d60a5dc6916dcec7c9f913d87f9c4c988e5 Mon Sep 17 00:00:00 2001 From: vadymbokatov <138146362+vadymbokatov@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:44:48 +0100 Subject: [PATCH 027/252] Update src/pages/home/report/ReactionList/PopoverReactionList/index.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Błażej Kustra <46095609+blazejkustra@users.noreply.github.com> --- .../report/ReactionList/PopoverReactionList/index.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx index 610146559786..4368f5a609ab 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx @@ -38,12 +38,4 @@ function PopoverReactionList(props: PopoverReactionListProps) { PopoverReactionList.displayName = 'PopoverReactionList'; -export default React.memo( - forwardRef((props, ref) => ( - - )), -); +export default React.memo(forwardRef(PopoverReactionList)); From a1a4f17f6c44190a5f63b131e0b76056d87a93e4 Mon Sep 17 00:00:00 2001 From: vadymbokatov <138146362+vadymbokatov@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:45:38 +0100 Subject: [PATCH 028/252] Update src/pages/home/report/ReactionList/BaseReactionList.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Błażej Kustra <46095609+blazejkustra@users.noreply.github.com> --- src/pages/home/report/ReactionList/BaseReactionList.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index f1002bddcddc..bec68ba268ff 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -78,9 +78,7 @@ function BaseReactionList(props: BaseReactionListProps) { style={{maxWidth: variables.mobileResponsiveWidthBreakpoint}} hoverStyle={hoveredComponentBG} onSelectRow={() => { - if (props.onClose) { - props.onClose(); - } + props.onClose?.(); Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID)); }} From 1059ccfb9be071330b004b744a2e46abb3d7e77d Mon Sep 17 00:00:00 2001 From: vadymbokatov <138146362+vadymbokatov@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:46:05 +0100 Subject: [PATCH 029/252] Update src/pages/home/report/ReactionList/BaseReactionList.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Błażej Kustra <46095609+blazejkustra@users.noreply.github.com> --- src/pages/home/report/ReactionList/BaseReactionList.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index bec68ba268ff..c905630f547c 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -68,9 +68,6 @@ function BaseReactionList(props: BaseReactionListProps) { * Items with the code "SPACER" return nothing and are used to fill rows up to 8 * so that the sticky headers function properly * - * @param params object - * @param params.item object - * @return React.Component */ const renderItem: FlatListProps['renderItem'] = ({item}) => ( Date: Wed, 24 Jan 2024 14:46:53 +0100 Subject: [PATCH 030/252] Update src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Błażej Kustra <46095609+blazejkustra@users.noreply.github.com> --- .../ReactionList/PopoverReactionList/BasePopoverReactionList.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx index 3f5fdeb6428f..5ef74a62ec67 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx @@ -9,7 +9,6 @@ import BaseReactionList from '@pages/home/report/ReactionList/BaseReactionList'; import ONYXKEYS from '@src/ONYXKEYS'; function BasePopoverReactionList(props: BasePopoverReactionListPropsWithLocalWithOnyx) { - // hooks const {emojiReactions, emojiName, reportActionID, currentUserPersonalDetails} = props; const {preferredLocale} = useLocalize(); const {isPopoverVisible, hideReactionList, popoverAnchorPosition, reactionListRef, getReactionInformation} = useBasePopoverReactionList({ From ffca03ed05404e85a1cc271cace70016c03d2c57 Mon Sep 17 00:00:00 2001 From: Vadym Date: Wed, 24 Jan 2024 16:22:38 +0100 Subject: [PATCH 031/252] fix: ref object not getting passed as the second arg --- .../report/ReactionList/PopoverReactionList/index.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx index 4368f5a609ab..75b8e080b301 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx @@ -4,11 +4,7 @@ import type {InnerReactionListRefType} from '@hooks/useBasePopoverReactionList/t import type {ReactionListRef} from '@pages/home/ReportScreenContext'; import BasePopoverReactionList from './BasePopoverReactionList'; -type PopoverReactionListProps = { - ref: ForwardedRef; -}; - -function PopoverReactionList(props: PopoverReactionListProps) { +function PopoverReactionList(props: unknown, ref: ForwardedRef) { const innerReactionListRef = useRef(null); const [reactionListReportActionID, setReactionListReportActionID] = useState(''); const [reactionListEmojiName, setReactionListEmojiName] = useState(''); @@ -25,7 +21,7 @@ function PopoverReactionList(props: PopoverReactionListProps) { const isActiveReportAction = (actionID: number | string) => Boolean(actionID) && reactionListReportActionID === actionID; - useImperativeHandle(props.ref, () => ({showReactionList, hideReactionList, isActiveReportAction})); + useImperativeHandle(ref, () => ({showReactionList, hideReactionList, isActiveReportAction})); return ( Date: Wed, 24 Jan 2024 16:25:41 +0100 Subject: [PATCH 032/252] fix: types naming convention --- src/hooks/useBasePopoverReactionList/types.ts | 4 ++-- .../home/report/ReactionList/PopoverReactionList/index.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hooks/useBasePopoverReactionList/types.ts b/src/hooks/useBasePopoverReactionList/types.ts index e4ba9263b41b..49620ccd9370 100644 --- a/src/hooks/useBasePopoverReactionList/types.ts +++ b/src/hooks/useBasePopoverReactionList/types.ts @@ -47,7 +47,7 @@ type BasePopoverReactionListState = { type ShowReactionList = (event: ReactionListEvent | undefined, reactionListAnchor: ReactionListAnchor) => void; -type InnerReactionListRefType = { +type InnerReactionListRef = { showReactionList: ShowReactionList; hideReactionList: () => void; isActiveReportAction: (actionID: number | string) => boolean; @@ -61,5 +61,5 @@ export type { BasePopoverReactionListOnyxProps, ShowReactionList, ReactionListAnchor, - InnerReactionListRefType, + InnerReactionListRef, }; diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx index 75b8e080b301..e7899c814568 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx @@ -1,11 +1,11 @@ import React, {forwardRef, useImperativeHandle, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; -import type {InnerReactionListRefType} from '@hooks/useBasePopoverReactionList/types'; +import type {InnerReactionListRef} from '@hooks/useBasePopoverReactionList/types'; import type {ReactionListRef} from '@pages/home/ReportScreenContext'; import BasePopoverReactionList from './BasePopoverReactionList'; function PopoverReactionList(props: unknown, ref: ForwardedRef) { - const innerReactionListRef = useRef(null); + const innerReactionListRef = useRef(null); const [reactionListReportActionID, setReactionListReportActionID] = useState(''); const [reactionListEmojiName, setReactionListEmojiName] = useState(''); From cebd4dcc436ec9a1f9f618f29c4f0a84265e6eb5 Mon Sep 17 00:00:00 2001 From: Vadym Date: Wed, 24 Jan 2024 16:37:36 +0100 Subject: [PATCH 033/252] chore: removes unneeded JSDocs --- .../report/ReactionList/BaseReactionList.tsx | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index c905630f547c..80ffd11e59ba 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -32,24 +32,8 @@ type BaseReactionListProps = ReactionListProps & { isVisible: boolean; }; -/** - * Create a unique key for each action in the FlatList. - * @param item object - * @param index number - * @return string - */ const keyExtractor: FlatListProps['keyExtractor'] = (item, index) => `${item.login}+${index}`; -/** - * This function will be used with FlatList getItemLayout property for optimization purpose that allows skipping - * the measurement of dynamic content if we know the size (height or width) of items ahead of time. - * Generate and return an object with properties length(height of each individual row), - * offset(distance of the current row from the top of the FlatList), index(current row index) - * - * @param data FlatList item - * @param index number - row index - * @returns object - */ const getItemLayout = (data: ArrayLike | null | undefined, index: number): {length: number; offset: number; index: number} => ({ index, length: variables.listItemHeightNormal, From 3f8233674cb94e7a6a92ebdb4c517ec4b29aac95 Mon Sep 17 00:00:00 2001 From: Vadym Date: Wed, 24 Jan 2024 16:51:22 +0100 Subject: [PATCH 034/252] chore: makes isVisible prop optional --- src/pages/home/report/ReactionList/BaseReactionList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index 80ffd11e59ba..a49c4d832f30 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -29,7 +29,7 @@ type BaseReactionListProps = ReactionListProps & { /** * Returns true if the reaction list is visible */ - isVisible: boolean; + isVisible?: boolean; }; const keyExtractor: FlatListProps['keyExtractor'] = (item, index) => `${item.login}+${index}`; From ac5f0889d73ca657b25c097ead4cb5159fad02f9 Mon Sep 17 00:00:00 2001 From: Vadym Date: Wed, 24 Jan 2024 20:09:46 +0100 Subject: [PATCH 035/252] fix: menu ref not getting forwarded and menu not showing up --- src/hooks/useBasePopoverReactionList/index.ts | 12 ++++++------ .../home/report/ReactionList/HeaderReactionList.tsx | 1 + .../PopoverReactionList/BasePopoverReactionList.tsx | 12 ++++++++---- .../ReactionList/PopoverReactionList/index.tsx | 4 ++++ 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/hooks/useBasePopoverReactionList/index.ts b/src/hooks/useBasePopoverReactionList/index.ts index cccb8a99b526..64c5f6d8efc0 100644 --- a/src/hooks/useBasePopoverReactionList/index.ts +++ b/src/hooks/useBasePopoverReactionList/index.ts @@ -1,4 +1,4 @@ -import {useEffect, useMemo, useRef, useState} from 'react'; +import {useEffect, useRef, useState} from 'react'; import type {SyntheticEvent} from 'react'; import {Dimensions} from 'react-native'; import * as EmojiUtils from '@libs/EmojiUtils'; @@ -11,11 +11,10 @@ export default function useBasePopoverReactionList({emojiName, emojiReactions, a const [popoverAnchorPosition, setPopoverAnchorPosition] = useState({horizontal: 0, vertical: 0}); const reactionListRef = useRef(null); - // Get the selected reaction - const selectedReaction = useMemo(() => (isPopoverVisible ? emojiReactions?.emojiName : null), [isPopoverVisible, emojiReactions]); - // custom methods function getReactionInformation() { + const selectedReaction = emojiReactions?.[emojiName]; + if (!selectedReaction) { // If there is no reaction, we return default values return { @@ -121,8 +120,9 @@ export default function useBasePopoverReactionList({emojiName, emojiReactions, a } // Hide the list when all reactions are removed - const emojiReactionsList = emojiReactions?.emojiName.users; - const isEmptyList = Array.isArray(emojiReactionsList) && !emojiReactionsList.some((emojiReaction) => emojiReaction); + const users = emojiReactions?.[emojiName].users; + const isEmptyList = users && Object.keys(users).length === 0; + if (!isEmptyList) { return; } diff --git a/src/pages/home/report/ReactionList/HeaderReactionList.tsx b/src/pages/home/report/ReactionList/HeaderReactionList.tsx index bf6c7c1192a5..5742566e115c 100644 --- a/src/pages/home/report/ReactionList/HeaderReactionList.tsx +++ b/src/pages/home/report/ReactionList/HeaderReactionList.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import {View} from 'react-native'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx index 5ef74a62ec67..6a464c46cce8 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx @@ -1,4 +1,5 @@ -import React from 'react'; +import React, {forwardRef, useImperativeHandle} from 'react'; +import type {ForwardedRef} from 'react'; import {withOnyx} from 'react-native-onyx'; import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; @@ -6,12 +7,13 @@ import useBasePopoverReactionList from '@hooks/useBasePopoverReactionList'; import type {BasePopoverReactionListOnyxProps, BasePopoverReactionListPropsWithLocalWithOnyx} from '@hooks/useBasePopoverReactionList/types'; import useLocalize from '@hooks/useLocalize'; import BaseReactionList from '@pages/home/report/ReactionList/BaseReactionList'; +import type {ReactionListRef} from '@pages/home/ReportScreenContext'; import ONYXKEYS from '@src/ONYXKEYS'; -function BasePopoverReactionList(props: BasePopoverReactionListPropsWithLocalWithOnyx) { +function BasePopoverReactionList(props: BasePopoverReactionListPropsWithLocalWithOnyx, ref: ForwardedRef>) { const {emojiReactions, emojiName, reportActionID, currentUserPersonalDetails} = props; const {preferredLocale} = useLocalize(); - const {isPopoverVisible, hideReactionList, popoverAnchorPosition, reactionListRef, getReactionInformation} = useBasePopoverReactionList({ + const {isPopoverVisible, hideReactionList, showReactionList, popoverAnchorPosition, reactionListRef, getReactionInformation} = useBasePopoverReactionList({ emojiName, emojiReactions, accountID: currentUserPersonalDetails.accountID, @@ -21,6 +23,8 @@ function BasePopoverReactionList(props: BasePopoverReactionListPropsWithLocalWit // Get the reaction information const {emojiCodes, reactionCount, hasUserReacted, users} = getReactionInformation(); + useImperativeHandle(ref, () => ({hideReactionList, showReactionList})); + return ( `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, }, - })(BasePopoverReactionList), + })(forwardRef(BasePopoverReactionList)), ); diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx index e7899c814568..2cdcac65feae 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx @@ -23,6 +23,10 @@ function PopoverReactionList(props: unknown, ref: ForwardedRef) useImperativeHandle(ref, () => ({showReactionList, hideReactionList, isActiveReportAction})); + if (reactionListReportActionID === '' || reactionListEmojiName === '') { + return null; + } + return ( Date: Thu, 25 Jan 2024 15:37:52 +0700 Subject: [PATCH 036/252] lint fix --- src/CONST.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index d3f712c77e1f..d2422088d1fb 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1387,7 +1387,7 @@ const CONST = { CLOSED: 6, STATE_SUSPENDED: 7, }, - ACTIVE_STATES: cardActiveStates + ACTIVE_STATES: cardActiveStates, }, AVATAR_ROW_SIZE: { DEFAULT: 4, From 7e80328234b81dbea037a970ebee6f53558c76e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20M=C3=B3rawski?= Date: Thu, 25 Jan 2024 18:31:47 +0100 Subject: [PATCH 037/252] da refactor --- src/components/CategoryPicker/index.js | 50 ++++++++++++-------------- src/libs/OptionsListUtils.js | 2 ++ 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/components/CategoryPicker/index.js b/src/components/CategoryPicker/index.js index a957e31a9de4..ca64c1cc444a 100644 --- a/src/components/CategoryPicker/index.js +++ b/src/components/CategoryPicker/index.js @@ -1,22 +1,18 @@ import lodashGet from 'lodash/get'; -import React, {useMemo, useState} from 'react'; +import React, {useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; -import OptionsSelector from '@components/OptionsSelector'; +import SelectionList from '@components/SelectionList'; +import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {defaultProps, propTypes} from './categoryPickerPropTypes'; function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedCategories, onSubmit}) { - const styles = useThemeStyles(); const {translate} = useLocalize(); - const [searchValue, setSearchValue] = useState(''); - - const policyCategoriesCount = OptionsListUtils.getEnabledCategoriesCount(_.values(policyCategories)); - const isCategoriesCountBelowThreshold = policyCategoriesCount < CONST.CATEGORY_LIST_THRESHOLD; + const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState(''); const selectedOptions = useMemo(() => { if (!selectedCategory) { @@ -28,17 +24,18 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC name: selectedCategory, enabled: true, accountID: null, + isSelected: true, }, ]; }, [selectedCategory]); - const sections = useMemo(() => { + const [sections, headerMessage, policyCategoriesCount, shouldShowTextInput] = useMemo(() => { const validPolicyRecentlyUsedCategories = _.filter(policyRecentlyUsedCategories, (p) => !_.isEmpty(p)); const {categoryOptions} = OptionsListUtils.getFilteredOptions( {}, {}, [], - searchValue, + debouncedSearchValue, selectedOptions, [], false, @@ -49,31 +46,28 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC false, ); - return categoryOptions; - }, [policyCategories, policyRecentlyUsedCategories, searchValue, selectedOptions]); + const header = OptionsListUtils.getHeaderMessageForNonUserList(lodashGet(categoryOptions, '[0].data.length', 0) > 0, debouncedSearchValue); + const policiesCount = OptionsListUtils.getEnabledCategoriesCount(_.values(policyCategories)); + const isCategoriesCountBelowThreshold = policyCategoriesCount < CONST.CATEGORY_LIST_THRESHOLD; + const showInput = !isCategoriesCountBelowThreshold; + + return [categoryOptions, header, policiesCount, showInput]; + }, [policyCategories, policyRecentlyUsedCategories, debouncedSearchValue, selectedOptions]); - const headerMessage = OptionsListUtils.getHeaderMessageForNonUserList(lodashGet(sections, '[0].data.length', 0) > 0, searchValue); - const shouldShowTextInput = !isCategoriesCountBelowThreshold; - const selectedOptionKey = lodashGet(_.filter(lodashGet(sections, '[0].data', []), (category) => category.searchText === selectedCategory)[0], 'keyForList'); + const selectedOptionKey = useMemo( + () => lodashGet(_.filter(lodashGet(sections, '[0].data', []), (category) => category.searchText === selectedCategory)[0], 'keyForList'), + [sections, selectedCategory], + ); return ( - ); } diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index d44df3c6c39c..fbaeeb8f5bc4 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -805,6 +805,7 @@ function getCategoryOptionTree(options, isOneLine = false) { searchText: option.name, tooltipText: option.name, isDisabled: !option.enabled, + isSelected: !!option.isSelected, }); return; @@ -825,6 +826,7 @@ function getCategoryOptionTree(options, isOneLine = false) { searchText, tooltipText: optionName, isDisabled: isChild ? !option.enabled : true, + isSelected: !!option.isSelected, }); }); }); From b64181c38ee322c3a0912d7e5d375bd6eb648c69 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Fri, 26 Jan 2024 13:09:00 +0100 Subject: [PATCH 038/252] review fixes --- src/components/SelectionList/BaseListItem.tsx | 4 +- .../SelectionList/BaseSelectionList.tsx | 4 +- .../SelectionList/RadioListItem.tsx | 4 +- src/components/SelectionList/UserListItem.tsx | 4 +- .../SelectionList/index.android.tsx | 4 +- src/components/SelectionList/index.ios.tsx | 4 +- src/components/SelectionList/index.tsx | 4 +- src/components/SelectionList/types.ts | 50 ++++--------------- .../ValuePicker/ValueSelectorModal.tsx | 8 ++- src/components/ValuePicker/index.tsx | 2 +- src/components/ValuePicker/types.ts | 6 +-- 11 files changed, 31 insertions(+), 63 deletions(-) diff --git a/src/components/SelectionList/BaseListItem.tsx b/src/components/SelectionList/BaseListItem.tsx index 71845931ba52..3ee59fb5a579 100644 --- a/src/components/SelectionList/BaseListItem.tsx +++ b/src/components/SelectionList/BaseListItem.tsx @@ -11,10 +11,10 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import RadioListItem from './RadioListItem'; -import type {BaseListItemProps, RadioItem, User} from './types'; +import type {BaseListItemProps, ListItem} from './types'; import UserListItem from './UserListItem'; -function BaseListItem({ +function BaseListItem({ item, isFocused = false, isDisabled = false, diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index d97c47c84ee7..2143f76d351f 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -22,9 +22,9 @@ import variables from '@styles/variables'; import CONST from '@src/CONST'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import BaseListItem from './BaseListItem'; -import type {BaseSelectionListProps, ButtonOrCheckBoxRoles, FlattenedSectionsReturn, RadioItem, Section, SectionListDataType, User} from './types'; +import type {BaseSelectionListProps, ButtonOrCheckBoxRoles, FlattenedSectionsReturn, ListItem, Section, SectionListDataType} from './types'; -function BaseSelectionList( +function BaseSelectionList( { sections, canSelectMultiple = false, diff --git a/src/components/SelectionList/RadioListItem.tsx b/src/components/SelectionList/RadioListItem.tsx index 769eaa80df4b..e666fa6865da 100644 --- a/src/components/SelectionList/RadioListItem.tsx +++ b/src/components/SelectionList/RadioListItem.tsx @@ -3,9 +3,9 @@ import {View} from 'react-native'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; import useThemeStyles from '@hooks/useThemeStyles'; -import type {RadioListItemProps} from './types'; +import type {ListItemProps} from './types'; -function RadioListItem({item, showTooltip, textStyles, alternateTextStyles}: RadioListItemProps) { +function RadioListItem({item, showTooltip, textStyles, alternateTextStyles}: ListItemProps) { const styles = useThemeStyles(); return ( diff --git a/src/components/SelectionList/UserListItem.tsx b/src/components/SelectionList/UserListItem.tsx index 3c973ad0bbea..016d4c74d65e 100644 --- a/src/components/SelectionList/UserListItem.tsx +++ b/src/components/SelectionList/UserListItem.tsx @@ -4,9 +4,9 @@ import SubscriptAvatar from '@components/SubscriptAvatar'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; import useThemeStyles from '@hooks/useThemeStyles'; -import type {UserListItemProps} from './types'; +import type {ListItemProps} from './types'; -function UserListItem({item, textStyles, alternateTextStyles, showTooltip, style}: UserListItemProps) { +function UserListItem({item, textStyles, alternateTextStyles, showTooltip, style}: ListItemProps) { const styles = useThemeStyles(); return ( <> diff --git a/src/components/SelectionList/index.android.tsx b/src/components/SelectionList/index.android.tsx index 8487c6e2cc67..46f2af8356f6 100644 --- a/src/components/SelectionList/index.android.tsx +++ b/src/components/SelectionList/index.android.tsx @@ -3,9 +3,9 @@ import type {ForwardedRef} from 'react'; import {Keyboard} from 'react-native'; import type {TextInput} from 'react-native'; import BaseSelectionList from './BaseSelectionList'; -import type {BaseSelectionListProps, RadioItem, User} from './types'; +import type {BaseSelectionListProps, ListItem} from './types'; -function SelectionList(props: BaseSelectionListProps, ref: ForwardedRef) { +function SelectionList(props: BaseSelectionListProps, ref: ForwardedRef) { return ( (props: BaseSelectionListProps, ref: ForwardedRef) { +function SelectionList(props: BaseSelectionListProps, ref: ForwardedRef) { return ( // eslint-disable-next-line react/jsx-props-no-spreading diff --git a/src/components/SelectionList/index.tsx b/src/components/SelectionList/index.tsx index 93754926cacb..2446e1b4f5c1 100644 --- a/src/components/SelectionList/index.tsx +++ b/src/components/SelectionList/index.tsx @@ -4,9 +4,9 @@ import {Keyboard} from 'react-native'; import type {TextInput} from 'react-native'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import BaseSelectionList from './BaseSelectionList'; -import type {BaseSelectionListProps, RadioItem, User} from './types'; +import type {BaseSelectionListProps, ListItem} from './types'; -function SelectionList(props: BaseSelectionListProps, ref: ForwardedRef) { +function SelectionList(props: BaseSelectionListProps, ref: ForwardedRef) { const [isScreenTouched, setIsScreenTouched] = useState(false); const touchStart = () => setIsScreenTouched(true); diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index a82ddef6febb..c939279dc684 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -32,7 +32,7 @@ type CommonListItemProps = { rightHandSideComponent?: ((item: TItem) => ReactElement) | ReactElement | null; }; -type User = { +type ListItem = { /** Text to display */ text: string; @@ -75,49 +75,21 @@ type User = { index?: number; }; -type UserListItemProps = CommonListItemProps & { +type ListItemProps = CommonListItemProps & { /** The section list item */ - item: User; + item: ListItem; /** Additional styles to apply to text */ style?: StyleProp; }; -type RadioItem = { - /** Text to display */ - text: string; - - /** Alternate text to display */ - alternateText?: string; - - /** Key used internally by React */ - keyForList: string; - - /** Whether this option is selected */ - isSelected?: boolean; - - /** Whether this option is disabled for selection */ - isDisabled?: boolean; - - /** Represents the index of the section it came from */ - sectionIndex?: number; - - /** Represents the index of the option within the section it came from */ - index?: number; -}; - -type RadioListItemProps = CommonListItemProps & { - /** The section list item */ - item: RadioItem; -}; - -type BaseListItemProps = CommonListItemProps & { +type BaseListItemProps = CommonListItemProps & { item: TItem; shouldPreventDefaultFocusOnSelectRow?: boolean; keyForList?: string; }; -type Section = { +type Section = { /** Title of the section */ title?: string; @@ -131,7 +103,7 @@ type Section = { isDisabled?: boolean; }; -type BaseSelectionListProps = Partial & { +type BaseSelectionListProps = Partial & { /** Sections for the section list */ sections: Array>>; @@ -237,7 +209,7 @@ type ItemLayout = { offset: number; }; -type FlattenedSectionsReturn = { +type FlattenedSectionsReturn = { allOptions: TItem[]; selectedOptions: TItem[]; disabledOptionsIndexes: number[]; @@ -247,17 +219,15 @@ type FlattenedSectionsReturn = { type ButtonOrCheckBoxRoles = 'button' | 'checkbox'; -type SectionListDataType = SectionListData>; +type SectionListDataType = SectionListData>; export type { BaseSelectionListProps, CommonListItemProps, - UserListItemProps, Section, - RadioListItemProps, BaseListItemProps, - User, - RadioItem, + ListItem, + ListItemProps, FlattenedSectionsReturn, ItemLayout, ButtonOrCheckBoxRoles, diff --git a/src/components/ValuePicker/ValueSelectorModal.tsx b/src/components/ValuePicker/ValueSelectorModal.tsx index a7b5f494aa67..98e276dcc9ec 100644 --- a/src/components/ValuePicker/ValueSelectorModal.tsx +++ b/src/components/ValuePicker/ValueSelectorModal.tsx @@ -7,19 +7,17 @@ import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import type {ValuePickerItem, ValueSelectorModalProps} from './types'; -function ValueSelectorModal({items = [], selectedItem, label = '', isVisible, onClose, onItemSelected, shouldShowTooltips}: ValueSelectorModalProps) { +function ValueSelectorModal({items = [], selectedItem, label = '', isVisible, onClose, onItemSelected, shouldShowTooltips = true}: ValueSelectorModalProps) { const styles = useThemeStyles(); const [sectionsData, setSectionsData] = useState([]); useEffect(() => { - const itemsData = items.map((item, index) => ({ + const itemsData = items.map((item) => ({ value: item?.value, alternateText: item?.description, keyForList: item.value ?? '', text: item?.label ?? '', isSelected: item === selectedItem, - sectionIndex: 0, - index, })); setSectionsData(itemsData); }, [items, selectedItem]); @@ -34,7 +32,7 @@ function ValueSelectorModal({items = [], selectedItem, label = '', isVisible, on useNativeDriver > item.value === value); return ( diff --git a/src/components/ValuePicker/types.ts b/src/components/ValuePicker/types.ts index 285299bc0101..2f29f820b818 100644 --- a/src/components/ValuePicker/types.ts +++ b/src/components/ValuePicker/types.ts @@ -1,9 +1,9 @@ -import type {RadioItem} from '@components/SelectionList/types'; - -type ValuePickerItem = RadioItem & { +type ValuePickerItem = { value?: string; label?: string; description?: string; + text: string; + keyForList: string; }; type ValueSelectorModalProps = { From 9633be5b555d71e14f7026b65066a64880ba784e Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Fri, 26 Jan 2024 13:51:53 +0100 Subject: [PATCH 039/252] Change onInputChange function type --- src/components/ValuePicker/index.tsx | 2 +- src/components/ValuePicker/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ValuePicker/index.tsx b/src/components/ValuePicker/index.tsx index 8d42bb49c4fc..795395615b45 100644 --- a/src/components/ValuePicker/index.tsx +++ b/src/components/ValuePicker/index.tsx @@ -23,7 +23,7 @@ function ValuePicker({value, label, items, placeholder = '', errorText = '', onI }; const updateInput = (item: ValuePickerItem) => { - if (item?.value && item.value !== value) { + if (item.value !== value) { onInputChange?.(item.value); } hidePickerModal(); diff --git a/src/components/ValuePicker/types.ts b/src/components/ValuePicker/types.ts index 2f29f820b818..1ff2e113f606 100644 --- a/src/components/ValuePicker/types.ts +++ b/src/components/ValuePicker/types.ts @@ -46,7 +46,7 @@ type ValuePickerProps = { errorText?: string; /** Callback to call when the input changes */ - onInputChange?: (value: string) => void; + onInputChange?: (value: string | undefined) => void; /** Text to display under the main menu item */ furtherDetails?: string; From 5af50d4f9d9b6f5c513872891a7de2bbd196efaa Mon Sep 17 00:00:00 2001 From: Vadym Date: Fri, 26 Jan 2024 15:47:34 +0100 Subject: [PATCH 040/252] chore: makes hasUserReacted optional with default value --- .../report/ReactionList/BaseReactionList.tsx | 19 +++++++++---------- .../ReactionList/HeaderReactionList.tsx | 14 +++++++------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index a49c4d832f30..09471faf0b42 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -24,7 +24,7 @@ type BaseReactionListProps = ReactionListProps & { /** * Returns true if the current account has reacted to the report action (with the given skin tone). */ - hasUserReacted: boolean; + hasUserReacted?: boolean; /** * Returns true if the reaction list is visible @@ -40,10 +40,10 @@ const getItemLayout = (data: ArrayLike | null | undefined, inde offset: variables.listItemHeightNormal * index, }); -function BaseReactionList(props: BaseReactionListProps) { +function BaseReactionList({hasUserReacted = false, users, isVisible = false, emojiCodes, emojiCount, emojiName, onClose}: BaseReactionListProps) { const {isSmallScreenWidth} = useWindowDimensions(); const {hoveredComponentBG, reactionListContainer, reactionListContainerFixedWidth, pv2} = useThemeStyles(); - if (!props.isVisible) { + if (!isVisible) { return null; } @@ -59,7 +59,7 @@ function BaseReactionList(props: BaseReactionListProps) { style={{maxWidth: variables.mobileResponsiveWidthBreakpoint}} hoverStyle={hoveredComponentBG} onSelectRow={() => { - props.onClose?.(); + onClose?.(); Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID)); }} @@ -84,14 +84,13 @@ function BaseReactionList(props: BaseReactionListProps) { return ( <> & { /** Returns true if the current account has reacted to the report action (with the given skin tone). */ - hasUserReacted: boolean; + hasUserReacted?: boolean; }; -function HeaderReactionList(props: HeaderReactionListProps) { +function HeaderReactionList({emojiCodes, emojiCount, emojiName, hasUserReacted = false}: HeaderReactionListProps) { const { flexRow, justifyContentBetween, @@ -32,11 +32,11 @@ function HeaderReactionList(props: HeaderReactionListProps) { return ( - - {props.emojiCodes.join('')} - {props.emojiCount} + + {emojiCodes.join('')} + {emojiCount} - {`:${EmojiUtils.getLocalizedEmojiName(props.emojiName, preferredLocale)}:`} + {`:${EmojiUtils.getLocalizedEmojiName(emojiName, preferredLocale)}:`} ); From ce370cd4af2b566b6fcbfca4b4aaf3233f8763d4 Mon Sep 17 00:00:00 2001 From: Vadym Date: Fri, 26 Jan 2024 15:52:42 +0100 Subject: [PATCH 041/252] chore: removes unneeded JSDocs returns and comments --- src/hooks/useBasePopoverReactionList/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/hooks/useBasePopoverReactionList/index.ts b/src/hooks/useBasePopoverReactionList/index.ts index 64c5f6d8efc0..ae7afc230bf9 100644 --- a/src/hooks/useBasePopoverReactionList/index.ts +++ b/src/hooks/useBasePopoverReactionList/index.ts @@ -11,7 +11,6 @@ export default function useBasePopoverReactionList({emojiName, emojiReactions, a const [popoverAnchorPosition, setPopoverAnchorPosition] = useState({horizontal: 0, vertical: 0}); const reactionListRef = useRef(null); - // custom methods function getReactionInformation() { const selectedReaction = emojiReactions?.[emojiName]; @@ -41,8 +40,6 @@ export default function useBasePopoverReactionList({emojiName, emojiReactions, a /** * Get the BasePopoverReactionList anchor position * We calculate the achor coordinates from measureInWindow async method - * - * @returns promise */ function getReactionListMeasuredLocation(): Promise<{x: number; y: number}> { return new Promise((resolve) => { From 3fa2b09f585f5ba2567c4dc33d391802186fa5ac Mon Sep 17 00:00:00 2001 From: Vadym Date: Fri, 26 Jan 2024 15:57:07 +0100 Subject: [PATCH 042/252] fix: reuses the AnchorPosition type --- src/hooks/useBasePopoverReactionList/types.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/hooks/useBasePopoverReactionList/types.ts b/src/hooks/useBasePopoverReactionList/types.ts index 49620ccd9370..2993b2b8acb6 100644 --- a/src/hooks/useBasePopoverReactionList/types.ts +++ b/src/hooks/useBasePopoverReactionList/types.ts @@ -2,6 +2,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; import type {ReactionListAnchor, ReactionListEvent} from '@pages/home/ReportScreenContext'; +import type {AnchorPosition} from '@src/styles'; import type {ReportActionReactions} from '@src/types/onyx'; type BasePopoverReactionListOnyxProps = { @@ -33,16 +34,10 @@ type BasePopoverReactionListState = { isPopoverVisible: boolean; /** The horizontal and vertical position (relative to the screen) where the popover will display. */ - popoverAnchorPosition: { - horizontal: number; - vertical: number; - }; + popoverAnchorPosition: AnchorPosition; /** The horizontal and vertical position (relative to the screen) where the cursor is. */ - cursorRelativePosition: { - horizontal: number; - vertical: number; - }; + cursorRelativePosition: AnchorPosition; }; type ShowReactionList = (event: ReactionListEvent | undefined, reactionListAnchor: ReactionListAnchor) => void; From dde9079eb50da52c3e6930b43d40fd903e3c1dfc Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 29 Jan 2024 16:18:40 +0700 Subject: [PATCH 043/252] fix three dot --- src/components/Modal/BaseModal.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index ad3bf2d6a057..5fb3bcdfe652 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -60,7 +60,7 @@ function BaseModal( */ const hideModal = useCallback( (callHideCallback = true) => { - Modal.willAlertModalBecomeVisible(false, isPopover); + Modal.willAlertModalBecomeVisible(false); if (shouldSetModalVisibility) { Modal.setModalVisibility(false); } @@ -72,14 +72,14 @@ function BaseModal( ComposerFocusManager.setReadyToFocus(); } }, - [shouldSetModalVisibility, onModalHide, fullscreen, isPopover], + [shouldSetModalVisibility, onModalHide, fullscreen], ); useEffect(() => { isVisibleRef.current = isVisible; let removeOnCloseListener: () => void; if (isVisible) { - Modal.willAlertModalBecomeVisible(true, isPopover); + Modal.willAlertModalBecomeVisible(true, isPopover || type === CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED); // To handle closing any modal already visible when this modal is mounted, i.e. PopoverReportActionContextMenu removeOnCloseListener = Modal.setCloseModal(onClose); } @@ -90,7 +90,7 @@ function BaseModal( } removeOnCloseListener(); }; - }, [isVisible, wasVisible, onClose, isPopover]); + }, [isVisible, wasVisible, onClose, isPopover, type]); useEffect( () => () => { From a1ebabc5215a313dc31ee4f6a3a1bcd7f527aee0 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 29 Jan 2024 16:29:14 +0700 Subject: [PATCH 044/252] add comment --- src/libs/actions/Modal.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/Modal.ts b/src/libs/actions/Modal.ts index 985fe4680d1f..6351d0165544 100644 --- a/src/libs/actions/Modal.ts +++ b/src/libs/actions/Modal.ts @@ -72,6 +72,7 @@ function setModalVisibility(isVisible: boolean) { /** * Allows other parts of app to know that an alert modal is about to open. * This will trigger as soon as a modal is opened but not yet visible while animation is running. + * isPopover indicates that the next open modal is popover or bottom docked */ function willAlertModalBecomeVisible(isVisible: boolean, isPopover = false) { Onyx.merge(ONYXKEYS.MODAL, {willAlertModalBecomeVisible: isVisible, isPopover}); From 89451556b7d6fc1bb845687363aa2765c3bd8b6e Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 30 Jan 2024 14:55:58 +0700 Subject: [PATCH 045/252] fix: Focus mode - User priority mode is switched back to focus automatically --- src/libs/actions/User.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index f118797fa659..8b8f6ef4a5da 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -582,13 +582,11 @@ function updateChatPriorityMode(mode: ValueOf, autom }, ]; - if (autoSwitchedToFocusMode) { - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.NVP_TRY_FOCUS_MODE, - value: true, - }); - } + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.NVP_TRY_FOCUS_MODE, + value: true, + }); const parameters: UpdateChatPriorityModeParams = { value: mode, From 1e8cff31160ef05ee63f43dcd60691681547f27e Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Thu, 1 Feb 2024 10:38:25 +0100 Subject: [PATCH 046/252] Fix typing for ValuePicker --- src/components/Form/types.ts | 5 +++-- .../ValuePicker/ValueSelectorModal.tsx | 16 +++------------- src/components/ValuePicker/index.tsx | 2 +- src/components/ValuePicker/types.ts | 8 +++----- src/pages/workspace/WorkspaceNewRoomPage.js | 9 ++++----- 5 files changed, 14 insertions(+), 26 deletions(-) diff --git a/src/components/Form/types.ts b/src/components/Form/types.ts index 447f3205ad68..7af027a5c0c7 100644 --- a/src/components/Form/types.ts +++ b/src/components/Form/types.ts @@ -6,6 +6,7 @@ import type CheckboxWithLabel from '@components/CheckboxWithLabel'; import type Picker from '@components/Picker'; import type SingleChoiceQuestion from '@components/SingleChoiceQuestion'; import type TextInput from '@components/TextInput'; +import type ValuePicker from '@components/ValuePicker'; import type {OnyxFormKey, OnyxValues} from '@src/ONYXKEYS'; import type Form from '@src/types/onyx/Form'; import type {BaseForm, FormValueType} from '@src/types/onyx/Form'; @@ -15,9 +16,9 @@ import type {BaseForm, FormValueType} from '@src/types/onyx/Form'; * when adding new inputs or removing old ones. * * TODO: Add remaining inputs here once these components are migrated to Typescript: - * CountrySelector | StatePicker | DatePicker | EmojiPickerButtonDropdown | RoomNameInput | ValuePicker + * CountrySelector | StatePicker | DatePicker | EmojiPickerButtonDropdown | RoomNameInput */ -type ValidInputs = typeof TextInput | typeof AmountTextInput | typeof SingleChoiceQuestion | typeof CheckboxWithLabel | typeof Picker | typeof AddressSearch; +type ValidInputs = typeof TextInput | typeof AmountTextInput | typeof SingleChoiceQuestion | typeof CheckboxWithLabel | typeof Picker | typeof AddressSearch | typeof ValuePicker; type ValueTypeKey = 'string' | 'boolean' | 'date'; diff --git a/src/components/ValuePicker/ValueSelectorModal.tsx b/src/components/ValuePicker/ValueSelectorModal.tsx index 98e276dcc9ec..18b601463d20 100644 --- a/src/components/ValuePicker/ValueSelectorModal.tsx +++ b/src/components/ValuePicker/ValueSelectorModal.tsx @@ -1,26 +1,16 @@ -import React, {useEffect, useState} from 'react'; +import React, {useMemo} from 'react'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Modal from '@components/Modal'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; -import type {ValuePickerItem, ValueSelectorModalProps} from './types'; +import type {ValueSelectorModalProps} from './types'; function ValueSelectorModal({items = [], selectedItem, label = '', isVisible, onClose, onItemSelected, shouldShowTooltips = true}: ValueSelectorModalProps) { const styles = useThemeStyles(); - const [sectionsData, setSectionsData] = useState([]); - useEffect(() => { - const itemsData = items.map((item) => ({ - value: item?.value, - alternateText: item?.description, - keyForList: item.value ?? '', - text: item?.label ?? '', - isSelected: item === selectedItem, - })); - setSectionsData(itemsData); - }, [items, selectedItem]); + const sectionsData = useMemo(() => items.map((item) => ({...item, isSelected: item === selectedItem, keyForList: item.value ?? ''})), [items, selectedItem]); return ( _.map(PolicyUtils.getActivePolicies(props.policies), (policy) => ({ - label: policy.name, - key: policy.id, + text: policy.name, value: policy.id, })), [props.policies], @@ -222,7 +221,7 @@ function WorkspaceNewRoomPage(props) { () => _.map(CONST.REPORT.WRITE_CAPABILITIES, (value) => ({ value, - label: translate(`writeCapabilityPage.writeCapability.${value}`), + text: translate(`writeCapabilityPage.writeCapability.${value}`), })), [translate], ); @@ -232,9 +231,9 @@ function WorkspaceNewRoomPage(props) { _.map( _.filter(_.values(CONST.REPORT.VISIBILITY), (visibilityOption) => visibilityOption !== CONST.REPORT.VISIBILITY.PUBLIC_ANNOUNCE), (visibilityOption) => ({ - label: translate(`newRoomPage.visibilityOptions.${visibilityOption}`), + text: translate(`newRoomPage.visibilityOptions.${visibilityOption}`), value: visibilityOption, - description: translate(`newRoomPage.${visibilityOption}Description`), + alternateText: translate(`newRoomPage.${visibilityOption}Description`), }), ), [translate], From f0871a0e6d9a688000c12a5c9830eb154a3a0673 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 1 Feb 2024 17:09:57 +0700 Subject: [PATCH 047/252] lint fix --- src/pages/settings/Wallet/PaymentMethodList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index ed776ffc213f..15c97a82989a 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -30,8 +30,8 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {AccountData, BankAccountList, CardList, FundList} from '@src/types/onyx'; -import {BankIcon} from '@src/types/onyx/Bank'; -import {Errors} from '@src/types/onyx/OnyxCommon'; +import type {BankIcon} from '@src/types/onyx/Bank'; +import type {Errors} from '@src/types/onyx/OnyxCommon'; import type PaymentMethod from '@src/types/onyx/PaymentMethod'; import type IconAsset from '@src/types/utils/IconAsset'; From b58badfdf09cd86be5606d5817f9c49d886f27fb Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Thu, 1 Feb 2024 20:39:11 +0530 Subject: [PATCH 048/252] Migrate MoneyRequestAmountForm to typescript --- ...ountForm.js => MoneyRequestAmountForm.tsx} | 123 ++++++++---------- 1 file changed, 54 insertions(+), 69 deletions(-) rename src/pages/iou/steps/{MoneyRequestAmountForm.js => MoneyRequestAmountForm.tsx} (81%) diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.js b/src/pages/iou/steps/MoneyRequestAmountForm.tsx similarity index 81% rename from src/pages/iou/steps/MoneyRequestAmountForm.js rename to src/pages/iou/steps/MoneyRequestAmountForm.tsx index 8775562d4476..69f48f231d91 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.js +++ b/src/pages/iou/steps/MoneyRequestAmountForm.tsx @@ -1,12 +1,8 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {ScrollView, View} from 'react-native'; -import _ from 'underscore'; import BigNumberPad from '@components/BigNumberPad'; import Button from '@components/Button'; import FormHelpMessage from '@components/FormHelpMessage'; -import refPropTypes from '@components/refPropTypes'; import TextInputWithCurrencySymbol from '@components/TextInputWithCurrencySymbol'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -18,68 +14,61 @@ import getOperatingSystem from '@libs/getOperatingSystem'; import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; +import type {ValueOf} from 'type-fest'; +import type {TextInput} from 'react-native' + +type MoneyRequestAmountFormProps = { + /** IOU amount saved in Onyx */ + amount?: number; + + /** Calculated tax amount based on selected tax rate */ + taxAmount?: number; + + /** Currency chosen by user or saved in Onyx */ + currency?: string; + + /** Whether the amount is being edited or not */ + isEditing?: boolean; + + /** Refs forwarded to the TextInputWithCurrencySymbol */ + forwardedRef?: refPropTypes, + + /** Fired when back button pressed, navigates to currency selection page */ + onCurrencyButtonPress: () => void; + + /** Fired when submit button pressed, saves the given amount and navigates to the next page */ + onSubmitButtonPress: ({amount, currency}: {amount: string, currency: string}) => void; + + /** The current tab we have navigated to in the request modal. String that corresponds to the request type. */ + selectedTab?: ValueOf; +} -const propTypes = { - /** IOU amount saved in Onyx */ - amount: PropTypes.number, - - /** Calculated tax amount based on selected tax rate */ - taxAmount: PropTypes.number, - - /** Currency chosen by user or saved in Onyx */ - currency: PropTypes.string, - - /** Whether the amount is being edited or not */ - isEditing: PropTypes.bool, - - /** Refs forwarded to the TextInputWithCurrencySymbol */ - forwardedRef: refPropTypes, - - /** Fired when back button pressed, navigates to currency selection page */ - onCurrencyButtonPress: PropTypes.func.isRequired, - - /** Fired when submit button pressed, saves the given amount and navigates to the next page */ - onSubmitButtonPress: PropTypes.func.isRequired, - - /** The current tab we have navigated to in the request modal. String that corresponds to the request type. */ - selectedTab: PropTypes.oneOf([CONST.TAB_REQUEST.DISTANCE, CONST.TAB_REQUEST.MANUAL, CONST.TAB_REQUEST.SCAN]), -}; - -const defaultProps = { - amount: 0, - taxAmount: 0, - currency: CONST.CURRENCY.USD, - forwardedRef: null, - isEditing: false, - selectedTab: CONST.TAB_REQUEST.MANUAL, -}; +type Selection = { + start: number; + end: number; +} /** * Returns the new selection object based on the updated amount's length - * - * @param {Object} oldSelection - * @param {Number} prevLength - * @param {Number} newLength - * @returns {Object} */ -const getNewSelection = (oldSelection, prevLength, newLength) => { +const getNewSelection = (oldSelection: Selection, prevLength: number, newLength: number): Selection => { const cursorPosition = oldSelection.end + (newLength - prevLength); return {start: cursorPosition, end: cursorPosition}; }; -const isAmountInvalid = (amount) => !amount.length || parseFloat(amount) < 0.01; -const isTaxAmountInvalid = (currentAmount, taxAmount, isTaxAmountForm) => isTaxAmountForm && currentAmount > CurrencyUtils.convertToFrontendAmount(taxAmount); +const isAmountInvalid = (amount: string) => !amount.length || parseFloat(amount) < 0.01; +const isTaxAmountInvalid = (currentAmount: string, taxAmount: number, isTaxAmountForm: boolean) => isTaxAmountForm && currentAmount > CurrencyUtils.convertToFrontendAmount(taxAmount); const AMOUNT_VIEW_ID = 'amountView'; const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView'; const NUM_PAD_VIEW_ID = 'numPadView'; -function MoneyRequestAmountForm({amount, taxAmount, currency, isEditing, forwardedRef, onCurrencyButtonPress, onSubmitButtonPress, selectedTab}) { +function MoneyRequestAmountForm({amount = 0, taxAmount = 0, currency = CONST.CURRENCY.USD, isEditing = false, forwardedRef = null, onCurrencyButtonPress, onSubmitButtonPress, selectedTab = CONST.TAB_REQUEST.MANUAL}: MoneyRequestAmountFormProps) { const styles = useThemeStyles(); const {isExtraSmallScreenHeight} = useWindowDimensions(); const {translate, toLocaleDigit, numberFormat} = useLocalize(); - const textInput = useRef(null); + const textInput = useRef(null); const isTaxAmountForm = Navigation.getActiveRoute().includes('taxAmount'); const decimals = CurrencyUtils.getCurrencyDecimals(currency); @@ -100,15 +89,14 @@ function MoneyRequestAmountForm({amount, taxAmount, currency, isEditing, forward /** * Event occurs when a user presses a mouse button over an DOM element. - * - * @param {Event} event - * @param {Array} ids */ - const onMouseDown = (event, ids) => { - const relatedTargetId = lodashGet(event, 'nativeEvent.target.id'); - if (!_.contains(ids, relatedTargetId)) { + const onMouseDown = (event: MouseEvent, ids: string[]) => { + // const relatedTargetId = lodashGet(event, 'nativeEvent.target.id'); + const relatedTargetId = event.nativeEvent.target.id; + if (ids.includes(relatedTargetId)) { return; } + event.preventDefault(); if (!textInput.current) { return; @@ -118,7 +106,7 @@ function MoneyRequestAmountForm({amount, taxAmount, currency, isEditing, forward } }; - const initializeAmount = useCallback((newAmount) => { + const initializeAmount = useCallback((newAmount: number) => { const frontendAmount = newAmount ? CurrencyUtils.convertToFrontendAmount(newAmount).toString() : ''; setCurrentAmount(frontendAmount); setSelection({ @@ -141,7 +129,7 @@ function MoneyRequestAmountForm({amount, taxAmount, currency, isEditing, forward * @param {String} newAmount - Changed amount from user input */ const setNewAmount = useCallback( - (newAmount) => { + (newAmount: string) => { // Remove spaces from the newAmount value because Safari on iOS adds spaces when pasting a copied value // More info: https://github.com/Expensify/App/issues/16974 const newAmountWithoutSpaces = MoneyRequestUtils.stripSpacesFromAmount(newAmount); @@ -188,13 +176,11 @@ function MoneyRequestAmountForm({amount, taxAmount, currency, isEditing, forward /** * Update amount with number or Backspace pressed for BigNumberPad. * Validate new amount with decimal number regex up to 6 digits and 2 decimal digit to enable Next button - * - * @param {String} key */ const updateAmountNumberPad = useCallback( - (key) => { - if (shouldUpdateSelection && !textInput.current.isFocused()) { - textInput.current.focus(); + (key: string) => { + if (shouldUpdateSelection && !textInput.current?.isFocused()) { + textInput.current?.focus(); } // Backspace button is pressed if (key === '<' || key === 'Backspace') { @@ -214,12 +200,12 @@ function MoneyRequestAmountForm({amount, taxAmount, currency, isEditing, forward /** * Update long press value, to remove items pressing on < * - * @param {Boolean} value - Changed text from user input + * @param value - Changed text from user input */ - const updateLongPressHandlerState = useCallback((value) => { + const updateLongPressHandlerState = useCallback((value: boolean) => { // param description looks wrong setShouldUpdateSelection(!value); - if (!value && !textInput.current.isFocused()) { - textInput.current.focus(); + if (!value && !textInput.current?.isFocused()) { + textInput.current?.focus(); } }, []); @@ -261,7 +247,7 @@ function MoneyRequestAmountForm({amount, taxAmount, currency, isEditing, forward forwardDeletePressedRef.current = key === 'delete' || (_.contains([CONST.OS.MAC_OS, CONST.OS.IOS], getOperatingSystem()) && nativeEvent.ctrlKey && key === 'd'); }; - const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit); + const formattedAmount: string = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit); const buttonText = isEditing ? translate('common.save') : translate('common.next'); const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen(); @@ -317,7 +303,8 @@ function MoneyRequestAmountForm({amount, taxAmount, currency, isEditing, forward ) : null}