From 2d0eefebc338eee0d5d7e3e4c02bd6bba7f6baa0 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 9 Jul 2024 17:10:50 +0100 Subject: [PATCH] Add social proof to suggested follows (#4602) * replace unused `followers` prop with social proof * Introduce 'minimal' version * Gate social proof one explore page, fix space if no desc * Use smaller avis for minimal --------- Co-authored-by: Eric Bailey --- src/components/KnownFollowers.tsx | 20 ++++-- src/lib/statsig/gates.ts | 1 + src/view/com/profile/ProfileCard.tsx | 103 ++++++++++----------------- src/view/screens/Search/Explore.tsx | 15 +++- 4 files changed, 64 insertions(+), 75 deletions(-) diff --git a/src/components/KnownFollowers.tsx b/src/components/KnownFollowers.tsx index 7b861dc6..4017a7b0 100644 --- a/src/components/KnownFollowers.tsx +++ b/src/components/KnownFollowers.tsx @@ -12,6 +12,7 @@ import {Link, LinkProps} from '#/components/Link' import {Text} from '#/components/Typography' const AVI_SIZE = 30 +const AVI_SIZE_SMALL = 20 const AVI_BORDER = 1 /** @@ -30,10 +31,12 @@ export function KnownFollowers({ profile, moderationOpts, onLinkPress, + minimal, }: { profile: AppBskyActorDefs.ProfileViewDetailed moderationOpts: ModerationOpts onLinkPress?: LinkProps['onPress'] + minimal?: boolean }) { const cache = React.useRef>( new Map(), @@ -59,6 +62,7 @@ export function KnownFollowers({ cachedKnownFollowers={cachedKnownFollowers} moderationOpts={moderationOpts} onLinkPress={onLinkPress} + minimal={minimal} /> ) } @@ -71,11 +75,13 @@ function KnownFollowersInner({ moderationOpts, cachedKnownFollowers, onLinkPress, + minimal, }: { profile: AppBskyActorDefs.ProfileViewDetailed moderationOpts: ModerationOpts cachedKnownFollowers: AppBskyActorDefs.KnownFollowers onLinkPress?: LinkProps['onPress'] + minimal?: boolean }) { const t = useTheme() const {_} = useLingui() @@ -110,6 +116,8 @@ function KnownFollowersInner({ */ if (slice.length === 0) return null + const SIZE = minimal ? AVI_SIZE_SMALL : AVI_SIZE + return ( @@ -129,8 +137,8 @@ function KnownFollowersInner({ diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index 378b2734..1c86d01d 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -1,6 +1,7 @@ export type Gate = // Keep this alphabetic please. | 'debug_show_feedcontext' + | 'explore_page_profile_card_social_proof' | 'native_pwi_disabled' | 'new_user_guided_tour' | 'new_user_progress_guide' diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index d18103f3..9458fbaf 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -5,7 +5,6 @@ import { moderateProfile, ModerationDecision, } from '@atproto/api' -import {Trans} from '@lingui/macro' import {useQueryClient} from '@tanstack/react-query' import {useProfileShadow} from '#/state/cache/profile-shadow' @@ -19,12 +18,16 @@ import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' import {s} from 'lib/styles' import {precacheProfile} from 'state/queries/profile' +import {atoms as a} from '#/alf' +import { + KnownFollowers, + shouldShowKnownFollowers, +} from '#/components/KnownFollowers' import {Link} from '../util/Link' import {Text} from '../util/text/Text' import {PreviewableUserAvatar} from '../util/UserAvatar' import {FollowButton} from './FollowButton' import hairlineWidth = StyleSheet.hairlineWidth -import {atoms as a} from '#/alf' import * as Pills from '#/components/Pills' export function ProfileCard({ @@ -33,22 +36,22 @@ export function ProfileCard({ noModFilter, noBg, noBorder, - followers, renderButton, onPress, style, + showKnownFollowers, }: { testID?: string profile: AppBskyActorDefs.ProfileViewBasic noModFilter?: boolean noBg?: boolean noBorder?: boolean - followers?: AppBskyActorDefs.ProfileView[] | undefined renderButton?: ( profile: Shadow, ) => React.ReactNode onPress?: () => void style?: StyleProp + showKnownFollowers?: boolean }) { const queryClient = useQueryClient() const pal = usePalette('default') @@ -70,6 +73,11 @@ export function ProfileCard({ return null } + const knownFollowersVisible = + showKnownFollowers && + shouldShowKnownFollowers(profile.viewer?.knownFollowers) && + moderationOpts + return ( {renderButton(profile)} ) : undefined} - {profile.description ? ( + {profile.description || knownFollowersVisible ? ( - - {profile.description as string} - + {profile.description ? ( + + {profile.description as string} + + ) : null} + {knownFollowersVisible ? ( + + + + ) : null} ) : null} - ) } @@ -155,73 +179,20 @@ export function ProfileCardPills({ ) } -function FollowersList({ - followers, -}: { - followers?: AppBskyActorDefs.ProfileView[] | undefined -}) { - const pal = usePalette('default') - const moderationOpts = useModerationOpts() - - const followersWithMods = React.useMemo(() => { - if (!followers || !moderationOpts) { - return [] - } - - return followers - .map(f => ({ - f, - mod: moderateProfile(f, moderationOpts), - })) - .filter(({mod}) => !mod.ui('profileList').filter) - }, [followers, moderationOpts]) - - if (!followersWithMods?.length) { - return null - } - - return ( - - - - Followed by{' '} - {followersWithMods.map(({f}) => f.displayName || f.handle).join(', ')} - - - {followersWithMods.slice(0, 3).map(({f, mod}) => ( - - - - - - ))} - - ) -} - export function ProfileCardWithFollowBtn({ profile, noBg, noBorder, - followers, onPress, logContext = 'ProfileCard', + showKnownFollowers, }: { - profile: AppBskyActorDefs.ProfileViewBasic + profile: AppBskyActorDefs.ProfileView noBg?: boolean noBorder?: boolean - followers?: AppBskyActorDefs.ProfileView[] | undefined onPress?: () => void logContext?: 'ProfileCard' | 'StarterPackProfilesList' + showKnownFollowers?: boolean }) { const {currentAccount} = useSession() const isMe = profile.did === currentAccount?.did @@ -231,7 +202,6 @@ export function ProfileCardWithFollowBtn({ profile={profile} noBg={noBg} noBorder={noBorder} - followers={followers} renderButton={ isMe ? undefined @@ -240,6 +210,7 @@ export function ProfileCardWithFollowBtn({ ) } onPress={onPress} + showKnownFollowers={!isMe && showKnownFollowers} /> ) } diff --git a/src/view/screens/Search/Explore.tsx b/src/view/screens/Search/Explore.tsx index 85e8ffa4..05fd85ef 100644 --- a/src/view/screens/Search/Explore.tsx +++ b/src/view/screens/Search/Explore.tsx @@ -10,6 +10,7 @@ import { import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useGate} from '#/lib/statsig/statsig' import {logger} from '#/logger' import {isWeb} from '#/platform/detection' import {useModerationOpts} from '#/state/preferences/moderation-opts' @@ -241,7 +242,7 @@ type ExploreScreenItems = | { type: 'profile' key: string - profile: AppBskyActorDefs.ProfileViewBasic + profile: AppBskyActorDefs.ProfileView } | { type: 'feed' @@ -291,6 +292,7 @@ export function Explore() { error: feedsError, fetchNextPage: fetchNextFeedsPage, } = useGetPopularFeedsQuery({limit: 10}) + const gate = useGate() const isLoadingMoreProfiles = isFetchingNextProfilesPage && !isLoadingProfiles const onLoadMoreProfiles = React.useCallback(async () => { @@ -492,7 +494,14 @@ export function Explore() { case 'profile': { return ( - + ) } @@ -555,7 +564,7 @@ export function Explore() { } } }, - [t, moderationOpts], + [t, moderationOpts, gate], ) return (