diff --git a/src/lib/hooks/useFollowProfile.ts b/src/lib/hooks/useFollowProfile.ts deleted file mode 100644 index 98dd63f5f7..0000000000 --- a/src/lib/hooks/useFollowProfile.ts +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react' -import {AppBskyActorDefs} from '@atproto/api' -import {useStores} from 'state/index' -import {FollowState} from 'state/models/cache/my-follows' -import {logger} from '#/logger' - -export function useFollowProfile(profile: AppBskyActorDefs.ProfileViewBasic) { - const store = useStores() - const state = store.me.follows.getFollowState(profile.did) - - return { - state, - following: state === FollowState.Following, - toggle: React.useCallback(async () => { - if (state === FollowState.Following) { - try { - await store.agent.deleteFollow( - store.me.follows.getFollowUri(profile.did), - ) - store.me.follows.removeFollow(profile.did) - return { - state: FollowState.NotFollowing, - following: false, - } - } catch (e: any) { - logger.error('Failed to delete follow', {error: e}) - throw e - } - } else if (state === FollowState.NotFollowing) { - try { - const res = await store.agent.follow(profile.did) - store.me.follows.addFollow(profile.did, { - followRecordUri: res.uri, - did: profile.did, - handle: profile.handle, - displayName: profile.displayName, - avatar: profile.avatar, - }) - return { - state: FollowState.Following, - following: true, - } - } catch (e: any) { - logger.error('Failed to create follow', {error: e}) - throw e - } - } - - return { - state: FollowState.Unknown, - following: false, - } - }, [store, profile, state]), - } -} diff --git a/src/state/queries/suggested-follows.ts b/src/state/queries/suggested-follows.ts index 805668bcb1..5b5e142ca7 100644 --- a/src/state/queries/suggested-follows.ts +++ b/src/state/queries/suggested-follows.ts @@ -1,7 +1,12 @@ -import {AppBskyActorGetSuggestions, moderateProfile} from '@atproto/api' +import { + AppBskyActorGetSuggestions, + AppBskyGraphGetSuggestedFollowsByActor, + moderateProfile, +} from '@atproto/api' import { useInfiniteQuery, useMutation, + useQuery, InfiniteData, QueryKey, } from '@tanstack/react-query' @@ -9,7 +14,11 @@ import { import {useSession} from '#/state/session' import {useModerationOpts} from '#/state/queries/preferences' -export const suggestedFollowsQueryKey = ['suggested-follows'] +const suggestedFollowsQueryKey = ['suggested-follows'] +const suggestedFollowsByActorQuery = (did: string) => [ + 'suggested-follows-by-actor', + did, +] export function useSuggestedFollowsQuery() { const {agent, currentAccount} = useSession() @@ -60,6 +69,21 @@ export function useSuggestedFollowsQuery() { }) } +export function useSuggestedFollowsByActorQuery({did}: {did: string}) { + const {agent} = useSession() + + return useQuery({ + queryKey: suggestedFollowsByActorQuery(did), + queryFn: async () => { + const res = await agent.app.bsky.graph.getSuggestedFollowsByActor({ + actor: did, + }) + return res.data + }, + }) +} + +// TODO: Delete and replace usages with the one above. export function useGetSuggestedFollowersByActor() { const {agent} = useSession() diff --git a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx index cf759ddd17..a34f2b5fe4 100644 --- a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx +++ b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx @@ -6,20 +6,16 @@ import Animated, { useAnimatedStyle, Easing, } from 'react-native-reanimated' -import {useQuery} from '@tanstack/react-query' import {AppBskyActorDefs, moderateProfile} from '@atproto/api' -import {observer} from 'mobx-react-lite' import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' import * as Toast from '../util/Toast' -import {useStores} from 'state/index' import {usePalette} from 'lib/hooks/usePalette' import {Text} from 'view/com/util/text/Text' import {UserAvatar} from 'view/com/util/UserAvatar' -import {useFollowProfile} from 'lib/hooks/useFollowProfile' import {Button} from 'view/com/util/forms/Button' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' @@ -27,6 +23,13 @@ import {makeProfileLink} from 'lib/routes/links' import {Link} from 'view/com/util/Link' import {useAnalytics} from 'lib/analytics/analytics' import {isWeb} from 'platform/detection' +import {useModerationOpts} from '#/state/queries/preferences' +import {useSuggestedFollowsByActorQuery} from '#/state/queries/suggested-follows' +import {useProfileShadow} from '#/state/cache/profile-shadow' +import { + useProfileFollowMutation, + useProfileUnfollowMutation, +} from '#/state/queries/profile' const OUTER_PADDING = 10 const INNER_PADDING = 14 @@ -43,7 +46,6 @@ export function ProfileHeaderSuggestedFollows({ }) { const {track} = useAnalytics() const pal = usePalette('default') - const store = useStores() const animatedHeight = useSharedValue(0) const animatedStyles = useAnimatedStyle(() => ({ opacity: animatedHeight.value / TOTAL_HEIGHT, @@ -66,31 +68,8 @@ export function ProfileHeaderSuggestedFollows({ } }, [active, animatedHeight, track]) - const {isLoading, data: suggestedFollows} = useQuery({ - enabled: active, - cacheTime: 0, - staleTime: 0, - queryKey: ['suggested_follows_by_actor', actorDid], - async queryFn() { - try { - const { - data: {suggestions}, - success, - } = await store.agent.app.bsky.graph.getSuggestedFollowsByActor({ - actor: actorDid, - }) - - if (!success) { - return [] - } - - store.me.follows.hydrateMany(suggestions) - - return suggestions - } catch (e) { - return [] - } - }, + const {isLoading, data, dataUpdatedAt} = useSuggestedFollowsByActorQuery({ + did: actorDid, }) return ( @@ -149,9 +128,13 @@ export function ProfileHeaderSuggestedFollows({ - ) : suggestedFollows ? ( - suggestedFollows.map(profile => ( - + ) : data ? ( + data.suggestions.map(profile => ( + )) ) : ( @@ -214,29 +197,51 @@ function SuggestedFollowSkeleton() { ) } -const SuggestedFollow = observer(function SuggestedFollowImpl({ - profile, +function SuggestedFollow({ + profile: profileUnshadowed, + dataUpdatedAt, }: { profile: AppBskyActorDefs.ProfileView + dataUpdatedAt: number }) { const {track} = useAnalytics() const pal = usePalette('default') - const store = useStores() - const {following, toggle} = useFollowProfile(profile) - const moderation = moderateProfile(profile, store.preferences.moderationOpts) + const moderationOpts = useModerationOpts() + const profile = useProfileShadow(profileUnshadowed, dataUpdatedAt) + const followMutation = useProfileFollowMutation() + const unfollowMutation = useProfileUnfollowMutation() - const onPress = React.useCallback(async () => { + const onPressFollow = React.useCallback(async () => { + if (profile.viewer?.following) { + return + } try { - const {following: isFollowing} = await toggle() + track('ProfileHeader:SuggestedFollowFollowed') + await followMutation.mutateAsync({did: profile.did}) + } catch (e: any) { + Toast.show('An issue occurred, please try again.') + } + }, [followMutation, profile, track]) - if (isFollowing) { - track('ProfileHeader:SuggestedFollowFollowed') - } + const onPressUnfollow = React.useCallback(async () => { + if (!profile.viewer?.following) { + return + } + try { + await unfollowMutation.mutateAsync({ + did: profile.did, + followUri: profile.viewer?.following, + }) } catch (e: any) { Toast.show('An issue occurred, please try again.') } - }, [toggle, track]) + }, [unfollowMutation, profile]) + if (!moderationOpts) { + return null + } + const moderation = moderateProfile(profile, moderationOpts) + const following = profile.viewer?.following return ( ) -}) +} const styles = StyleSheet.create({ suggestedFollowCardOuter: {