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