Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TS migration] Migrate 'ReportParticipants' page to TypeScript #34882

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/components/OptionsList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,13 @@ type OptionsListProps = {
renderFooterContent?: () => JSX.Element;

/** Whether to show a button pill instead of a standard tickbox */
shouldShowMultipleOptionSelectorAsButton: boolean;
shouldShowMultipleOptionSelectorAsButton?: boolean;

/** Text for button pill */
multipleOptionSelectorButtonText: string;
multipleOptionSelectorButtonText?: string;

/** Callback to fire when the multiple selector (tickbox or button) is clicked */
onAddToSelection: () => void;
onAddToSelection?: () => void;

/** Safe area style */
safeAreaPaddingBottomStyle?: StyleProp<ViewStyle>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,75 +1,61 @@
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import OptionsList from '@components/OptionsList';
import ScreenWrapper from '@components/ScreenWrapper';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
import * as LocalePhoneNumber from '@libs/LocalePhoneNumber';
import type * as Localize from '@libs/Localize';
import Navigation from '@libs/Navigation/Navigation';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import type {OptionData} from '@libs/ReportUtils';
import * as UserUtils from '@libs/UserUtils';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {PersonalDetailsList, Report} from '@src/types/onyx';
import type {WithReportOrNotFoundProps} from './home/report/withReportOrNotFound';
import withReportOrNotFound from './home/report/withReportOrNotFound';
import personalDetailsPropType from './personalDetailsPropType';
import reportPropTypes from './reportPropTypes';

const propTypes = {
/* Onyx Props */

/** The personal details of the person who is logged in */
personalDetails: PropTypes.objectOf(personalDetailsPropType),

/** The active report */
report: reportPropTypes.isRequired,

/** Route params */
route: PropTypes.shape({
params: PropTypes.shape({
/** Report ID passed via route r/:reportID/participants */
reportID: PropTypes.string,
}),
}).isRequired,

...withLocalizePropTypes,
type ReportParticipantsPageOnyxProps = {
/** Personal details of all the users */
personalDetails: OnyxEntry<PersonalDetailsList>;
};

const defaultProps = {
personalDetails: {},
};
type ReportParticipantsPageProps = ReportParticipantsPageOnyxProps & WithReportOrNotFoundProps;

/**
* Returns all the participants in the active report
*
* @param {Object} report The active report object
* @param {Object} personalDetails The personal details of the users
* @param {Object} translate The localize
* @return {Array}
* @param report The active report object
* @param personalDetails The personal details of the users
* @param translate The localize
*/
const getAllParticipants = (report, personalDetails, translate) =>
_.chain(ReportUtils.getVisibleMemberIDs(report))
const getAllParticipants = (
report: OnyxEntry<Report>,
personalDetails: OnyxEntry<PersonalDetailsList>,
translate: <TKey extends TranslationPaths>(phraseKey: TKey, ...phraseParameters: Localize.PhraseParameters<Localize.Phrase<TKey>>) => string,
): OptionData[] =>
ReportUtils.getVisibleMemberIDs(report)
.map((accountID, index) => {
const userPersonalDetail = lodashGet(personalDetails, accountID, {displayName: personalDetails.displayName || translate('common.hidden'), avatar: ''});
const userLogin = LocalePhoneNumber.formatPhoneNumber(userPersonalDetail.login || '') || translate('common.hidden');
const userPersonalDetail = personalDetails?.[accountID];
const userLogin = LocalePhoneNumber.formatPhoneNumber(userPersonalDetail?.login ?? '') ?? translate('common.hidden');
JKobrynski marked this conversation as resolved.
Show resolved Hide resolved
const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(userPersonalDetail);

return {
alternateText: userLogin,
displayName,
accountID: userPersonalDetail.accountID,
accountID: userPersonalDetail?.accountID ?? accountID,
icons: [
JKobrynski marked this conversation as resolved.
Show resolved Hide resolved
{
id: accountID,
source: UserUtils.getAvatar(userPersonalDetail.avatar, accountID),
source: UserUtils.getAvatar(userPersonalDetail?.avatar ?? '', accountID),
name: userLogin,
type: CONST.ICON_TYPE_AVATAR,
},
Expand All @@ -79,16 +65,18 @@ const getAllParticipants = (report, personalDetails, translate) =>
text: displayName,
tooltipText: userLogin,
participantsList: [{accountID, displayName}],
reportID: report?.reportID ?? '',
};
})
.sortBy((participant) => participant.displayName.toLowerCase())
.value();
.sort((a, b) => a.displayName.localeCompare(b.displayName.toLowerCase()));

function ReportParticipantsPage(props) {
function ReportParticipantsPage({report, personalDetails}: ReportParticipantsPageProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const participants = _.map(getAllParticipants(props.report, props.personalDetails, props.translate), (participant) => ({

const participants = getAllParticipants(report, personalDetails, translate).map((participant) => ({
...participant,
isDisabled: ReportUtils.isOptimisticPersonalDetail(participant.accountID),
isDisabled: participant?.accountID ? ReportUtils.isOptimisticPersonalDetail(participant.accountID) : false,
}));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming from https://github.com/Expensify/App/pull/35385/files#r1472924653:
Was there any reason to return false when participant.accountID is falsy?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


return (
Expand All @@ -97,20 +85,20 @@ function ReportParticipantsPage(props) {
testID={ReportParticipantsPage.displayName}
>
{({safeAreaPaddingBottomStyle}) => (
<FullPageNotFoundView shouldShow={_.isEmpty(props.report) || ReportUtils.isArchivedRoom(props.report)}>
<FullPageNotFoundView shouldShow={!report || ReportUtils.isArchivedRoom(report)}>
<HeaderWithBackButton
title={props.translate(
ReportUtils.isChatRoom(props.report) ||
ReportUtils.isPolicyExpenseChat(props.report) ||
ReportUtils.isChatThread(props.report) ||
ReportUtils.isTaskReport(props.report) ||
ReportUtils.isMoneyRequestReport(props.report)
title={translate(
ReportUtils.isChatRoom(report) ||
ReportUtils.isPolicyExpenseChat(report) ||
ReportUtils.isChatThread(report) ||
ReportUtils.isTaskReport(report) ||
ReportUtils.isMoneyRequestReport(report)
? 'common.members'
: 'common.details',
)}
/>
<View style={[styles.containerWithSpaceBetween, styles.pointerEventsBoxNone]}>
{Boolean(participants.length) && (
{participants?.length && (
<OptionsList
sections={[
{
Expand All @@ -120,7 +108,10 @@ function ReportParticipantsPage(props) {
indexOffset: 0,
},
]}
onSelectRow={(option) => {
onSelectRow={(option: OptionData) => {
if (!option.accountID) {
return;
}
Navigation.navigate(ROUTES.PROFILE.getRoute(option.accountID));
}}
hideSectionHeaders
Expand All @@ -139,16 +130,12 @@ function ReportParticipantsPage(props) {
);
}

ReportParticipantsPage.propTypes = propTypes;
ReportParticipantsPage.defaultProps = defaultProps;
ReportParticipantsPage.displayName = 'ReportParticipantsPage';

export default compose(
withLocalize,
withReportOrNotFound(),
withOnyx({
export default withReportOrNotFound()(
withOnyx<ReportParticipantsPageProps, ReportParticipantsPageOnyxProps>({
personalDetails: {
JKobrynski marked this conversation as resolved.
Show resolved Hide resolved
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
},
}),
)(ReportParticipantsPage);
})(ReportParticipantsPage),
);
9 changes: 5 additions & 4 deletions src/pages/home/report/withReportOrNotFound.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import type * as OnyxTypes from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';

type OnyxProps = {
type WithReportOrNotFoundOnyxProps = {
/** The report currently being looked at */
report: OnyxEntry<OnyxTypes.Report>;
/** The policies which the user has access to */
Expand All @@ -24,15 +24,15 @@ type OnyxProps = {
isLoadingReportData: OnyxEntry<boolean>;
};

type WithReportOrNotFoundProps = OnyxProps & {
type WithReportOrNotFoundProps = WithReportOrNotFoundOnyxProps & {
route: RouteProp<{params: {reportID: string}}>;
};

export default function (
shouldRequireReportID = true,
): <TProps extends WithReportOrNotFoundProps, TRef>(
WrappedComponent: React.ComponentType<TProps & React.RefAttributes<TRef>>,
) => React.ComponentType<Omit<TProps & React.RefAttributes<TRef>, keyof OnyxProps>> {
) => React.ComponentType<Omit<TProps & React.RefAttributes<TRef>, keyof WithReportOrNotFoundOnyxProps>> {
return function <TProps extends WithReportOrNotFoundProps, TRef>(WrappedComponent: ComponentType<TProps & RefAttributes<TRef>>) {
function WithReportOrNotFound(props: TProps, ref: ForwardedRef<TRef>) {
const contentShown = React.useRef(false);
Expand Down Expand Up @@ -87,7 +87,7 @@ export default function (

WithReportOrNotFound.displayName = `withReportOrNotFound(${getComponentDisplayName(WrappedComponent)})`;

return withOnyx<TProps & RefAttributes<TRef>, OnyxProps>({
return withOnyx<TProps & RefAttributes<TRef>, WithReportOrNotFoundOnyxProps>({
report: {
key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`,
},
Expand All @@ -103,4 +103,5 @@ export default function (
})(React.forwardRef(WithReportOrNotFound));
};
}

export type {WithReportOrNotFoundProps};
2 changes: 1 addition & 1 deletion src/types/onyx/PersonalDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type PersonalDetails = {
phoneNumber?: string;

/** Avatar URL of the current user from their personal details */
avatar: AvatarSource;
avatar?: AvatarSource;

/** Avatar thumbnail URL of the current user from their personal details */
avatarThumbnail?: string;
Expand Down
Loading