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 <git@esb.lol>
This commit is contained in:
parent
4360087ced
commit
2d0eefebc3
4 changed files with 64 additions and 75 deletions
|
@ -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<AppBskyActorDefs.ProfileViewBasic>,
|
||||
) => React.ReactNode
|
||||
onPress?: () => void
|
||||
style?: StyleProp<ViewStyle>
|
||||
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 (
|
||||
<Link
|
||||
testID={testID}
|
||||
|
@ -118,14 +126,30 @@ export function ProfileCard({
|
|||
<View style={styles.layoutButton}>{renderButton(profile)}</View>
|
||||
) : undefined}
|
||||
</View>
|
||||
{profile.description ? (
|
||||
{profile.description || knownFollowersVisible ? (
|
||||
<View style={styles.details}>
|
||||
<Text style={pal.text} numberOfLines={4}>
|
||||
{profile.description as string}
|
||||
</Text>
|
||||
{profile.description ? (
|
||||
<Text style={pal.text} numberOfLines={4}>
|
||||
{profile.description as string}
|
||||
</Text>
|
||||
) : null}
|
||||
{knownFollowersVisible ? (
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.gap_sm,
|
||||
!!profile.description && a.mt_md,
|
||||
]}>
|
||||
<KnownFollowers
|
||||
minimal
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
) : null}
|
||||
<FollowersList followers={followers} />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
@ -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 (
|
||||
<View style={styles.followedBy}>
|
||||
<Text
|
||||
type="sm"
|
||||
style={[styles.followsByDesc, pal.textLight]}
|
||||
numberOfLines={2}
|
||||
lineHeight={1.2}>
|
||||
<Trans>
|
||||
Followed by{' '}
|
||||
{followersWithMods.map(({f}) => f.displayName || f.handle).join(', ')}
|
||||
</Trans>
|
||||
</Text>
|
||||
{followersWithMods.slice(0, 3).map(({f, mod}) => (
|
||||
<View key={f.did} style={styles.followedByAviContainer}>
|
||||
<View style={[styles.followedByAvi, pal.view]}>
|
||||
<PreviewableUserAvatar
|
||||
size={32}
|
||||
profile={f}
|
||||
moderation={mod.ui('avatar')}
|
||||
type={f.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<View style={[a.border_b, t.atoms.border_contrast_low]}>
|
||||
<ProfileCardWithFollowBtn profile={item.profile} noBg noBorder />
|
||||
<ProfileCardWithFollowBtn
|
||||
profile={item.profile}
|
||||
noBg
|
||||
noBorder
|
||||
showKnownFollowers={gate(
|
||||
'explore_page_profile_card_social_proof',
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
@ -555,7 +564,7 @@ export function Explore() {
|
|||
}
|
||||
}
|
||||
},
|
||||
[t, moderationOpts],
|
||||
[t, moderationOpts, gate],
|
||||
)
|
||||
|
||||
return (
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue