This repository has no description
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}