diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index 2b4e81205590..082a1c7efbd5 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -45,6 +45,9 @@ export default { // Contains all the personalDetails the user has access to PERSONAL_DETAILS: 'personalDetails', + // Contains all the personalDetails the user has access to, keyed by accountID + PERSONAL_DETAILS_LIST: 'personalDetailsList', + // Contains all the private personal details of the user PRIVATE_PERSONAL_DETAILS: 'private_personalDetails', diff --git a/src/ROUTES.js b/src/ROUTES.js index bd0ad304c2f5..89f7527645bc 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -114,10 +114,12 @@ export default { SET_PASSWORD_WITH_VALIDATE_CODE: 'setpassword/:accountID/:validateCode', DETAILS: 'details', getDetailsRoute: (login) => `details?login=${encodeURIComponent(login)}`, + PROFILE: 'a/:accountID', + getProfileRoute: (accountID) => `a/${accountID}`, REPORT_PARTICIPANTS: 'r/:reportID/participants', getReportParticipantsRoute: (reportID) => `r/${reportID}/participants`, - REPORT_PARTICIPANT: 'r/:reportID/participants/details', - getReportParticipantRoute: (reportID, login) => `r/${reportID}/participants/details?login=${encodeURIComponent(login)}`, + REPORT_PARTICIPANT: 'r/:reportID/participants/a/:accountID', + getReportParticipantRoute: (reportID, accountID) => `r/${reportID}/participants/a/${accountID}`, REPORT_WITH_ID_DETAILS: 'r/:reportID/details', getReportDetailsRoute: (reportID) => `r/${reportID}/details`, REPORT_SETTINGS: 'r/:reportID/settings', diff --git a/src/components/BlockingViews/BlockingView.js b/src/components/BlockingViews/BlockingView.js index d3a552d83824..0575b92e243f 100644 --- a/src/components/BlockingViews/BlockingView.js +++ b/src/components/BlockingViews/BlockingView.js @@ -20,7 +20,7 @@ const propTypes = { title: PropTypes.string.isRequired, /** Subtitle message below the title */ - subtitle: PropTypes.string.isRequired, + subtitle: PropTypes.string, /** Link message below the subtitle */ link: PropTypes.string, @@ -40,6 +40,7 @@ const propTypes = { const defaultProps = { iconColor: themeColors.offline, + subtitle: '', shouldShowLink: false, link: 'notFound.goBackHome', iconWidth: variables.iconSizeSuperLarge, diff --git a/src/components/ReportWelcomeText.js b/src/components/ReportWelcomeText.js index a954fe3d8730..f2a79dd50fe3 100644 --- a/src/components/ReportWelcomeText.js +++ b/src/components/ReportWelcomeText.js @@ -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) => { Navigation.navigate(ROUTES.getDetailsRoute(participants[index]))} + onPress={() => Navigation.navigate(ROUTES.getProfileRoute(participantAccountIDs[index]))} > {displayName} diff --git a/src/languages/en.js b/src/languages/en.js index e099245b8392..85f696cc9ccc 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -44,6 +44,7 @@ export default { and: 'and', details: 'Details', privacy: 'Privacy', + hidden: 'Hidden', delete: 'Delete', archived: 'archived', contacts: 'Contacts', diff --git a/src/languages/es.js b/src/languages/es.js index a44bd0ae5bcd..d162c19f8513 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -43,6 +43,7 @@ export default { and: 'y', details: 'Detalles', privacy: 'Privacidad', + hidden: 'Oculto', delete: 'Eliminar', archived: 'archivado', contacts: 'Contactos', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index ab4753a67c09..b983ffd14968 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -131,6 +131,16 @@ const DetailsModalStackNavigator = createModalStackNavigator([ }, ]); +const ProfileModalStackNavigator = createModalStackNavigator([ + { + getComponent: () => { + const ProfilePage = require('../../../pages/ProfilePage').default; + return ProfilePage; + }, + name: 'Profile_Root', + }, +]); + const ReportDetailsModalStackNavigator = createModalStackNavigator([ { getComponent: () => { @@ -223,8 +233,8 @@ const ReportParticipantsModalStackNavigator = createModalStackNavigator([ }, { getComponent: () => { - const DetailsPage = require('../../../pages/DetailsPage').default; - return DetailsPage; + const ProfilePage = require('../../../pages/ProfilePage').default; + return ProfilePage; }, name: 'ReportParticipants_Details', }, @@ -725,6 +735,7 @@ export { IOUSendModalStackNavigator, SplitDetailsModalStackNavigator, DetailsModalStackNavigator, + ProfileModalStackNavigator, ReportDetailsModalStackNavigator, TaskModalStackNavigator, ReportSettingsModalStackNavigator, diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js index 2f5ac9267a50..cb82795936c2 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js @@ -34,6 +34,11 @@ function RigthModalNavigator() { options={defaultModalScreenOptions} component={ModalStackNavigators.DetailsModalStackNavigator} /> + { + // 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) : ''; +}; + +function ProfilePage(props) { + const accountID = lodashGet(props.route.params, 'accountID', 0); + + // eslint-disable-next-line rulesdir/prefer-early-return + useEffect(() => { + if (accountID > 0) { + PersonalDetails.openPublicProfilePage(accountID); + } + }, [accountID]); + + const details = lodashGet(props.personalDetails, accountID, {}); + const displayName = details.displayName ? details.displayName : 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 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 = props.translate(`pronouns.${localeKey}`); + } + + const isSMSLogin = Str.isSMSLogin(login); + const phoneNumber = getPhoneNumber(details); + const phoneOrEmail = isSMSLogin ? getPhoneNumber(details) : login; + + const isCurrentUser = _.keys(props.loginList).includes(login); + const hasMinimumDetails = !_.isEmpty(details.avatar); + const isLoading = lodashGet(details, 'isLoading', false) || _.isEmpty(details); + + // If the API returns an error for some reason there won't be any details and isLoading will get set to false, so we want to show a blocking screen + const shouldShowBlockingView = !hasMinimumDetails && !isLoading; + + return ( + + Navigation.goBack(ROUTES.HOME)} + /> + + {hasMinimumDetails && ( + + + + {({show}) => ( + + + + + + )} + + {Boolean(displayName) && ( + + {displayName} + + )} + {login ? ( + + + {props.translate(isSMSLogin ? 'common.phoneNumber' : 'common.email')} + + + + {isSMSLogin ? props.formatPhoneNumber(phoneNumber) : login} + + + + ) : null} + {pronouns ? ( + + + {props.translate('profilePage.preferredPronouns')} + + {pronouns} + + ) : null} + {shouldShowLocalTime && } + + {!isCurrentUser && Boolean(login) && ( + Report.navigateToAndOpenReport([login])} + wrapperStyle={styles.breakAll} + shouldShowRightIcon + /> + )} + + )} + {!hasMinimumDetails && isLoading && } + {shouldShowBlockingView && ( + + )} + + + ); +} + +ProfilePage.propTypes = propTypes; +ProfilePage.defaultProps = defaultProps; +ProfilePage.displayName = 'ProfilePage'; + +export default compose( + withLocalize, + withOnyx({ + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, + loginList: { + key: ONYXKEYS.LOGIN_LIST, + }, + }), +)(ProfilePage); diff --git a/src/pages/ReportParticipantsPage.js b/src/pages/ReportParticipantsPage.js index 3e5717bc15b8..0f9d070aaec3 100755 --- a/src/pages/ReportParticipantsPage.js +++ b/src/pages/ReportParticipantsPage.js @@ -64,6 +64,7 @@ const getAllParticipants = (report, personalDetails) => { return { alternateText: userLogin, displayName: userPersonalDetail.displayName, + accountID: userPersonalDetail.accountID, icons: [ { source: UserUtils.getAvatar(userPersonalDetail.avatar, login), @@ -109,7 +110,7 @@ const ReportParticipantsPage = (props) => { }, ]} onSelectRow={(option) => { - Navigation.navigate(ROUTES.getReportParticipantRoute(props.route.params.reportID, option.login)); + Navigation.navigate(ROUTES.getReportParticipantRoute(props.route.params.reportID, option.accountID)); }} hideSectionHeaders showTitleTooltip diff --git a/src/pages/home/report/ReactionList/BaseReactionList.js b/src/pages/home/report/ReactionList/BaseReactionList.js index eda4f4db0f48..7b3453072994 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.js +++ b/src/pages/home/report/ReactionList/BaseReactionList.js @@ -79,7 +79,7 @@ const BaseReactionList = (props) => { hoverStyle={styles.hoveredComponentBG} onSelectRow={() => { props.onClose(); - Navigation.navigate(ROUTES.getDetailsRoute(item.login)); + Navigation.navigate(ROUTES.getProfileRoute(item.accountID)); }} option={{ text: Str.removeSMSDomain(item.displayName), diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 96fd6be6ad26..eb5f2fa07434 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -61,13 +61,13 @@ const defaultProps = { report: undefined, }; -const showUserDetails = (email) => { - Navigation.navigate(ROUTES.getDetailsRoute(email)); +const showUserDetails = (accountID) => { + Navigation.navigate(ROUTES.getProfileRoute(accountID)); }; const ReportActionItemSingle = (props) => { const actorEmail = props.action.actorEmail.replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, ''); - const {avatar, displayName, pendingFields} = props.personalDetails[actorEmail] || {}; + const {accountID, avatar, displayName, pendingFields} = props.personalDetails[actorEmail] || {}; const avatarSource = UserUtils.getAvatar(avatar, actorEmail); // Since the display name for a report action message is delivered with the report history as an array of fragments @@ -88,7 +88,7 @@ const ReportActionItemSingle = (props) => { style={[styles.alignSelfStart, styles.mr3]} onPressIn={ControlSelection.block} onPressOut={ControlSelection.unblock} - onPress={() => showUserDetails(actorEmail)} + onPress={() => showUserDetails(accountID)} > {props.shouldShowSubscriptAvatar ? ( @@ -118,7 +118,7 @@ const ReportActionItemSingle = (props) => { style={[styles.flexShrink1, styles.mr1]} onPressIn={ControlSelection.block} onPressOut={ControlSelection.unblock} - onPress={() => showUserDetails(actorEmail)} + onPress={() => showUserDetails(accountID)} > {_.map(personArray, (fragment, index) => (