This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / lib / components / profile / FollowButton.svelte
2.7 kB 100 lines
1<script lang="ts"> 2 import Button from '$lib/components/ui/Button.svelte'; 3 import UserRoundPlus from '$icon/user-round-plus'; 4 import UserRoundMinus from '$icon/user-round-minus'; 5 import { getAuth } from '$lib/auth.svelte'; 6 import { createFollow, deleteFollow } from '$lib/api/graph'; 7 import { getProfileCounts } from './counts.svelte'; 8 import type { FollowChange } from './types'; 9 import { createOptimisticRelation } from '$lib/optimistic.svelte'; 10 11 interface Props { 12 profileDid: string; 13 initialRkey?: string | null; 14 size?: 'sm' | 'md'; 15 onCommit?: (change: FollowChange) => void; 16 } 17 18 let { profileDid, initialRkey, size = 'sm', onCommit }: Props = $props(); 19 20 const auth = getAuth(); 21 const profileCounts = getProfileCounts(); 22 const signedIn = $derived(Boolean(auth.currentDid)); 23 const isSelf = $derived(auth.currentDid === profileDid); 24 const relation = createOptimisticRelation({ 25 key: () => `${auth.currentDid ?? ''}:${profileDid}`, 26 loadedRkey: () => initialRkey 27 }); 28 29 let busy = $state(false); 30 const following = $derived(relation.active); 31 32 const commit = (change: FollowChange) => { 33 profileCounts?.adjust(change.subjectDid, 'followers', change.delta); 34 profileCounts?.adjust(change.viewerDid, 'following', change.delta); 35 onCommit?.(change); 36 }; 37 38 const toggle = async () => { 39 const agent = auth.agent; 40 if (!agent || busy || !relation.known) return; 41 busy = true; 42 relation.resetFailure(); 43 try { 44 if (relation.active && relation.rkey) { 45 await deleteFollow(agent, relation.rkey); 46 relation.deleted(); 47 commit({ 48 viewerDid: agent.sub, 49 subjectDid: profileDid, 50 following: false, 51 rkey: null, 52 delta: -1 53 }); 54 } else { 55 const rkey = await createFollow(agent, profileDid); 56 relation.created(rkey); 57 commit({ 58 viewerDid: agent.sub, 59 subjectDid: profileDid, 60 following: true, 61 rkey, 62 delta: 1 63 }); 64 } 65 } catch { 66 relation.fail(); 67 } finally { 68 busy = false; 69 } 70 }; 71</script> 72 73{#if !isSelf} 74 {#if !signedIn} 75 <Button href="/login" variant="default" {size} class="w-full gap-2"> 76 <UserRoundPlus class="size-4" aria-hidden="true" /><span>Follow</span> 77 </Button> 78 {:else} 79 <div class="w-full"> 80 <Button 81 variant="default" 82 {size} 83 class="w-full gap-2" 84 loading={busy} 85 disabled={!relation.known} 86 onclick={toggle} 87 > 88 {#if following} 89 <UserRoundMinus class="size-4" aria-hidden="true" /> 90 {:else} 91 <UserRoundPlus class="size-4" aria-hidden="true" /> 92 {/if} 93 <span>{following ? 'Unfollow' : 'Follow'}</span> 94 </Button> 95 {#if relation.failed} 96 <p class="mt-1 text-xs text-danger">Something went wrong. Try again.</p> 97 {/if} 98 </div> 99 {/if} 100{/if}