From 10f9139aa38b5242f86c6e7aa1e4a8b71cf4378f Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 24 Jan 2024 18:04:27 +0100 Subject: [PATCH 1/9] Migrate WorkspaceInviteMessagePage to TypeScript --- src/ONYXKEYS.ts | 1 + src/libs/OptionsListUtils.js | 2 +- ...Page.js => WorkspaceInviteMessagePage.tsx} | 138 ++++++++---------- .../workspace/WorkspacePageWithSections.tsx | 8 +- .../invoices/WorkspaceInvoicesPage.tsx | 9 +- src/pages/workspace/withPolicy.tsx | 7 +- 6 files changed, 76 insertions(+), 89 deletions(-) rename src/pages/workspace/{WorkspaceInviteMessagePage.js => WorkspaceInviteMessagePage.tsx} (60%) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 9693c907a5fe..a9e59a55e172 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -460,6 +460,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_REPORT_FIELDS]: OnyxTypes.RecentlyUsedReportFields; [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMembers; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: Record; + [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT]: string; [ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report; [ONYXKEYS.COLLECTION.REPORT_METADATA]: OnyxTypes.ReportMetadata; [ONYXKEYS.COLLECTION.REPORT_ACTIONS]: OnyxTypes.ReportActions; diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 2973228af51f..2bd15fc96983 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -200,7 +200,7 @@ function getPersonalDetailsForAccountIDs(accountIDs, personalDetails) { /** * Return true if personal details data is ready, i.e. report list options can be created. - * @param {Object} personalDetails + * @param {Object | null} personalDetails * @returns {Boolean} */ function isPersonalDetailsReady(personalDetails) { diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.js b/src/pages/workspace/WorkspaceInviteMessagePage.tsx similarity index 60% rename from src/pages/workspace/WorkspaceInviteMessagePage.js rename to src/pages/workspace/WorkspaceInviteMessagePage.tsx index 00bdce30891a..22bdd9c8db94 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.js +++ b/src/pages/workspace/WorkspaceInviteMessagePage.tsx @@ -1,9 +1,10 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; +import type {StackScreenProps} from '@react-navigation/stack'; +import lodashDebounce from 'lodash/debounce'; import React, {useEffect, useState} from 'react'; import {Keyboard, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {OnyxEntry} from 'react-native-onyx'; +import type {GestureResponderEvent} from 'react-native/Libraries/Types/CoreEventTypes'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; @@ -13,118 +14,96 @@ import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeed import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; -import withNavigationFocus from '@components/withNavigationFocus'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; +import type {SettingsNavigatorParamList} from '@navigation/types'; import * as Link from '@userActions/Link'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import type {PersonalDetailsList} from '@src/types/onyx'; +import type {Errors, Icon} from '@src/types/onyx/OnyxCommon'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import SearchInputManager from './SearchInputManager'; -import {policyDefaultProps, policyPropTypes} from './withPolicy'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; +import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading'; -const personalDetailsPropTypes = PropTypes.shape({ - /** The accountID of the person */ - accountID: PropTypes.number.isRequired, - - /** The login of the person (either email or phone number) */ - login: PropTypes.string, - - /** The URL of the person's avatar (there should already be a default avatar if - the person doesn't have their own avatar uploaded yet, except for anon users) */ - avatar: PropTypes.string, - - /** This is either the user's full name, or their login if full name is an empty string */ - displayName: PropTypes.string, -}); - -const propTypes = { +type WorkspaceInviteMessagePageOnyxProps = { /** All of the personal details for everyone */ - allPersonalDetails: PropTypes.objectOf(personalDetailsPropTypes), - - invitedEmailsToAccountIDsDraft: PropTypes.objectOf(PropTypes.number), + allPersonalDetails: OnyxEntry; - /** URL Route params */ - route: PropTypes.shape({ - /** Params from the URL path */ - params: PropTypes.shape({ - /** policyID passed via route: /workspace/:policyID/invite-message */ - policyID: PropTypes.string, - }), - }).isRequired, + /** An object containing the accountID for every invited user email */ + invitedEmailsToAccountIDsDraft: OnyxEntry>; - ...policyPropTypes, + /** Updated workspace invite message */ + workspaceInviteMessageDraft: OnyxEntry; }; -const defaultProps = { - ...policyDefaultProps, - allPersonalDetails: {}, - invitedEmailsToAccountIDsDraft: {}, -}; +type WorkspaceInviteMessagePageProps = WithPolicyAndFullscreenLoadingProps & + WorkspaceInviteMessagePageOnyxProps & + StackScreenProps; -function WorkspaceInviteMessagePage(props) { +function WorkspaceInviteMessagePage({workspaceInviteMessageDraft, invitedEmailsToAccountIDsDraft, policy, route, allPersonalDetails}: WorkspaceInviteMessagePageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const [welcomeNote, setWelcomeNote] = useState(); + const [welcomeNote, setWelcomeNote] = useState(); const {inputCallbackRef} = useAutoFocusInput(); const getDefaultWelcomeNote = () => - props.workspaceInviteMessageDraft || + // workspaceInviteMessageDraft can be an empty string + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + workspaceInviteMessageDraft || translate('workspace.inviteMessage.welcomeNote', { - workspaceName: props.policy.name, + workspaceName: policy?.name ?? '', }); useEffect(() => { - if (!_.isEmpty(props.invitedEmailsToAccountIDsDraft)) { + if (!isEmptyObject(invitedEmailsToAccountIDsDraft)) { setWelcomeNote(getDefaultWelcomeNote()); return; } - Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(props.route.params.policyID), true); + Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(route.params.policyID), true); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const debouncedSaveDraft = _.debounce((newDraft) => { - Policy.setWorkspaceInviteMessageDraft(props.route.params.policyID, newDraft); + const debouncedSaveDraft = lodashDebounce((newDraft: string) => { + Policy.setWorkspaceInviteMessageDraft(route.params.policyID, newDraft); }); const sendInvitation = () => { Keyboard.dismiss(); - Policy.addMembersToWorkspace(props.invitedEmailsToAccountIDsDraft, welcomeNote, props.route.params.policyID); - Policy.setWorkspaceInviteMembersDraft(props.route.params.policyID, {}); + Policy.addMembersToWorkspace(invitedEmailsToAccountIDsDraft ?? {}, welcomeNote ?? '', route.params.policyID); + Policy.setWorkspaceInviteMembersDraft(route.params.policyID, {}); SearchInputManager.searchInput = ''; // Pop the invite message page before navigating to the members page. Navigation.goBack(ROUTES.HOME); - Navigation.navigate(ROUTES.WORKSPACE_MEMBERS.getRoute(props.route.params.policyID)); + Navigation.navigate(ROUTES.WORKSPACE_MEMBERS.getRoute(route.params.policyID)); }; - /** - * Opens privacy url as an external link - * @param {Object} event - */ - const openPrivacyURL = (event) => { - event.preventDefault(); + /** Opens privacy url as an external link */ + const openPrivacyURL = (event: GestureResponderEvent | KeyboardEvent | undefined) => { + event?.preventDefault(); Link.openExternalLink(CONST.PRIVACY_URL); }; - const validate = () => { - const errorFields = {}; - if (_.isEmpty(props.invitedEmailsToAccountIDsDraft)) { + const validate = (): Errors => { + const errorFields: Errors = {}; + if (isEmptyObject(invitedEmailsToAccountIDsDraft)) { errorFields.welcomeMessage = 'workspace.inviteMessage.inviteNoMembersError'; } return errorFields; }; - const policyName = lodashGet(props.policy, 'name'); + const policyName = policy?.name; return ( Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} > Navigation.dismissModal()} - onBackButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(props.route.params.policyID))} + onBackButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(route.params.policyID))} /> + {/* @ts-expect-error TODO: Remove this once FormProvider (https://github.com/Expensify/App/issues/31972) is migrated to TypeScript. */} { + onChangeText={(text: string) => { setWelcomeNote(text); debouncedSaveDraft(text); }} - ref={(el) => { - if (!el) { + ref={(element) => { + if (!element) { return; } - inputCallbackRef(el); - updateMultilineInputRange(el); + inputCallbackRef(element); + updateMultilineInputRange(element); }} /> @@ -210,13 +198,10 @@ function WorkspaceInviteMessagePage(props) { ); } -WorkspaceInviteMessagePage.propTypes = propTypes; -WorkspaceInviteMessagePage.defaultProps = defaultProps; WorkspaceInviteMessagePage.displayName = 'WorkspaceInviteMessagePage'; -export default compose( - withPolicyAndFullscreenLoading, - withOnyx({ +export default withPolicyAndFullscreenLoading( + withOnyx({ allPersonalDetails: { key: ONYXKEYS.PERSONAL_DETAILS_LIST, }, @@ -226,6 +211,5 @@ export default compose( workspaceInviteMessageDraft: { key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT}${route.params.policyID.toString()}`, }, - }), - withNavigationFocus, -)(WorkspaceInviteMessagePage); + })(WorkspaceInviteMessagePage), +); diff --git a/src/pages/workspace/WorkspacePageWithSections.tsx b/src/pages/workspace/WorkspacePageWithSections.tsx index 8817f813a990..48e8d60a5a51 100644 --- a/src/pages/workspace/WorkspacePageWithSections.tsx +++ b/src/pages/workspace/WorkspacePageWithSections.tsx @@ -1,4 +1,3 @@ -import type {RouteProp} from '@react-navigation/native'; import React, {useEffect, useMemo, useRef} from 'react'; import type {ReactNode} from 'react'; import {View} from 'react-native'; @@ -20,6 +19,7 @@ import ROUTES from '@src/ROUTES'; import type {Route} from '@src/ROUTES'; import type {Policy, ReimbursementAccount, User} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import type {PolicyRoute} from './withPolicy'; import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; @@ -40,7 +40,7 @@ type WorkspacePageWithSectionsProps = WithPolicyAndFullscreenLoadingProps & headerText: string; /** The route object passed to this page from the navigator */ - route: RouteProp<{params: {policyID: string}}>; + route: PolicyRoute; /** Main content of the page */ children: (hasVBA?: boolean, policyID?: string, isUsingECard?: boolean) => ReactNode; @@ -92,7 +92,7 @@ function WorkspacePageWithSections({ const isLoading = reimbursementAccount?.isLoading ?? true; const achState = reimbursementAccount?.achData?.state ?? ''; const isUsingECard = user?.isUsingExpensifyCard ?? false; - const policyID = route.params.policyID; + const policyID = route.params?.policyID; const policyName = policy?.name; const hasVBA = achState === BankAccount.STATE.OPEN; const content = children(hasVBA, policyID, isUsingECard); @@ -132,7 +132,7 @@ function WorkspacePageWithSections({ subtitle={policyName} shouldShowGetAssistanceButton guidesCallTaskID={guidesCallTaskID} - onBackButtonPress={() => Navigation.goBack(backButtonRoute ?? ROUTES.WORKSPACE_INITIAL.getRoute(policyID))} + onBackButtonPress={() => Navigation.goBack(backButtonRoute ?? ROUTES.WORKSPACE_INITIAL.getRoute(policyID ?? ''))} /> {(isLoading || firstRender.current) && shouldShowLoading ? ( diff --git a/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx b/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx index 79ff76204c69..ffd9a700ae7e 100644 --- a/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx +++ b/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx @@ -1,15 +1,14 @@ -import type {RouteProp} from '@react-navigation/native'; +import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; import useLocalize from '@hooks/useLocalize'; +import type {SettingsNavigatorParamList} from '@navigation/types'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; import CONST from '@src/CONST'; +import type SCREENS from '@src/SCREENS'; import WorkspaceInvoicesNoVBAView from './WorkspaceInvoicesNoVBAView'; import WorkspaceInvoicesVBAView from './WorkspaceInvoicesVBAView'; -/** Defined route object that contains the policyID param, WorkspacePageWithSections is a common component for Workspaces and expect the route prop that includes the policyID */ -type WorkspaceInvoicesPageProps = { - route: RouteProp<{params: {policyID: string}}>; -}; +type WorkspaceInvoicesPageProps = StackScreenProps; function WorkspaceInvoicesPage({route}: WorkspaceInvoicesPageProps) { const {translate} = useLocalize(); diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index ec38b61fb0dc..aee03f1f74e9 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -5,13 +5,16 @@ import type {ComponentType, ForwardedRef, RefAttributes} from 'react'; import React, {forwardRef} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import type {SettingsNavigatorParamList} from '@navigation/types'; import policyMemberPropType from '@pages/policyMemberPropType'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; -type PolicyRoute = RouteProp<{params: {policyID: string}}>; +type PolicyRoute = RouteProp>; function getPolicyIDFromRoute(route: PolicyRoute): string { return route?.params?.policyID ?? ''; @@ -131,4 +134,4 @@ export default function (WrappedComponent: } export {policyPropTypes, policyDefaultProps}; -export type {WithPolicyOnyxProps, WithPolicyProps}; +export type {WithPolicyOnyxProps, WithPolicyProps, PolicyRoute}; From 3d4a3e24fc5ca7baf97b9debbd288c8e9c4f0575 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 29 Jan 2024 16:25:12 +0100 Subject: [PATCH 2/9] Migrate WorkspaceInvitePage to TypeScript --- src/ONYXKEYS.ts | 4 +- src/libs/OptionsListUtils.ts | 7 +- .../workspace/WorkspaceInviteMessagePage.tsx | 26 +- ...eInvitePage.js => WorkspaceInvitePage.tsx} | 224 +++++++++--------- .../onyx/InvitedEmailsToAccountIDsDraft.ts | 3 + src/types/onyx/index.ts | 2 + 6 files changed, 129 insertions(+), 137 deletions(-) rename src/pages/workspace/{WorkspaceInvitePage.js => WorkspaceInvitePage.tsx} (58%) create mode 100644 src/types/onyx/InvitedEmailsToAccountIDsDraft.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 8f8627a2927d..7aa89de1caa4 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -453,8 +453,8 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS]: OnyxTypes.PolicyReportFields; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_REPORT_FIELDS]: OnyxTypes.RecentlyUsedReportFields; [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMembers; - [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: Record; - [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT]: string; + [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: OnyxTypes.InvitedEmailsToAccountIDsDraft | undefined; + [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT]: string | undefined; [ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report; [ONYXKEYS.COLLECTION.REPORT_METADATA]: OnyxTypes.ReportMetadata; [ONYXKEYS.COLLECTION.REPORT_ACTIONS]: OnyxTypes.ReportActions; diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 6332a57deec0..93664f1dbc21 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -310,9 +310,9 @@ function getPersonalDetailsForAccountIDs(accountIDs: number[] | undefined, perso /** * Return true if personal details data is ready, i.e. report list options can be created. */ -function isPersonalDetailsReady(personalDetails: OnyxEntry): boolean { - const personalDetailsKeys = Object.keys(personalDetails ?? {}); - return personalDetailsKeys.some((key) => personalDetails?.[key]?.accountID); +function isPersonalDetailsReady(personalDetails: OnyxEntry | ReportUtils.OptionData[]): boolean { + const personalDetailsValues = Array.isArray(personalDetails) ? personalDetails : Object.values(personalDetails ?? {}); + return personalDetailsValues.some((personalDetail) => personalDetail?.accountID); } /** @@ -1993,3 +1993,4 @@ export { formatSectionsFromSearchTerm, transformedTaxRates, }; +export type {MemberForList}; diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.tsx b/src/pages/workspace/WorkspaceInviteMessagePage.tsx index 22bdd9c8db94..bd206811ff5a 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessagePage.tsx @@ -11,6 +11,7 @@ import InputWrapper from '@components/Form/InputWrapper'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MultipleAvatars from '@components/MultipleAvatars'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import type {AnimatedTextInputRef} from '@components/RNTextInput'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; @@ -28,8 +29,8 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {PersonalDetailsList} from '@src/types/onyx'; -import type {Errors, Icon} from '@src/types/onyx/OnyxCommon'; +import type {InvitedEmailsToAccountIDsDraft, PersonalDetailsList} from '@src/types/onyx'; +import type {Errors} from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import SearchInputManager from './SearchInputManager'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; @@ -40,10 +41,10 @@ type WorkspaceInviteMessagePageOnyxProps = { allPersonalDetails: OnyxEntry; /** An object containing the accountID for every invited user email */ - invitedEmailsToAccountIDsDraft: OnyxEntry>; + invitedEmailsToAccountIDsDraft: OnyxEntry; /** Updated workspace invite message */ - workspaceInviteMessageDraft: OnyxEntry; + workspaceInviteMessageDraft: OnyxEntry; }; type WorkspaceInviteMessagePageProps = WithPolicyAndFullscreenLoadingProps & @@ -124,7 +125,6 @@ function WorkspaceInviteMessagePage({workspaceInviteMessageDraft, invitedEmailsT onCloseButtonPress={() => Navigation.dismissModal()} onBackButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(route.params.policyID))} /> - {/* @ts-expect-error TODO: Remove this once FormProvider (https://github.com/Expensify/App/issues/31972) is migrated to TypeScript. */} { + ref={(element: AnimatedTextInputRef) => { if (!element) { return; } diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.tsx similarity index 58% rename from src/pages/workspace/WorkspaceInvitePage.js rename to src/pages/workspace/WorkspaceInvitePage.tsx index 72f3747c127c..172dd12314fa 100644 --- a/src/pages/workspace/WorkspaceInvitePage.js +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -1,11 +1,10 @@ import {useNavigation} from '@react-navigation/native'; +import type {StackNavigationProp, StackScreenProps} from '@react-navigation/stack'; import Str from 'expensify-common/lib/str'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {OnyxEntry} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -14,85 +13,73 @@ import SelectionList from '@components/SelectionList'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as LoginUtils from '@libs/LoginUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import {MemberForList} from '@libs/OptionsListUtils'; import {parsePhoneNumber} from '@libs/PhoneNumber'; import * as PolicyUtils from '@libs/PolicyUtils'; +import type {OptionData} from '@libs/ReportUtils'; +import type {SettingsNavigatorParamList} from '@navigation/types'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import type {Beta, InvitedEmailsToAccountIDsDraft, PersonalDetailsList} from '@src/types/onyx'; +import type {Errors} from '@src/types/onyx/OnyxCommon'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import SearchInputManager from './SearchInputManager'; -import {policyDefaultProps, policyPropTypes} from './withPolicy'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; +import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading'; -const personalDetailsPropTypes = PropTypes.shape({ - /** The login of the person (either email or phone number) */ - login: PropTypes.string, +type SelectedOption = Partial; - /** The URL of the person's avatar (there should already be a default avatar if - the person doesn't have their own avatar uploaded yet, except for anon users) */ - avatar: PropTypes.string, - - /** This is either the user's full name, or their login if full name is an empty string */ - displayName: PropTypes.string, -}); +type WorkspaceInvitePageOnyxProps = { + /** All of the personal details for everyone */ + personalDetails: OnyxEntry; -const propTypes = { /** Beta features list */ - betas: PropTypes.arrayOf(PropTypes.string), - - /** All of the personal details for everyone */ - personalDetails: PropTypes.objectOf(personalDetailsPropTypes), - - /** URL Route params */ - route: PropTypes.shape({ - /** Params from the URL path */ - params: PropTypes.shape({ - /** policyID passed via route: /workspace/:policyID/invite */ - policyID: PropTypes.string, - }), - }).isRequired, - - isLoadingReportData: PropTypes.bool, - invitedEmailsToAccountIDsDraft: PropTypes.objectOf(PropTypes.number), - ...policyPropTypes, -}; + betas: OnyxEntry; -const defaultProps = { - personalDetails: {}, - betas: [], - isLoadingReportData: true, - invitedEmailsToAccountIDsDraft: {}, - ...policyDefaultProps, + /** An object containing the accountID for every invited user email */ + invitedEmailsToAccountIDsDraft: OnyxEntry; }; -function WorkspaceInvitePage(props) { +type WorkspaceInvitePageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceInvitePageOnyxProps & StackScreenProps; + +function WorkspaceInvitePage({ + route, + policyMembers, + personalDetails: personalDetailsProp, + betas, + invitedEmailsToAccountIDsDraft, + policy, + isLoadingReportData = true, +}: WorkspaceInvitePageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [searchTerm, setSearchTerm] = useState(''); - const [selectedOptions, setSelectedOptions] = useState([]); - const [personalDetails, setPersonalDetails] = useState([]); - const [usersToInvite, setUsersToInvite] = useState([]); + const [selectedOptions, setSelectedOptions] = useState([]); + const [personalDetails, setPersonalDetails] = useState([]); + const [usersToInvite, setUsersToInvite] = useState([]); const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); - const navigation = useNavigation(); + const navigation = useNavigation>(); const openWorkspaceInvitePage = () => { - const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(props.policyMembers, props.personalDetails); - Policy.openWorkspaceInvitePage(props.route.params.policyID, _.keys(policyMemberEmailsToAccountIDs)); + const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policyMembers, personalDetailsProp); + Policy.openWorkspaceInvitePage(route.params.policyID, Object.keys(policyMemberEmailsToAccountIDs)); }; useEffect(() => { setSearchTerm(SearchInputManager.searchInput); return () => { - Policy.setWorkspaceInviteMembersDraft(props.route.params.policyID, {}); + Policy.setWorkspaceInviteMembersDraft(route.params.policyID, {}); }; - }, [props.route.params.policyID]); + }, [route.params.policyID]); useEffect(() => { - Policy.clearErrors(props.route.params.policyID); + Policy.clearErrors(route.params.policyID); openWorkspaceInvitePage(); // eslint-disable-next-line react-hooks/exhaustive-deps -- policyID changes remount the component }, []); @@ -111,54 +98,66 @@ function WorkspaceInvitePage(props) { useNetwork({onReconnect: openWorkspaceInvitePage}); - const excludedUsers = useMemo(() => PolicyUtils.getIneligibleInvitees(props.policyMembers, props.personalDetails), [props.policyMembers, props.personalDetails]); + const excludedUsers = useMemo(() => PolicyUtils.getIneligibleInvitees(policyMembers, personalDetailsProp), [policyMembers, personalDetailsProp]); useEffect(() => { - const newUsersToInviteDict = {}; - const newPersonalDetailsDict = {}; - const newSelectedOptionsDict = {}; + const newUsersToInviteDict: Record = {}; + const newPersonalDetailsDict: Record = {}; + const newSelectedOptionsDict: Record = {}; - const inviteOptions = OptionsListUtils.getMemberInviteOptions(props.personalDetails, props.betas, searchTerm, excludedUsers, true); + const inviteOptions = OptionsListUtils.getMemberInviteOptions(personalDetailsProp, betas ?? [], searchTerm, excludedUsers, true); // Update selectedOptions with the latest personalDetails and policyMembers information - const detailsMap = {}; - _.each(inviteOptions.personalDetails, (detail) => (detailsMap[detail.login] = OptionsListUtils.formatMemberForList(detail))); + const detailsMap: Record = {}; + inviteOptions.personalDetails.forEach((detail) => { + if (!detail.login) { + return; + } - const newSelectedOptions = []; - _.each(_.keys(props.invitedEmailsToAccountIDsDraft), (login) => { - if (!_.has(detailsMap, login)) { + detailsMap[detail.login] = OptionsListUtils.formatMemberForList(detail); + }); + + const newSelectedOptions: SelectedOption[] = []; + Object.keys(invitedEmailsToAccountIDsDraft ?? {}).forEach((login) => { + if (!(login in detailsMap)) { return; } newSelectedOptions.push({...detailsMap[login], isSelected: true}); }); - _.each(selectedOptions, (option) => { - newSelectedOptions.push(_.has(detailsMap, option.login) ? {...detailsMap[option.login], isSelected: true} : option); + selectedOptions.forEach((option) => { + newSelectedOptions.push(option.login && option.login in detailsMap ? {...detailsMap[option.login], isSelected: true} : option); }); const userToInvite = inviteOptions.userToInvite; // Only add the user to the invites list if it is valid - if (userToInvite) { + if (typeof userToInvite?.accountID === 'number') { newUsersToInviteDict[userToInvite.accountID] = userToInvite; } // Add all personal details to the new dict - _.each(inviteOptions.personalDetails, (details) => { + inviteOptions.personalDetails.forEach((details) => { + if (typeof details.accountID !== 'number') { + return; + } newPersonalDetailsDict[details.accountID] = details; }); // Add all selected options to the new dict - _.each(newSelectedOptions, (option) => { + newSelectedOptions.forEach((option) => { + if (typeof option.accountID !== 'number') { + return; + } newSelectedOptionsDict[option.accountID] = option; }); // Strip out dictionary keys and update arrays - setUsersToInvite(_.values(newUsersToInviteDict)); - setPersonalDetails(_.values(newPersonalDetailsDict)); - setSelectedOptions(_.values(newSelectedOptionsDict)); + setUsersToInvite(Object.values(newUsersToInviteDict)); + setPersonalDetails(Object.values(newPersonalDetailsDict)); + setSelectedOptions(Object.values(newSelectedOptionsDict)); // eslint-disable-next-line react-hooks/exhaustive-deps -- we don't want to recalculate when selectedOptions change - }, [props.personalDetails, props.policyMembers, props.betas, searchTerm, excludedUsers]); + }, [personalDetailsProp, policyMembers, betas, searchTerm, excludedUsers]); const sections = useMemo(() => { const sectionsArr = []; @@ -171,13 +170,13 @@ function WorkspaceInvitePage(props) { // Filter all options that is a part of the search term or in the personal details let filterSelectedOptions = selectedOptions; if (searchTerm !== '') { - filterSelectedOptions = _.filter(selectedOptions, (option) => { - const accountID = lodashGet(option, 'accountID', null); - const isOptionInPersonalDetails = _.some(personalDetails, (personalDetail) => personalDetail.accountID === accountID); + filterSelectedOptions = selectedOptions.filter((option) => { + const accountID = option.accountID; + const isOptionInPersonalDetails = Object.values(personalDetails).some((personalDetail) => personalDetail.accountID === accountID); const parsedPhoneNumber = parsePhoneNumber(LoginUtils.appendCountryCode(Str.removeSMSDomain(searchTerm))); - const searchValue = parsedPhoneNumber.possible ? parsedPhoneNumber.number.e164 : searchTerm.toLowerCase(); + const searchValue = parsedPhoneNumber.possible ? parsedPhoneNumber.number?.e164 ?? '' : searchTerm.toLowerCase(); - const isPartOfSearchTerm = option.text.toLowerCase().includes(searchValue) || option.login.toLowerCase().includes(searchValue); + const isPartOfSearchTerm = !!option.text?.toLowerCase().includes(searchValue) || !!option.login?.toLowerCase().includes(searchValue); return isPartOfSearchTerm || isOptionInPersonalDetails; }); } @@ -191,20 +190,20 @@ function WorkspaceInvitePage(props) { indexOffset += filterSelectedOptions.length; // Filtering out selected users from the search results - const selectedLogins = _.map(selectedOptions, ({login}) => login); - const personalDetailsWithoutSelected = _.filter(personalDetails, ({login}) => !_.contains(selectedLogins, login)); - const personalDetailsFormatted = _.map(personalDetailsWithoutSelected, OptionsListUtils.formatMemberForList); + const selectedLogins = selectedOptions.map(({login}) => login); + const personalDetailsWithoutSelected = Object.values(personalDetails).filter(({login}) => !selectedLogins.some((selectedLogin) => selectedLogin === login)); + const personalDetailsFormatted = personalDetailsWithoutSelected.map((item) => OptionsListUtils.formatMemberForList(item)); sectionsArr.push({ title: translate('common.contacts'), data: personalDetailsFormatted, - shouldShow: !_.isEmpty(personalDetailsFormatted), + shouldShow: !isEmptyObject(personalDetailsFormatted), indexOffset, }); indexOffset += personalDetailsFormatted.length; - _.each(usersToInvite, (userToInvite) => { - const hasUnselectedUserToInvite = !_.contains(selectedLogins, userToInvite.login); + Object.values(usersToInvite).forEach((userToInvite) => { + const hasUnselectedUserToInvite = !selectedLogins.some((selectedLogin) => selectedLogin === userToInvite.login); if (hasUnselectedUserToInvite) { sectionsArr.push({ @@ -219,14 +218,14 @@ function WorkspaceInvitePage(props) { return sectionsArr; }, [personalDetails, searchTerm, selectedOptions, usersToInvite, translate, didScreenTransitionEnd]); - const toggleOption = (option) => { - Policy.clearErrors(props.route.params.policyID); + const toggleOption = (option: OptionData) => { + Policy.clearErrors(route.params.policyID); - const isOptionInList = _.some(selectedOptions, (selectedOption) => selectedOption.login === option.login); + const isOptionInList = selectedOptions.some((selectedOption) => selectedOption.login === option.login); - let newSelectedOptions; + let newSelectedOptions: SelectedOption[]; if (isOptionInList) { - newSelectedOptions = _.reject(selectedOptions, (selectedOption) => selectedOption.login === option.login); + newSelectedOptions = selectedOptions.filter((selectedOption) => selectedOption.login !== option.login); } else { newSelectedOptions = [...selectedOptions, {...option, isSelected: true}]; } @@ -234,14 +233,14 @@ function WorkspaceInvitePage(props) { setSelectedOptions(newSelectedOptions); }; - const validate = () => { - const errors = {}; + const validate = (): boolean => { + const errors: Errors = {}; if (selectedOptions.length <= 0) { - errors.noUserSelected = true; + errors.noUserSelected = 'true'; } - Policy.setWorkspaceErrors(props.route.params.policyID, errors); - return _.size(errors) <= 0; + Policy.setWorkspaceErrors(route.params.policyID, errors); + return isEmptyObject(errors); }; const inviteUser = () => { @@ -249,27 +248,24 @@ function WorkspaceInvitePage(props) { return; } - const invitedEmailsToAccountIDs = {}; - _.each(selectedOptions, (option) => { - const login = option.login || ''; - const accountID = lodashGet(option, 'accountID', ''); + const invitedEmailsToAccountIDs: Record = {}; + selectedOptions.forEach((option) => { + const login = option.login ?? ''; + const accountID = option.accountID ?? ''; if (!login.toLowerCase().trim() || !accountID) { return; } invitedEmailsToAccountIDs[login] = Number(accountID); }); - Policy.setWorkspaceInviteMembersDraft(props.route.params.policyID, invitedEmailsToAccountIDs); - Navigation.navigate(ROUTES.WORKSPACE_INVITE_MESSAGE.getRoute(props.route.params.policyID)); + Policy.setWorkspaceInviteMembersDraft(route.params.policyID, invitedEmailsToAccountIDs); + Navigation.navigate(ROUTES.WORKSPACE_INVITE_MESSAGE.getRoute(route.params.policyID)); }; - const [policyName, shouldShowAlertPrompt] = useMemo( - () => [lodashGet(props.policy, 'name'), _.size(lodashGet(props.policy, 'errors', {})) > 0 || lodashGet(props.policy, 'alertMessage', '').length > 0], - [props.policy], - ); + const [policyName, shouldShowAlertPrompt] = useMemo(() => [policy?.name ?? '', !isEmptyObject(policy?.errors) || !!policy?.alertMessage], [policy]); const headerMessage = useMemo(() => { const searchValue = searchTerm.trim().toLowerCase(); - if (usersToInvite.length === 0 && CONST.EXPENSIFY_EMAILS.includes(searchValue)) { + if (usersToInvite.length === 0 && CONST.EXPENSIFY_EMAILS.some((email) => email === searchValue)) { return translate('messages.errorMessageInvalidEmail'); } if ( @@ -289,8 +285,8 @@ function WorkspaceInvitePage(props) { testID={WorkspaceInvitePage.displayName} > Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} > { - Policy.clearErrors(props.route.params.policyID); - Navigation.goBack(ROUTES.WORKSPACE_MEMBERS.getRoute(props.route.params.policyID)); + Policy.clearErrors(route.params.policyID); + Navigation.goBack(ROUTES.WORKSPACE_MEMBERS.getRoute(route.params.policyID)); }} /> @@ -325,7 +321,7 @@ function WorkspaceInvitePage(props) { isAlertVisible={shouldShowAlertPrompt} buttonText={translate('common.next')} onSubmit={inviteUser} - message={props.policy.alertMessage} + message={policy?.alertMessage} containerStyles={[styles.flexReset, styles.flexGrow0, styles.flexShrink0, styles.flexBasisAuto, styles.mb5]} enabledWhenOffline disablePressOnEnter @@ -336,24 +332,18 @@ function WorkspaceInvitePage(props) { ); } -WorkspaceInvitePage.propTypes = propTypes; -WorkspaceInvitePage.defaultProps = defaultProps; WorkspaceInvitePage.displayName = 'WorkspaceInvitePage'; -export default compose( - withPolicyAndFullscreenLoading, - withOnyx({ +export default withPolicyAndFullscreenLoading( + withOnyx({ personalDetails: { key: ONYXKEYS.PERSONAL_DETAILS_LIST, }, betas: { key: ONYXKEYS.BETAS, }, - isLoadingReportData: { - key: ONYXKEYS.IS_LOADING_REPORT_DATA, - }, invitedEmailsToAccountIDsDraft: { key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${route.params.policyID.toString()}`, }, - }), -)(WorkspaceInvitePage); + })(WorkspaceInvitePage), +); diff --git a/src/types/onyx/InvitedEmailsToAccountIDsDraft.ts b/src/types/onyx/InvitedEmailsToAccountIDsDraft.ts new file mode 100644 index 000000000000..d29282b0aee9 --- /dev/null +++ b/src/types/onyx/InvitedEmailsToAccountIDsDraft.ts @@ -0,0 +1,3 @@ +type InvitedEmailsToAccountIDsDraft = Record; + +export default InvitedEmailsToAccountIDsDraft; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 5b04cae58671..a222efbbeed3 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -15,6 +15,7 @@ import type FrequentlyUsedEmoji from './FrequentlyUsedEmoji'; import type {FundList} from './Fund'; import type Fund from './Fund'; import type IntroSelected from './IntroSelected'; +import type InvitedEmailsToAccountIDsDraft from './InvitedEmailsToAccountIDsDraft'; import type IOU from './IOU'; import type Locale from './Locale'; import type {LoginList} from './Login'; @@ -150,5 +151,6 @@ export type { NewRoomForm, IKnowATeacherForm, IntroSchoolPrincipalForm, + InvitedEmailsToAccountIDsDraft, PrivateNotesForm, }; From 4f70618c0b5010fbf887118981cf2c10b7dca2ef Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 29 Jan 2024 17:20:21 +0100 Subject: [PATCH 3/9] TypeScript fixes --- src/components/SelectionList/BaseListItem.tsx | 2 +- .../SelectionList/BaseSelectionList.tsx | 6 ++--- src/components/SelectionList/types.ts | 25 +++++++++++-------- src/libs/OptionsListUtils.ts | 7 ++++-- src/pages/workspace/WorkspaceInvitePage.tsx | 10 +++++--- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/components/SelectionList/BaseListItem.tsx b/src/components/SelectionList/BaseListItem.tsx index 71845931ba52..37e5ff0bff1c 100644 --- a/src/components/SelectionList/BaseListItem.tsx +++ b/src/components/SelectionList/BaseListItem.tsx @@ -55,7 +55,7 @@ function BaseListItem({ onSelectRow(item)} disabled={isDisabled} - accessibilityLabel={item.text} + accessibilityLabel={item.text ?? ''} role={CONST.ROLE.BUTTON} hoverDimmingValue={1} hoverStyle={styles.hoveredComponentBG} diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 1d73873d836b..07125a0fb005 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -300,14 +300,14 @@ function BaseSelectionList( selectRow(item, true)} onDismissError={onDismissError} shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} rightHandSideComponent={rightHandSideComponent} - keyForList={item.keyForList} + keyForList={item.keyForList ?? undefined} /> ); }; @@ -471,7 +471,7 @@ function BaseSelectionList( getItemLayout={getItemLayout} onScroll={onScroll} onScrollBeginDrag={onScrollBeginDrag} - keyExtractor={(item) => item.keyForList} + keyExtractor={(item) => item.keyForList ?? ''} extraData={focusedIndex} indicatorStyle="white" keyboardShouldPersistTaps="always" diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index a82ddef6febb..3e1d74a2e05d 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -34,28 +34,28 @@ type CommonListItemProps = { type User = { /** Text to display */ - text: string; + text?: string; /** Alternate text to display */ - alternateText?: string; + alternateText?: string | null; /** Key used internally by React */ - keyForList: string; + keyForList?: string | null; /** Whether this option is selected */ isSelected?: boolean; /** Whether this option is disabled for selection */ - isDisabled?: boolean; + isDisabled?: boolean | null; /** User accountID */ - accountID?: number; + accountID?: number | null; /** User login */ - login?: string; + login?: string | null; /** Element to show on the right side of the item */ - rightElement?: ReactElement; + rightElement?: ReactElement | null; /** Icons for the user (can be multiple if it's a Workspace) */ icons?: Icon[]; @@ -85,19 +85,19 @@ type UserListItemProps = CommonListItemProps & { type RadioItem = { /** Text to display */ - text: string; + text?: string; /** Alternate text to display */ - alternateText?: string; + alternateText?: string | null; /** Key used internally by React */ - keyForList: string; + keyForList?: string | null; /** Whether this option is selected */ isSelected?: boolean; /** Whether this option is disabled for selection */ - isDisabled?: boolean; + isDisabled?: boolean | null; /** Represents the index of the section it came from */ sectionIndex?: number; @@ -129,6 +129,9 @@ type Section = { /** Whether this section items disabled for selection */ isDisabled?: boolean; + + /** Whether this section should be shown or not */ + shouldShow?: boolean; }; type BaseSelectionListProps = Partial & { diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 93664f1dbc21..67437957999d 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -5,6 +5,7 @@ import lodashGet from 'lodash/get'; import lodashOrderBy from 'lodash/orderBy'; import lodashSet from 'lodash/set'; import lodashSortBy from 'lodash/sortBy'; +import type {ReactElement} from 'react'; import Onyx from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import CONST from '@src/CONST'; @@ -109,7 +110,7 @@ type MemberForList = { isDisabled: boolean | null; accountID?: number | null; login: string | null; - rightElement: React.ReactNode | null; + rightElement: ReactElement | null; icons?: OnyxCommon.Icon[]; pendingAction?: OnyxCommon.PendingAction; }; @@ -1810,7 +1811,9 @@ function getShareDestinationOptions( * @param member - personalDetails or userToInvite * @param config - keys to overwrite the default values */ -function formatMemberForList(member: ReportUtils.OptionData, config: ReportUtils.OptionData | EmptyObject = {}): MemberForList | undefined { +function formatMemberForList(member: ReportUtils.OptionData, config?: ReportUtils.OptionData | EmptyObject): MemberForList; +function formatMemberForList(member: null | undefined, config?: ReportUtils.OptionData | EmptyObject): undefined; +function formatMemberForList(member: ReportUtils.OptionData | null | undefined, config: ReportUtils.OptionData | EmptyObject = {}): MemberForList | undefined { if (!member) { return undefined; } diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index 172dd12314fa..3a8023b6256f 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -2,6 +2,7 @@ import {useNavigation} from '@react-navigation/native'; import type {StackNavigationProp, StackScreenProps} from '@react-navigation/stack'; import Str from 'expensify-common/lib/str'; import React, {useEffect, useMemo, useState} from 'react'; +import type {SectionListData} from 'react-native'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; @@ -10,6 +11,7 @@ import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; +import type {Section} from '@components/SelectionList/types'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -17,7 +19,7 @@ import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as LoginUtils from '@libs/LoginUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; -import {MemberForList} from '@libs/OptionsListUtils'; +import type {MemberForList} from '@libs/OptionsListUtils'; import {parsePhoneNumber} from '@libs/PhoneNumber'; import * as PolicyUtils from '@libs/PolicyUtils'; import type {OptionData} from '@libs/ReportUtils'; @@ -159,8 +161,8 @@ function WorkspaceInvitePage({ // eslint-disable-next-line react-hooks/exhaustive-deps -- we don't want to recalculate when selectedOptions change }, [personalDetailsProp, policyMembers, betas, searchTerm, excludedUsers]); - const sections = useMemo(() => { - const sectionsArr = []; + const sections: Array>> = useMemo(() => { + const sectionsArr: Array>> = []; let indexOffset = 0; if (!didScreenTransitionEnd) { @@ -218,7 +220,7 @@ function WorkspaceInvitePage({ return sectionsArr; }, [personalDetails, searchTerm, selectedOptions, usersToInvite, translate, didScreenTransitionEnd]); - const toggleOption = (option: OptionData) => { + const toggleOption = (option: SelectedOption) => { Policy.clearErrors(route.params.policyID); const isOptionInList = selectedOptions.some((selectedOption) => selectedOption.login === option.login); From 2fe100c6b2c483332d96b854243d09f87ec391d1 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 30 Jan 2024 10:41:47 +0100 Subject: [PATCH 4/9] Type improvements --- src/components/SelectionList/BaseListItem.tsx | 2 +- .../SelectionList/BaseSelectionList.tsx | 6 +++--- src/components/SelectionList/types.ts | 20 +++++++++---------- src/libs/OptionsListUtils.ts | 20 +++++++++---------- src/pages/RoomInvitePage.js | 6 +++--- src/pages/workspace/WorkspaceInvitePage.tsx | 18 ++++++++--------- 6 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/components/SelectionList/BaseListItem.tsx b/src/components/SelectionList/BaseListItem.tsx index 37e5ff0bff1c..71845931ba52 100644 --- a/src/components/SelectionList/BaseListItem.tsx +++ b/src/components/SelectionList/BaseListItem.tsx @@ -55,7 +55,7 @@ function BaseListItem({ onSelectRow(item)} disabled={isDisabled} - accessibilityLabel={item.text ?? ''} + accessibilityLabel={item.text} role={CONST.ROLE.BUTTON} hoverDimmingValue={1} hoverStyle={styles.hoveredComponentBG} diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 07125a0fb005..1d73873d836b 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -300,14 +300,14 @@ function BaseSelectionList( selectRow(item, true)} onDismissError={onDismissError} shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} rightHandSideComponent={rightHandSideComponent} - keyForList={item.keyForList ?? undefined} + keyForList={item.keyForList} /> ); }; @@ -471,7 +471,7 @@ function BaseSelectionList( getItemLayout={getItemLayout} onScroll={onScroll} onScrollBeginDrag={onScrollBeginDrag} - keyExtractor={(item) => item.keyForList ?? ''} + keyExtractor={(item) => item.keyForList} extraData={focusedIndex} indicatorStyle="white" keyboardShouldPersistTaps="always" diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 3e1d74a2e05d..e34f0f28be42 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -34,25 +34,25 @@ type CommonListItemProps = { type User = { /** Text to display */ - text?: string; + text: string; /** Alternate text to display */ - alternateText?: string | null; + alternateText?: string; /** Key used internally by React */ - keyForList?: string | null; + keyForList: string; /** Whether this option is selected */ isSelected?: boolean; /** Whether this option is disabled for selection */ - isDisabled?: boolean | null; + isDisabled?: boolean; /** User accountID */ - accountID?: number | null; + accountID?: number; /** User login */ - login?: string | null; + login?: string; /** Element to show on the right side of the item */ rightElement?: ReactElement | null; @@ -85,19 +85,19 @@ type UserListItemProps = CommonListItemProps & { type RadioItem = { /** Text to display */ - text?: string; + text: string; /** Alternate text to display */ - alternateText?: string | null; + alternateText?: string; /** Key used internally by React */ - keyForList?: string | null; + keyForList: string; /** Whether this option is selected */ isSelected?: boolean; /** Whether this option is disabled for selection */ - isDisabled?: boolean | null; + isDisabled?: boolean; /** Represents the index of the section it came from */ sectionIndex?: number; diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 67437957999d..3f262f360c18 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -16,7 +16,6 @@ import type {Participant} from '@src/types/onyx/IOU'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import type {PolicyTaxRate, PolicyTaxRates} from '@src/types/onyx/PolicyTaxRates'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; -import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import times from '@src/utils/times'; import Timing from './actions/Timing'; @@ -104,12 +103,12 @@ type GetOptionsConfig = { type MemberForList = { text: string; - alternateText: string | null; - keyForList: string | null; + alternateText: string; + keyForList: string; isSelected: boolean; - isDisabled: boolean | null; - accountID?: number | null; - login: string | null; + isDisabled: boolean; + accountID?: number; + login: string; rightElement: ReactElement | null; icons?: OnyxCommon.Icon[]; pendingAction?: OnyxCommon.PendingAction; @@ -1811,14 +1810,14 @@ function getShareDestinationOptions( * @param member - personalDetails or userToInvite * @param config - keys to overwrite the default values */ -function formatMemberForList(member: ReportUtils.OptionData, config?: ReportUtils.OptionData | EmptyObject): MemberForList; -function formatMemberForList(member: null | undefined, config?: ReportUtils.OptionData | EmptyObject): undefined; -function formatMemberForList(member: ReportUtils.OptionData | null | undefined, config: ReportUtils.OptionData | EmptyObject = {}): MemberForList | undefined { +function formatMemberForList(member: ReportUtils.OptionData): MemberForList; +function formatMemberForList(member: null | undefined): undefined; +function formatMemberForList(member: ReportUtils.OptionData | null | undefined): MemberForList | undefined { if (!member) { return undefined; } - const accountID = member.accountID; + const accountID = member.accountID ?? undefined; return { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing @@ -1834,7 +1833,6 @@ function formatMemberForList(member: ReportUtils.OptionData | null | undefined, rightElement: null, icons: member.icons, pendingAction: member.pendingAction, - ...config, }; } diff --git a/src/pages/RoomInvitePage.js b/src/pages/RoomInvitePage.js index 588a90e98649..40c9559c9619 100644 --- a/src/pages/RoomInvitePage.js +++ b/src/pages/RoomInvitePage.js @@ -89,7 +89,7 @@ function RoomInvitePage(props) { // Update selectedOptions with the latest personalDetails information const detailsMap = {}; - _.forEach(inviteOptions.personalDetails, (detail) => (detailsMap[detail.login] = OptionsListUtils.formatMemberForList(detail, false))); + _.forEach(inviteOptions.personalDetails, (detail) => (detailsMap[detail.login] = OptionsListUtils.formatMemberForList(detail))); const newSelectedOptions = []; _.forEach(selectedOptions, (option) => { newSelectedOptions.push(_.has(detailsMap, option.login) ? {...detailsMap[option.login], isSelected: true} : option); @@ -145,7 +145,7 @@ function RoomInvitePage(props) { // Filtering out selected users from the search results const selectedLogins = _.map(selectedOptions, ({login}) => login); const personalDetailsWithoutSelected = _.filter(personalDetails, ({login}) => !_.contains(selectedLogins, login)); - const personalDetailsFormatted = _.map(personalDetailsWithoutSelected, (personalDetail) => OptionsListUtils.formatMemberForList(personalDetail, false)); + const personalDetailsFormatted = _.map(personalDetailsWithoutSelected, (personalDetail) => OptionsListUtils.formatMemberForList(personalDetail)); const hasUnselectedUserToInvite = userToInvite && !_.contains(selectedLogins, userToInvite.login); sectionsArr.push({ @@ -159,7 +159,7 @@ function RoomInvitePage(props) { if (hasUnselectedUserToInvite) { sectionsArr.push({ title: undefined, - data: [OptionsListUtils.formatMemberForList(userToInvite, false)], + data: [OptionsListUtils.formatMemberForList(userToInvite)], shouldShow: true, indexOffset, }); diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index 3a8023b6256f..541b8413b0ec 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -36,7 +36,7 @@ import SearchInputManager from './SearchInputManager'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading'; -type SelectedOption = Partial; +type MembersSection = SectionListData>; type WorkspaceInvitePageOnyxProps = { /** All of the personal details for everyone */ @@ -63,7 +63,7 @@ function WorkspaceInvitePage({ const styles = useThemeStyles(); const {translate} = useLocalize(); const [searchTerm, setSearchTerm] = useState(''); - const [selectedOptions, setSelectedOptions] = useState([]); + const [selectedOptions, setSelectedOptions] = useState([]); const [personalDetails, setPersonalDetails] = useState([]); const [usersToInvite, setUsersToInvite] = useState([]); const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); @@ -105,12 +105,12 @@ function WorkspaceInvitePage({ useEffect(() => { const newUsersToInviteDict: Record = {}; const newPersonalDetailsDict: Record = {}; - const newSelectedOptionsDict: Record = {}; + const newSelectedOptionsDict: Record = {}; const inviteOptions = OptionsListUtils.getMemberInviteOptions(personalDetailsProp, betas ?? [], searchTerm, excludedUsers, true); // Update selectedOptions with the latest personalDetails and policyMembers information - const detailsMap: Record = {}; + const detailsMap: Record = {}; inviteOptions.personalDetails.forEach((detail) => { if (!detail.login) { return; @@ -119,7 +119,7 @@ function WorkspaceInvitePage({ detailsMap[detail.login] = OptionsListUtils.formatMemberForList(detail); }); - const newSelectedOptions: SelectedOption[] = []; + const newSelectedOptions: MemberForList[] = []; Object.keys(invitedEmailsToAccountIDsDraft ?? {}).forEach((login) => { if (!(login in detailsMap)) { return; @@ -161,8 +161,8 @@ function WorkspaceInvitePage({ // eslint-disable-next-line react-hooks/exhaustive-deps -- we don't want to recalculate when selectedOptions change }, [personalDetailsProp, policyMembers, betas, searchTerm, excludedUsers]); - const sections: Array>> = useMemo(() => { - const sectionsArr: Array>> = []; + const sections: MembersSection[] = useMemo(() => { + const sectionsArr: MembersSection[] = []; let indexOffset = 0; if (!didScreenTransitionEnd) { @@ -220,12 +220,12 @@ function WorkspaceInvitePage({ return sectionsArr; }, [personalDetails, searchTerm, selectedOptions, usersToInvite, translate, didScreenTransitionEnd]); - const toggleOption = (option: SelectedOption) => { + const toggleOption = (option: MemberForList) => { Policy.clearErrors(route.params.policyID); const isOptionInList = selectedOptions.some((selectedOption) => selectedOption.login === option.login); - let newSelectedOptions: SelectedOption[]; + let newSelectedOptions: MemberForList[]; if (isOptionInList) { newSelectedOptions = selectedOptions.filter((selectedOption) => selectedOption.login !== option.login); } else { From f8896692f83bf0393714d5e5400b9395b2a0f684 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 30 Jan 2024 10:57:16 +0100 Subject: [PATCH 5/9] Update formatMemberForList typing --- src/libs/OptionsListUtils.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 3f262f360c18..73c867381609 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1810,9 +1810,9 @@ function getShareDestinationOptions( * @param member - personalDetails or userToInvite * @param config - keys to overwrite the default values */ -function formatMemberForList(member: ReportUtils.OptionData): MemberForList; -function formatMemberForList(member: null | undefined): undefined; -function formatMemberForList(member: ReportUtils.OptionData | null | undefined): MemberForList | undefined { +function formatMemberForList(member: ReportUtils.OptionData, config?: Partial): MemberForList; +function formatMemberForList(member: null | undefined, config?: Partial): undefined; +function formatMemberForList(member: ReportUtils.OptionData | null | undefined, config: Partial = {}): MemberForList | undefined { if (!member) { return undefined; } @@ -1833,6 +1833,7 @@ function formatMemberForList(member: ReportUtils.OptionData | null | undefined): rightElement: null, icons: member.icons, pendingAction: member.pendingAction, + ...config, }; } From 191d707a92a01ab0f83fad2f7f1b848c8aa35f3d Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 31 Jan 2024 15:09:53 +0100 Subject: [PATCH 6/9] Use right personalDetails prop --- src/pages/workspace/WorkspaceInvitePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index 541b8413b0ec..1fcb95def4cb 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -314,7 +314,7 @@ function WorkspaceInvitePage({ onSelectRow={toggleOption} onConfirm={inviteUser} showScrollIndicator - showLoadingPlaceholder={!didScreenTransitionEnd || !OptionsListUtils.isPersonalDetailsReady(personalDetails)} + showLoadingPlaceholder={!didScreenTransitionEnd || !OptionsListUtils.isPersonalDetailsReady(personalDetailsProp)} shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} /> From fc1aad500c54268f0ec8bf83fe337ecd9c6f54ef Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 31 Jan 2024 16:54:36 +0100 Subject: [PATCH 7/9] Remove unnecessary updates, code improvements --- src/ONYXKEYS.ts | 2 +- src/libs/OptionsListUtils.ts | 7 ++++--- src/libs/actions/Policy.ts | 20 +++++++++++++++---- .../workspace/WorkspaceInviteMessagePage.tsx | 4 ++-- src/pages/workspace/WorkspaceInvitePage.tsx | 6 +++--- src/types/onyx/InvitedEmailsToAccountIDs.ts | 3 +++ .../onyx/InvitedEmailsToAccountIDsDraft.ts | 3 --- src/types/onyx/index.ts | 4 ++-- 8 files changed, 31 insertions(+), 18 deletions(-) create mode 100644 src/types/onyx/InvitedEmailsToAccountIDs.ts delete mode 100644 src/types/onyx/InvitedEmailsToAccountIDsDraft.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 7f2867aed87b..eebdc743337a 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -459,7 +459,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS]: OnyxTypes.PolicyReportFields; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_REPORT_FIELDS]: OnyxTypes.RecentlyUsedReportFields; [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMembers; - [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: OnyxTypes.InvitedEmailsToAccountIDsDraft | undefined; + [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: OnyxTypes.InvitedEmailsToAccountIDs | undefined; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT]: string | undefined; [ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report; [ONYXKEYS.COLLECTION.REPORT_METADATA]: OnyxTypes.ReportMetadata; diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 62a8c17011ac..c801752b89aa 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -310,9 +310,9 @@ function getPersonalDetailsForAccountIDs(accountIDs: number[] | undefined, perso /** * Return true if personal details data is ready, i.e. report list options can be created. */ -function isPersonalDetailsReady(personalDetails: OnyxEntry | ReportUtils.OptionData[]): boolean { - const personalDetailsValues = Array.isArray(personalDetails) ? personalDetails : Object.values(personalDetails ?? {}); - return personalDetailsValues.some((personalDetail) => personalDetail?.accountID); +function isPersonalDetailsReady(personalDetails: OnyxEntry): boolean { + const personalDetailsKeys = Object.keys(personalDetails ?? {}); + return personalDetailsKeys.some((key) => personalDetails?.[key]?.accountID); } /** @@ -2005,4 +2005,5 @@ export { formatSectionsFromSearchTerm, transformedTaxRates, }; + export type {MemberForList}; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index fbe92aeb378d..97f4e402058d 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -35,7 +35,19 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PersonalDetailsList, Policy, PolicyMember, PolicyTags, RecentlyUsedCategories, RecentlyUsedTags, ReimbursementAccount, Report, ReportAction, Transaction} from '@src/types/onyx'; +import type { + InvitedEmailsToAccountIDs, + PersonalDetailsList, + Policy, + PolicyMember, + PolicyTags, + RecentlyUsedCategories, + RecentlyUsedTags, + ReimbursementAccount, + Report, + ReportAction, + Transaction, +} from '@src/types/onyx'; import type {Errors} from '@src/types/onyx/OnyxCommon'; import type {CustomUnit} from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -517,7 +529,7 @@ function removeMembers(accountIDs: number[], policyID: string) { * * @returns - object with onyxSuccessData, onyxOptimisticData, and optimisticReportIDs (map login to reportID) */ -function createPolicyExpenseChats(policyID: string, invitedEmailsToAccountIDs: Record, hasOutstandingChildRequest = false): WorkspaceMembersChats { +function createPolicyExpenseChats(policyID: string, invitedEmailsToAccountIDs: InvitedEmailsToAccountIDs, hasOutstandingChildRequest = false): WorkspaceMembersChats { const workspaceMembersChats: WorkspaceMembersChats = { onyxSuccessData: [], onyxOptimisticData: [], @@ -606,7 +618,7 @@ function createPolicyExpenseChats(policyID: string, invitedEmailsToAccountIDs: R /** * Adds members to the specified workspace/policyID */ -function addMembersToWorkspace(invitedEmailsToAccountIDs: Record, welcomeNote: string, policyID: string) { +function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccountIDs, welcomeNote: string, policyID: string) { const membersListKey = `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}` as const; const logins = Object.keys(invitedEmailsToAccountIDs).map((memberLogin) => OptionsListUtils.addSMSDomainIfPhoneNumber(memberLogin)); const accountIDs = Object.values(invitedEmailsToAccountIDs); @@ -1498,7 +1510,7 @@ function openDraftWorkspaceRequest(policyID: string) { API.read(READ_COMMANDS.OPEN_DRAFT_WORKSPACE_REQUEST, params); } -function setWorkspaceInviteMembersDraft(policyID: string, invitedEmailsToAccountIDs: Record) { +function setWorkspaceInviteMembersDraft(policyID: string, invitedEmailsToAccountIDs: InvitedEmailsToAccountIDs) { Onyx.set(`${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${policyID}`, invitedEmailsToAccountIDs); } diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.tsx b/src/pages/workspace/WorkspaceInviteMessagePage.tsx index bd206811ff5a..bdb0dd840bdc 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessagePage.tsx @@ -29,7 +29,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {InvitedEmailsToAccountIDsDraft, PersonalDetailsList} from '@src/types/onyx'; +import type {InvitedEmailsToAccountIDs, PersonalDetailsList} from '@src/types/onyx'; import type {Errors} from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import SearchInputManager from './SearchInputManager'; @@ -41,7 +41,7 @@ type WorkspaceInviteMessagePageOnyxProps = { allPersonalDetails: OnyxEntry; /** An object containing the accountID for every invited user email */ - invitedEmailsToAccountIDsDraft: OnyxEntry; + invitedEmailsToAccountIDsDraft: OnyxEntry; /** Updated workspace invite message */ workspaceInviteMessageDraft: OnyxEntry; diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index 1fcb95def4cb..488cb946a68a 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -29,7 +29,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {Beta, InvitedEmailsToAccountIDsDraft, PersonalDetailsList} from '@src/types/onyx'; +import type {Beta, InvitedEmailsToAccountIDs, PersonalDetailsList} from '@src/types/onyx'; import type {Errors} from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import SearchInputManager from './SearchInputManager'; @@ -46,7 +46,7 @@ type WorkspaceInvitePageOnyxProps = { betas: OnyxEntry; /** An object containing the accountID for every invited user email */ - invitedEmailsToAccountIDsDraft: OnyxEntry; + invitedEmailsToAccountIDsDraft: OnyxEntry; }; type WorkspaceInvitePageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceInvitePageOnyxProps & StackScreenProps; @@ -250,7 +250,7 @@ function WorkspaceInvitePage({ return; } - const invitedEmailsToAccountIDs: Record = {}; + const invitedEmailsToAccountIDs: InvitedEmailsToAccountIDs = {}; selectedOptions.forEach((option) => { const login = option.login ?? ''; const accountID = option.accountID ?? ''; diff --git a/src/types/onyx/InvitedEmailsToAccountIDs.ts b/src/types/onyx/InvitedEmailsToAccountIDs.ts new file mode 100644 index 000000000000..929d21746682 --- /dev/null +++ b/src/types/onyx/InvitedEmailsToAccountIDs.ts @@ -0,0 +1,3 @@ +type InvitedEmailsToAccountIDs = Record; + +export default InvitedEmailsToAccountIDs; diff --git a/src/types/onyx/InvitedEmailsToAccountIDsDraft.ts b/src/types/onyx/InvitedEmailsToAccountIDsDraft.ts deleted file mode 100644 index d29282b0aee9..000000000000 --- a/src/types/onyx/InvitedEmailsToAccountIDsDraft.ts +++ /dev/null @@ -1,3 +0,0 @@ -type InvitedEmailsToAccountIDsDraft = Record; - -export default InvitedEmailsToAccountIDsDraft; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index a222efbbeed3..285da6062ac8 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -15,7 +15,7 @@ import type FrequentlyUsedEmoji from './FrequentlyUsedEmoji'; import type {FundList} from './Fund'; import type Fund from './Fund'; import type IntroSelected from './IntroSelected'; -import type InvitedEmailsToAccountIDsDraft from './InvitedEmailsToAccountIDsDraft'; +import type InvitedEmailsToAccountIDs from './InvitedEmailsToAccountIDs'; import type IOU from './IOU'; import type Locale from './Locale'; import type {LoginList} from './Login'; @@ -151,6 +151,6 @@ export type { NewRoomForm, IKnowATeacherForm, IntroSchoolPrincipalForm, - InvitedEmailsToAccountIDsDraft, + InvitedEmailsToAccountIDs, PrivateNotesForm, }; From accc5e8a81a9c73f55ec35d9242b5b72d1dec8e2 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 6 Feb 2024 12:59:01 +0100 Subject: [PATCH 8/9] Update type to use ReactNode --- src/components/SelectionList/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index e34f0f28be42..d423345fc8ac 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -55,7 +55,7 @@ type User = { login?: string; /** Element to show on the right side of the item */ - rightElement?: ReactElement | null; + rightElement?: ReactNode; /** Icons for the user (can be multiple if it's a Workspace) */ icons?: Icon[]; From 9f449313cb2e1c173cf799f27293e8d2f5d3035a Mon Sep 17 00:00:00 2001 From: VickyStash Date: Fri, 9 Feb 2024 10:29:54 +0100 Subject: [PATCH 9/9] TS fix --- src/pages/workspace/WorkspaceInvitePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index f09572ea53a1..ae139752a052 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -323,7 +323,7 @@ function WorkspaceInvitePage({ isAlertVisible={shouldShowAlertPrompt} buttonText={translate('common.next')} onSubmit={inviteUser} - message={[policy?.alertMessage, {isTranslated: true}]} + message={[policy?.alertMessage ?? '', {isTranslated: true}]} containerStyles={[styles.flexReset, styles.flexGrow0, styles.flexShrink0, styles.flexBasisAuto, styles.mb5]} enabledWhenOffline disablePressOnEnter