Skip to content

Commit

Permalink
Wired up FollowButton to /unfollow API
Browse files Browse the repository at this point in the history
ref https://linear.app/ghost/issue/AP-311

This is very similar to the /follow API so we've mostly copied the existing
patterns there.
  • Loading branch information
allouis committed Jan 30, 2025
1 parent a4935e4 commit c58c150
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 5 deletions.
6 changes: 6 additions & 0 deletions apps/admin-x-activitypub/src/api/activitypub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
16 changes: 12 additions & 4 deletions apps/admin-x-activitypub/src/components/global/FollowButton.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
Expand All @@ -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);
}
};

Expand Down
42 changes: 41 additions & 1 deletion apps/admin-x-activitypub/src/hooks/useActivityPubQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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({
Expand Down

0 comments on commit c58c150

Please sign in to comment.