diff --git a/apps/admin-x-activitypub/src/api/activitypub.ts b/apps/admin-x-activitypub/src/api/activitypub.ts index d2fbed84daf..2958f86c238 100644 --- a/apps/admin-x-activitypub/src/api/activitypub.ts +++ b/apps/admin-x-activitypub/src/api/activitypub.ts @@ -195,6 +195,12 @@ export class ActivityPubAPI { return json as Actor; } + async unfollow(username: string): Promise<Actor> { + const url = new URL(`.ghost/activitypub/actions/unfollow/${username}`, this.apiUrl); + const json = await this.fetchJSON(url, 'POST'); + return json as Actor; + } + get likedApiUrl() { return new URL(`.ghost/activitypub/liked/${this.handle}`, this.apiUrl); } diff --git a/apps/admin-x-activitypub/src/components/global/FollowButton.tsx b/apps/admin-x-activitypub/src/components/global/FollowButton.tsx index f266f220632..d03cd1770e6 100644 --- a/apps/admin-x-activitypub/src/components/global/FollowButton.tsx +++ b/apps/admin-x-activitypub/src/components/global/FollowButton.tsx @@ -1,7 +1,7 @@ import clsx from 'clsx'; import {Button} from '@tryghost/admin-x-design-system'; import {useEffect, useState} from 'react'; -import {useFollow} from '../../hooks/useActivityPubQueries'; +import {useFollow, useUnfollow} from '../../hooks/useActivityPubQueries'; interface FollowButtonProps { className?: string; @@ -25,7 +25,15 @@ const FollowButton: React.FC<FollowButtonProps> = ({ const [isFollowing, setIsFollowing] = useState(following); const [isHovered, setIsHovered] = useState(false); - const mutation = useFollow('index', + const unfollowMutation = useUnfollow('index', + noop, + () => { + setIsFollowing(false); + onUnfollow(); + } + ); + + const followMutation = useFollow('index', noop, () => { setIsFollowing(false); @@ -37,11 +45,11 @@ const FollowButton: React.FC<FollowButtonProps> = ({ if (isFollowing) { setIsFollowing(false); onUnfollow(); - // @TODO: Implement unfollow mutation + unfollowMutation.mutate(handle); } else { setIsFollowing(true); onFollow(); - mutation.mutate(handle); + followMutation.mutate(handle); } }; diff --git a/apps/admin-x-activitypub/src/hooks/useActivityPubQueries.ts b/apps/admin-x-activitypub/src/hooks/useActivityPubQueries.ts index 183d1fafbcb..fa1f187cbcc 100644 --- a/apps/admin-x-activitypub/src/hooks/useActivityPubQueries.ts +++ b/apps/admin-x-activitypub/src/hooks/useActivityPubQueries.ts @@ -7,7 +7,8 @@ import { Actor, type GetAccountFollowsResponse, type Profile, - type SearchResults + type SearchResults, + Actor } from '../api/activitypub'; import {Activity} from '../components/activities/ActivityItem'; import { @@ -203,6 +204,45 @@ export function useFollowingForUser(handle: string) { }); } +export function useUnfollow(handle: string, onSuccess: () => void, onError: () => void) { + const queryClient = useQueryClient(); + return useMutation({ + async mutationFn(username: string) { + const siteUrl = await getSiteUrl(); + const api = createActivityPubAPI(handle, siteUrl); + return api.unfollow(username); + }, + onSuccess(unfollowedActor, fullHandle) { + queryClient.setQueryData([`profile:${fullHandle}`], (currentProfile: unknown) => { + if (!currentProfile) { + return currentProfile; + } + return { + ...currentProfile, + isFollowing: false + }; + }); + + queryClient.setQueryData(['following:index'], (currentFollowing?: Actor[]) => { + if (!currentFollowing) { + return currentFollowing; + } + return currentFollowing.filter(item => item.id !== unfollowedActor.id); + }); + + queryClient.setQueryData(['followingCount:index'], (currentFollowingCount?: number) => { + if (!currentFollowingCount) { + return 0; + } + return currentFollowingCount - 1; + }); + + onSuccess(); + }, + onError + }); +} + export function useFollow(handle: string, onSuccess: () => void, onError: () => void) { const queryClient = useQueryClient(); return useMutation({