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 onCommit?: (change: FollowChange) => void;
16 }
17
18 let { profileDid, initialRkey, 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={resolve("/login")} variant="default" class="w-full gap-2" insetShadow={true}>
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 class="w-full gap-2"
83 insetShadow={true}
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 typography-paragraph-small text-foreground-danger">
97 Something went wrong. Try again.
98 </p>
99 {/if}
100 </div>
101 {/if}
102{/if}