Add follow button next to each user in search results

Extract FollowButton and shared follow/unfollow actions, reused by the user profile page.
This commit is contained in:
2026-09-23 22:34:56 +02:00
parent 84408b842b
commit 72cbc18a48
6 changed files with 110 additions and 59 deletions
@@ -1,10 +1,6 @@
import type { PageServerLoad, Actions } from './$types';
import {
createApiClient,
optionalUser,
requireAccessTokenAction,
unwrapOrError
} from '$lib/server/api';
import { createApiClient, optionalUser, unwrapOrError } from '$lib/server/api';
import { followUserAction, unfollowUserAction } from '$lib/server/followActions';
const PROFILE_PLAYLISTS_PAGE_SIZE = 50;
@@ -55,21 +51,6 @@ export const load: PageServerLoad = async ({ params, locals, url, fetch, parent
};
export const actions: Actions = {
follow: async ({ params, locals, fetch }) => {
const accessToken = requireAccessTokenAction(locals);
if (typeof accessToken !== 'string') return accessToken;
const api = createApiClient({ fetch, accessToken });
await api.POST('/users/{id}/follow', { params: { path: { id: params.id } } });
return { following: true };
},
unfollow: async ({ params, locals, fetch }) => {
const accessToken = requireAccessTokenAction(locals);
if (typeof accessToken !== 'string') return accessToken;
const api = createApiClient({ fetch, accessToken });
await api.DELETE('/users/{id}/follow', { params: { path: { id: params.id } } });
return { following: false };
}
follow: followUserAction,
unfollow: unfollowUserAction
};
@@ -1,29 +1,19 @@
<script lang="ts">
import { untrack } from 'svelte';
import { enhance } from '$app/forms';
import ListMusic from '@lucide/svelte/icons/list-music';
import Page from '$lib/components/ui/layout/Page.svelte';
import MediaCard from '$lib/components/ui/media/MediaCard.svelte';
import EmptyState from '$lib/components/ui/primitives/EmptyState.svelte';
import Button from '$lib/components/ui/primitives/Button.svelte';
import FollowButton from '$lib/components/ui/media/FollowButton.svelte';
import Avatar from '$lib/components/ui/media/Avatar.svelte';
import PageHeader from '$lib/components/ui/layout/PageHeader.svelte';
import SectionHeading from '$lib/components/ui/layout/SectionHeading.svelte';
import { plural, playlistMeta } from '$lib/utils/format';
let { data, form } = $props();
let { data } = $props();
const profile = $derived(data.profile);
const playlists = $derived(data.playlists);
const canFollow = $derived(!data.isOwnProfile && !data.isAnonymous);
let following = $state(untrack(() => data.isFollowing));
let submitting = $state(false);
$effect(() => {
if (form && 'following' in form && typeof form.following === 'boolean')
following = form.following;
});
</script>
<svelte:head>
@@ -32,26 +22,7 @@
</svelte:head>
{#snippet followAction()}
<form
method="POST"
action={following ? '?/unfollow' : '?/follow'}
use:enhance={() => {
submitting = true;
return async ({ update }) => {
await update();
submitting = false;
};
}}
>
<Button
type="submit"
size="sm"
variant={following ? 'secondary' : 'primary'}
disabled={submitting}
>
{following ? 'Dejar de seguir' : 'Seguir'}
</Button>
</form>
<FollowButton following={data.isFollowing} invalidateAll />
{/snippet}
<Page>