diff --git a/lib/shared/thread-utils.js b/lib/shared/thread-utils.js --- a/lib/shared/thread-utils.js +++ b/lib/shared/thread-utils.js @@ -102,6 +102,7 @@ AccountUserInfo, LoggedInUserInfo, UserInfo, + ProfileUserInfo, } from '../types/user-types.js'; import { ET, @@ -1618,7 +1619,9 @@ ); } -function useUserProfileThreadInfo(userInfo: ?UserInfo): ?UserProfileThreadInfo { +function useUserProfileThreadInfo( + userInfo: ?ProfileUserInfo, +): ?UserProfileThreadInfo { const userID = userInfo?.id; const username = userInfo?.username; diff --git a/lib/types/user-types.js b/lib/types/user-types.js --- a/lib/types/user-types.js +++ b/lib/types/user-types.js @@ -74,6 +74,14 @@ +avatar?: ?ClientAvatar, }; +export type ProfileUserInfo = { + +id: string, + +username: ?string, + +farcasterUsername?: ?string, + +relationshipStatus?: UserRelationshipStatus, + +avatar?: ?ClientAvatar, +}; + export type LoggedInUserInfo = { +id: string, +username: string, diff --git a/native/user-profile/user-profile.react.js b/native/user-profile/user-profile.react.js --- a/native/user-profile/user-profile.react.js +++ b/native/user-profile/user-profile.react.js @@ -6,10 +6,14 @@ import { View, Text, TouchableOpacity } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { useResolvedUsername } from 'lib/hooks/names-cache.js'; +import { useResolvableNames } from 'lib/hooks/names-cache.js'; import { relationshipBlockedInEitherDirection } from 'lib/shared/relationship-utils.js'; import { useUserProfileThreadInfo } from 'lib/shared/thread-utils.js'; -import type { UserInfo } from 'lib/types/user-types'; +import { + stringForUserExplicit, + ensNameForFarcasterUsername, +} from 'lib/shared/user-utils.js'; +import type { ProfileUserInfo } from 'lib/types/user-types.js'; import sleep from 'lib/utils/sleep.js'; import UserProfileAvatar from './user-profile-avatar.react.js'; @@ -27,7 +31,7 @@ import { useStyles } from '../themes/colors.js'; type Props = { - +userInfo: ?UserInfo, + +userInfo: ?ProfileUserInfo, }; function UserProfile(props: Props): React.Node { @@ -35,7 +39,32 @@ const userProfileThreadInfo = useUserProfileThreadInfo(userInfo); - const resolvedUsernameText = useResolvedUsername(userInfo); + const [resolvedUserInfo] = useResolvableNames([userInfo]); + const resolvedUsernameText = stringForUserExplicit(resolvedUserInfo); + + const farcasterUsername = resolvedUserInfo?.farcasterUsername; + + const styles = useStyles(unboundStyles); + + const farcasterUsernameElement = React.useMemo(() => { + if (!farcasterUsername) { + return null; + } + const ensFCName = ensNameForFarcasterUsername(farcasterUsername); + if (ensFCName === resolvedUsernameText) { + return null; + } + return ( + + Farcaster: {ensFCName} + + ); + }, [ + farcasterUsername, + resolvedUsernameText, + styles.farcasterUsernameText, + styles.farcasterUsernameContainer, + ]); const [usernameCopied, setUsernameCopied] = React.useState(false); @@ -76,8 +105,6 @@ userProfileThreadInfo, ]); - const styles = useStyles(unboundStyles); - const menuButton = React.useMemo(() => { if (!userProfileThreadInfo) { return null; @@ -177,6 +204,7 @@ {resolvedUsernameText} {copyUsernameButton} + {farcasterUsernameElement} {messageButton} @@ -203,6 +231,14 @@ fontSize: 18, fontWeight: '500', }, + farcasterUsernameContainer: { + paddingVertical: 8, + }, + farcasterUsernameText: { + color: 'modalForegroundLabel', + fontSize: 14, + fontWeight: '500', + }, copyUsernameContainer: { flexDirection: 'row', justifyContent: 'center', diff --git a/web/modals/user-profile/user-profile-modal.react.js b/web/modals/user-profile/user-profile-modal.react.js --- a/web/modals/user-profile/user-profile-modal.react.js +++ b/web/modals/user-profile/user-profile-modal.react.js @@ -4,7 +4,7 @@ import { useModalContext } from 'lib/components/modal-provider.react.js'; import { useUserProfileThreadInfo } from 'lib/shared/thread-utils.js'; -import type { UserInfo } from 'lib/types/user-types.js'; +import type { ProfileUserInfo } from 'lib/types/user-types.js'; import UserProfileMenu from './user-profile-menu.react.js'; import UserProfile from './user-profile.react.js'; @@ -20,7 +20,7 @@ const { popModal } = useModalContext(); - const userInfo: ?UserInfo = useSelector( + const userInfo: ?ProfileUserInfo = useSelector( state => state.userStore.userInfos[userID], ); diff --git a/web/modals/user-profile/user-profile.css b/web/modals/user-profile/user-profile.css --- a/web/modals/user-profile/user-profile.css +++ b/web/modals/user-profile/user-profile.css @@ -25,6 +25,16 @@ font-weight: 500; } +.farcasterUsernameContainer { + padding: 8px 0; +} + +.farcasterUsernameText { + color: var(--text-background-primary-default); + font-size: var(--s-font-14); + font-weight: 500; +} + .copyUsernameContainer { display: flex; align-items: center; diff --git a/web/modals/user-profile/user-profile.react.js b/web/modals/user-profile/user-profile.react.js --- a/web/modals/user-profile/user-profile.react.js +++ b/web/modals/user-profile/user-profile.react.js @@ -4,10 +4,14 @@ import { useModalContext } from 'lib/components/modal-provider.react.js'; import SWMansionIcon from 'lib/components/swmansion-icon.react.js'; -import { useResolvedUsername } from 'lib/hooks/names-cache.js'; +import { useResolvableNames } from 'lib/hooks/names-cache.js'; import { relationshipBlockedInEitherDirection } from 'lib/shared/relationship-utils.js'; -import type { UserProfileThreadInfo } from 'lib/types/thread-types'; -import type { UserInfo } from 'lib/types/user-types'; +import { + stringForUserExplicit, + ensNameForFarcasterUsername, +} from 'lib/shared/user-utils.js'; +import type { UserProfileThreadInfo } from 'lib/types/thread-types.js'; +import type { ProfileUserInfo } from 'lib/types/user-types.js'; import sleep from 'lib/utils/sleep.js'; import UserProfileActionButtons from './user-profile-action-buttons.react.js'; @@ -17,7 +21,7 @@ import SingleLine from '../../components/single-line.react.js'; type Props = { - +userInfo: ?UserInfo, + +userInfo: ?ProfileUserInfo, +userProfileThreadInfo: ?UserProfileThreadInfo, }; @@ -26,7 +30,10 @@ const { pushModal } = useModalContext(); - const resolvedUsernameText = useResolvedUsername(userInfo); + const [resolvedUserInfo] = useResolvableNames([userInfo]); + const resolvedUsernameText = stringForUserExplicit(resolvedUserInfo); + + const farcasterUsername = resolvedUserInfo?.farcasterUsername; const [usernameCopied, setUsernameCopied] = React.useState(false); @@ -58,6 +65,21 @@ ); }, [userInfo?.relationshipStatus, userProfileThreadInfo]); + const farcasterUsernameElement = React.useMemo(() => { + if (!farcasterUsername) { + return null; + } + const ensFCName = ensNameForFarcasterUsername(farcasterUsername); + if (ensFCName === resolvedUsernameText) { + return null; + } + return ( +
+

Farcaster: {ensFCName}

+
+ ); + }, [farcasterUsername, resolvedUsernameText]); + const userProfile = React.useMemo( () => (
@@ -81,6 +103,7 @@ {!usernameCopied ? 'Copy username' : 'Username copied!'}

+ {farcasterUsernameElement} {actionButtons} @@ -93,6 +116,7 @@ userInfo?.id, usernameCopied, resolvedUsernameText, + farcasterUsernameElement, ], );