This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / lib / components / profile / tabs / VouchTab.svelte
2.2 kB 71 lines
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>