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 const following = $derived(relation.active);
30
31 const commit = (change: FollowChange) => {
32 profileCounts?.adjust(change.subjectDid, "followers", change.delta);
33 profileCounts?.adjust(change.viewerDid, "following", change.delta);
34 onCommit?.(change);
35 };
36
37 const toggle = async () => {
38 const agent = auth.agent;
39 if (!agent || !relation.known || relation.loading) return;
40 relation.begin();
41 try {
42 if (relation.active && relation.rkey) {
43 await deleteFollow(agent, relation.rkey);
44 relation.deleted();
45 commit({
46 viewerDid: agent.sub,
47 subjectDid: profileDid,
48 following: false,
49 rkey: null,
50 delta: -1
51 });
52 } else {
53 const rkey = await createFollow(agent, profileDid);
54 relation.created(rkey);
55 commit({
56 viewerDid: agent.sub,
57 subjectDid: profileDid,
58 following: true,
59 rkey,
60 delta: 1
61 });
62 }
63 } catch {
64 relation.fail();
65 }
66 };
67</script>
68
69{#if !isSelf}
70 {#if !signedIn}
71 <Button href={resolve("/login")} variant="default" class="w-full gap-2" insetShadow={true}>
72 <UserRoundPlus class="size-4" aria-hidden="true" /><span>Follow</span>
73 </Button>
74 {:else}
75 <div class="w-full">
76 <Button
77 variant="default"
78 class="w-full gap-2"
79 insetShadow={true}
80 loading={relation.loading}
81 disabled={!relation.known}
82 onclick={toggle}
83 >
84 {#if following}
85 <UserRoundMinus class="size-4" aria-hidden="true" />
86 {:else}
87 <UserRoundPlus class="size-4" aria-hidden="true" />
88 {/if}
89 <span>{following ? "Unfollow" : "Follow"}</span>
90 </Button>
91 {#if relation.failed}
92 <p class="mt-1 typography-paragraph-small text-foreground-danger">
93 Something went wrong. Try again.
94 </p>
95 {/if}
96 </div>
97 {/if}
98{/if}