This repository has no description
1<script lang="ts">
2 import { untrack } from "svelte";
3 import { getAuth } from "$lib/auth.svelte";
4 import { createBobbinClient } from "$lib/api/client";
5 import { IdentityCache } from "$lib/api/identity";
6 import { fetchVouchesPage, type VouchCursors } from "../pages";
7 import VouchCard from "../VouchCard.svelte";
8 import Section from "$lib/components/ui/Section.svelte";
9 import LoadMore from "$lib/components/ui/LoadMore.svelte";
10 import type { VouchData } from "../types";
11
12 interface Props {
13 initial: VouchData[];
14 cursors: VouchCursors;
15 did: string;
16 isSelf: boolean;
17 profileHandle: string;
18 }
19
20 let { initial, cursors: initialCursors, did, isSelf, profileHandle }: Props = $props();
21
22 const auth = getAuth();
23
24 let extra = $state<VouchData[]>([]);
25 let cursors = $state(untrack(() => initialCursors));
26 let loading = $state(false);
27 let failed = $state(false);
28 // shared across pages so repeat dids only resolve once
29 let identityCache: IdentityCache | undefined;
30
31 const profileLabel = $derived(isSelf ? "you" : profileHandle);
32 // incoming and outgoing pages interleave, so the merged list re-sorts on append
33 const vouches = $derived(
34 [...initial, ...extra].sort(
35 (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
36 )
37 );
38 const exhausted = $derived(cursors.incoming === null && cursors.outgoing === null);
39
40 const loadMore = async () => {
41 if (loading || exhausted) return;
42 loading = true;
43 failed = false;
44 try {
45 const ctx = createBobbinClient({ serviceUrl: auth.bobbinUrl });
46 identityCache ??= new IdentityCache(ctx);
47 const next = await fetchVouchesPage(ctx, { did, cursors, cache: identityCache });
48 extra = [...extra, ...next.items];
49 cursors = next.cursors;
50 } catch {
51 failed = true;
52 } finally {
53 loading = false;
54 }
55 };
56</script>
57
58<Section
59 title="Vouches"
60 empty={vouches.length === 0}
61 emptyMessage="No vouches yet."
62 listClass="relative ml-5 flex flex-col gap-6 border border-transparent px-4"
63>
64 <div class="absolute inset-y-8 z-0 w-0.5 bg-border-default"></div>
65 {#each vouches as vouch (vouch.uri)}
66 <VouchCard {vouch} {profileLabel} />
67 {/each}
68 {#if !exhausted}
69 <LoadMore {loading} {failed} onclick={loadMore} />
70 {/if}
71</Section>