-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Add new Profile page #20144
Add new Profile page #20144
Changes from 12 commits
e1ab57e
558b23c
540087d
09e6b1f
e138aee
249369e
2d9e221
7f5f165
f5832a3
b1f7118
05d5979
282d9e3
516daeb
b28bf1c
a9631fc
c1d5958
61976f1
8b4f566
afb818c
922a505
c27e85c
d77d9b5
74b5ed2
133cd8e
3ddc601
57beb1d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,6 +55,7 @@ const ReportWelcomeText = (props) => { | |
const isChatRoom = ReportUtils.isChatRoom(props.report); | ||
const isDefault = !(isChatRoom || isPolicyExpenseChat); | ||
const participants = lodashGet(props.report, 'participants', []); | ||
const participantAccountIDs = lodashGet(props.report, 'participantAccountIDs', []); | ||
const isMultipleParticipant = participants.length > 1; | ||
const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForLogins(participants, props.personalDetails), isMultipleParticipant); | ||
const roomWelcomeMessage = ReportUtils.getRoomWelcomeMessage(props.report); | ||
|
@@ -97,7 +98,7 @@ const ReportWelcomeText = (props) => { | |
<Tooltip text={tooltip}> | ||
<Text | ||
style={[styles.textStrong]} | ||
onPress={() => Navigation.navigate(ROUTES.getDetailsRoute(participants[index]))} | ||
onPress={() => Navigation.navigate(ROUTES.getProfileRoute(participantAccountIDs[index]))} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was overlooked. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. #20583 (comment) for more details about the root cause. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh wow, that makes total sense. Should've considered that. Thanks for the feedback! |
||
> | ||
{displayName} | ||
</Text> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
import React from 'react'; | ||
puneetlath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import {View, ScrollView} from 'react-native'; | ||
import PropTypes from 'prop-types'; | ||
import _ from 'underscore'; | ||
import {withOnyx} from 'react-native-onyx'; | ||
import Str from 'expensify-common/lib/str'; | ||
import lodashGet from 'lodash/get'; | ||
import {parsePhoneNumber} from 'awesome-phonenumber'; | ||
import styles from '../styles/styles'; | ||
import Text from '../components/Text'; | ||
import ONYXKEYS from '../ONYXKEYS'; | ||
import Avatar from '../components/Avatar'; | ||
import HeaderWithCloseButton from '../components/HeaderWithCloseButton'; | ||
import Navigation from '../libs/Navigation/Navigation'; | ||
import ScreenWrapper from '../components/ScreenWrapper'; | ||
import personalDetailsPropType from './personalDetailsPropType'; | ||
import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; | ||
import compose from '../libs/compose'; | ||
import CommunicationsLink from '../components/CommunicationsLink'; | ||
import Tooltip from '../components/Tooltip'; | ||
import CONST from '../CONST'; | ||
import * as ReportUtils from '../libs/ReportUtils'; | ||
import * as Expensicons from '../components/Icon/Expensicons'; | ||
import MenuItem from '../components/MenuItem'; | ||
import AttachmentModal from '../components/AttachmentModal'; | ||
import PressableWithoutFocus from '../components/PressableWithoutFocus'; | ||
import * as Report from '../libs/actions/Report'; | ||
import OfflineWithFeedback from '../components/OfflineWithFeedback'; | ||
import AutoUpdateTime from '../components/AutoUpdateTime'; | ||
import * as UserUtils from '../libs/UserUtils'; | ||
import * as PersonalDetails from '../libs/actions/PersonalDetails'; | ||
import FullScreenLoadingIndicator from '../components/FullscreenLoadingIndicator'; | ||
|
||
const matchType = PropTypes.shape({ | ||
params: PropTypes.shape({ | ||
/** accountID passed via route /a/:accountID */ | ||
accountID: PropTypes.string, | ||
|
||
/** report ID passed */ | ||
reportID: PropTypes.string, | ||
puneetlath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}), | ||
}); | ||
|
||
const propTypes = { | ||
/* Onyx Props */ | ||
|
||
/** The personal details of all users */ | ||
personalDetails: personalDetailsPropType, | ||
puneetlath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** Route params */ | ||
route: matchType.isRequired, | ||
|
||
/** Login list for the user that is signed in */ | ||
loginList: PropTypes.shape({ | ||
/** Date login was validated, used to show info indicator status */ | ||
validatedDate: PropTypes.string, | ||
|
||
/** Field-specific server side errors keyed by microtime */ | ||
errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), | ||
puneetlath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}), | ||
|
||
...withLocalizePropTypes, | ||
}; | ||
|
||
const defaultProps = { | ||
// When opening someone else's profile (via deep link) before login, this is empty | ||
personalDetails: {}, | ||
loginList: {}, | ||
}; | ||
|
||
/** | ||
* Gets the phone number to display for SMS logins | ||
* | ||
* @param {Object} details | ||
* @param {String} details.login | ||
* @param {String} details.displayName | ||
* @returns {String} | ||
*/ | ||
const getPhoneNumber = (details) => { | ||
// If the user hasn't set a displayName, it is set to their phone number, so use that | ||
const displayName = lodashGet(details, 'displayName', ''); | ||
const parsedPhoneNumber = parsePhoneNumber(displayName); | ||
if (parsedPhoneNumber.possible) { | ||
return parsedPhoneNumber.number.e164; | ||
} | ||
|
||
// If the user has set a displayName, get the phone number from the SMS login | ||
return details.login ? Str.removeSMSDomain(details.login) : ''; | ||
}; | ||
|
||
class ProfilePage extends React.PureComponent { | ||
puneetlath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
componentDidMount() { | ||
PersonalDetails.openPublicProfilePage(this.props.route.params.accountID); | ||
puneetlath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
render() { | ||
const accountID = lodashGet(this.props.route.params, 'accountID', 0); | ||
const reportID = lodashGet(this.props.route.params, 'reportID', ''); | ||
const details = lodashGet(this.props.personalDetails, accountID, {}); | ||
const displayName = details.displayName ? details.displayName : this.props.translate('common.hidden'); | ||
const avatar = lodashGet(details, 'avatar', UserUtils.getDefaultAvatar()); | ||
const originalFileName = lodashGet(details, 'originalFileName', ''); | ||
const login = lodashGet(details, 'login', ''); | ||
const timezone = lodashGet(details, 'timezone', {}); | ||
|
||
// If we have a reportID param this means that we | ||
// arrived here via the ParticipantsPage and should be allowed to navigate back to it | ||
const shouldShowBackButton = Boolean(reportID); | ||
puneetlath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const shouldShowLocalTime = !ReportUtils.hasAutomatedExpensifyEmails([login]) && !_.isEmpty(timezone); | ||
|
||
let pronouns = lodashGet(details, 'pronouns', ''); | ||
if (pronouns && pronouns.startsWith(CONST.PRONOUNS.PREFIX)) { | ||
const localeKey = pronouns.replace(CONST.PRONOUNS.PREFIX, ''); | ||
pronouns = this.props.translate(`pronouns.${localeKey}`); | ||
} | ||
|
||
const isSMSLogin = Str.isSMSLogin(login); | ||
const phoneNumber = getPhoneNumber(details); | ||
const phoneOrEmail = isSMSLogin ? getPhoneNumber(details) : login; | ||
|
||
const isCurrentUser = _.keys(this.props.loginList).includes(login); | ||
|
||
return ( | ||
<ScreenWrapper> | ||
<HeaderWithCloseButton | ||
title={this.props.translate('common.profile')} | ||
shouldShowBackButton={shouldShowBackButton} | ||
onBackButtonPress={() => Navigation.goBack()} | ||
onCloseButtonPress={() => Navigation.dismissModal()} | ||
/> | ||
<View | ||
pointerEvents="box-none" | ||
style={[styles.containerWithSpaceBetween]} | ||
> | ||
{_.isEmpty(details) ? ( | ||
<FullScreenLoadingIndicator style={styles.flex1} /> | ||
) : ( | ||
<ScrollView> | ||
<View style={styles.avatarSectionWrapper}> | ||
<AttachmentModal | ||
headerTitle={displayName} | ||
source={UserUtils.getFullSizeAvatar(avatar, login || accountID)} | ||
isAuthTokenRequired | ||
originalFileName={originalFileName} | ||
> | ||
{({show}) => ( | ||
<PressableWithoutFocus | ||
style={styles.noOutline} | ||
onPress={show} | ||
> | ||
<OfflineWithFeedback pendingAction={lodashGet(details, 'pendingFields.avatar', null)}> | ||
<Avatar | ||
containerStyles={[styles.avatarLarge, styles.mb3]} | ||
imageStyles={[styles.avatarLarge]} | ||
source={UserUtils.getAvatar(avatar, login || accountID)} | ||
puneetlath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
size={CONST.AVATAR_SIZE.LARGE} | ||
/> | ||
</OfflineWithFeedback> | ||
</PressableWithoutFocus> | ||
)} | ||
</AttachmentModal> | ||
{Boolean(displayName) && ( | ||
<Text | ||
style={[styles.textHeadline, styles.mb6, styles.pre]} | ||
numberOfLines={1} | ||
> | ||
{displayName} | ||
</Text> | ||
)} | ||
{login ? ( | ||
<View style={[styles.mb6, styles.detailsPageSectionContainer, styles.w100]}> | ||
<Text | ||
style={[styles.textLabelSupporting, styles.mb1]} | ||
numberOfLines={1} | ||
> | ||
{this.props.translate(isSMSLogin ? 'common.phoneNumber' : 'common.email')} | ||
</Text> | ||
<CommunicationsLink value={phoneOrEmail}> | ||
<Tooltip text={phoneOrEmail}> | ||
<Text numberOfLines={1}>{isSMSLogin ? this.props.formatPhoneNumber(phoneNumber) : login}</Text> | ||
</Tooltip> | ||
</CommunicationsLink> | ||
</View> | ||
) : null} | ||
{pronouns ? ( | ||
<View style={[styles.mb6, styles.detailsPageSectionContainer]}> | ||
<Text | ||
style={[styles.textLabelSupporting, styles.mb1]} | ||
numberOfLines={1} | ||
> | ||
{this.props.translate('profilePage.preferredPronouns')} | ||
</Text> | ||
<Text numberOfLines={1}>{pronouns}</Text> | ||
</View> | ||
) : null} | ||
{shouldShowLocalTime && <AutoUpdateTime timezone={timezone} />} | ||
</View> | ||
{!isCurrentUser && Boolean(login) && ( | ||
<MenuItem | ||
title={`${this.props.translate('common.message')}${displayName}`} | ||
icon={Expensicons.ChatBubble} | ||
onPress={() => Report.navigateToAndOpenReport([login])} | ||
wrapperStyle={styles.breakAll} | ||
shouldShowRightIcon | ||
/> | ||
)} | ||
</ScrollView> | ||
)} | ||
</View> | ||
</ScreenWrapper> | ||
); | ||
} | ||
} | ||
|
||
ProfilePage.propTypes = propTypes; | ||
ProfilePage.defaultProps = defaultProps; | ||
|
||
export default compose( | ||
withLocalize, | ||
withOnyx({ | ||
personalDetails: { | ||
key: ONYXKEYS.PERSONAL_DETAILS_LIST, | ||
}, | ||
loginList: { | ||
key: ONYXKEYS.LOGIN_LIST, | ||
}, | ||
}), | ||
)(ProfilePage); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👋 Just a heads-up that this has caused a regression in #21969
Deep linking wasn't working for the new PROFILE route.
When adding a new route we should also add it to App/well-known
/apple-app-site-association for iOS
And to AndroidManifest.xml for Android
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wow. I had no idea. Can we make that obvious somehow? Like with a checklist item or lint rule or something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@puneetlath, I proposed to add a checklist item in https://expensify.slack.com/archives/C049HHMV9SM/p1690395182200269
Looks like we should add it, I'll do that tomorrow (not sure what the process is just yet, will find it)