Monorepo for Tangled
0

Configure Feed

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

web: match vouch card styles

Signed-off-by: oppiliappan <me@oppi.li>

oppiliappan (Jul 15, 2026, 11:06 AM +0100) 4e18ed88 04d93c9e

+56 -33
+1 -1
web/src/routes/[handle]/+page.svelte
··· 22 22 {:else if data.tab === "following"} 23 23 <PeopleTab people={data.people} title="Following" emptyMessage="Not following anyone yet." /> 24 24 {:else if data.tab === "vouches"} 25 - <VouchTab vouches={data.vouches} /> 25 + <VouchTab vouches={data.vouches} isSelf={data.isSelf} profileHandle={data.profileHandle} /> 26 26 {/if}
+44 -29
web/src/lib/components/profile/VouchCard.svelte
··· 1 1 <script lang="ts"> 2 2 import { resolve } from "$app/paths"; 3 - import Avatar from "$lib/components/ui/Avatar.svelte"; 4 - import ShieldCheck from "$icon/shield-check"; 5 - import ShieldAlert from "$icon/shield-alert"; 3 + import ArrowUpRight from "$icon/arrow-up-right"; 4 + import ArrowDownLeft from "$icon/arrow-down-left"; 6 5 import { relativeTime } from "$lib/format"; 7 6 import type { VouchData } from "./types"; 8 7 9 - let { vouch }: { vouch: VouchData } = $props(); 8 + let { vouch, profileLabel }: { vouch: VouchData; profileLabel: string } = $props(); 9 + 10 10 const denounce = $derived(vouch.kind === "denounce"); 11 + const outgoing = $derived(vouch.direction === "outgoing"); 11 12 </script> 12 13 13 - <article class="rounded border border-border-default bg-background-default px-6 py-4 shadow-sm"> 14 - <div class="flex items-center gap-4"> 15 - <Avatar src={vouch.avatar} handle={vouch.handle} size="size-12" /> 16 - <div class="min-w-0 flex-grow"> 17 - <a 18 - href={resolve(`/${vouch.handle}` as "/")} 19 - class="block truncate font-medium text-foreground-default" 20 - > 21 - {vouch.handle} 22 - </a> 23 - <span 24 - class={`flex items-center gap-1 text-sm ${denounce ? "text-foreground-danger" : "text-foreground-success"}`} 25 - > 26 - {#if denounce} 27 - <ShieldAlert class="size-3.5" aria-hidden="true" />denounced 28 - {:else} 29 - <ShieldCheck class="size-3.5" aria-hidden="true" />vouched 30 - {/if} 31 - <span class="text-foreground-subtle">&middot; {relativeTime(vouch.createdAt)}</span> 32 - </span> 33 - </div> 14 + <div class="relative z-10 -ml-4 flex items-start gap-3"> 15 + <div 16 + class={`flex size-8 shrink-0 items-center justify-center rounded-full border border-border-default ${denounce ? "bg-background-danger-subtle" : "bg-background-success-subtle"}`} 17 + > 18 + {#if outgoing} 19 + <ArrowUpRight 20 + class={`size-5 ${denounce ? "text-foreground-danger" : "text-foreground-success"}`} 21 + aria-hidden="true" 22 + /> 23 + {:else} 24 + <ArrowDownLeft 25 + class={`size-5 ${denounce ? "text-foreground-danger" : "text-foreground-success"}`} 26 + aria-hidden="true" 27 + /> 28 + {/if} 34 29 </div> 35 30 36 - {#if vouch.reason} 37 - <p class="mt-3 text-sm text-foreground-muted">{vouch.reason}</p> 38 - {/if} 39 - </article> 31 + <div class="mt-1 flex flex-col gap-1"> 32 + <div class="flex flex-wrap items-center gap-1 text-foreground-default"> 33 + {#if outgoing} 34 + <span class="font-medium">{profileLabel}</span> 35 + {:else} 36 + <a href={resolve(`/${vouch.handle}` as "/")} class="font-medium hover:underline" 37 + >{vouch.handle}</a 38 + > 39 + {/if} 40 + <span class="text-foreground-subtle">{denounce ? "denounced" : "vouched for"}</span> 41 + {#if outgoing} 42 + <a href={resolve(`/${vouch.handle}` as "/")} class="font-medium hover:underline" 43 + >{vouch.handle}</a 44 + > 45 + {:else} 46 + <span class="font-medium">{profileLabel}</span> 47 + {/if} 48 + <span class="text-sm text-foreground-placeholder">{relativeTime(vouch.createdAt)}</span> 49 + </div> 50 + {#if vouch.reason} 51 + <p class="text-sm text-foreground-subtle">{vouch.reason}</p> 52 + {/if} 53 + </div> 54 + </div>
+1
web/src/lib/components/profile/types.ts
··· 59 59 handle: string; 60 60 avatar?: string; 61 61 kind: "vouch" | "denounce"; 62 + direction: "incoming" | "outgoing"; 62 63 reason?: string; 63 64 createdAt: string; 64 65 }
+10 -3
web/src/lib/components/profile/tabs/VouchTab.svelte
··· 3 3 import EmptyState from "../EmptyState.svelte"; 4 4 import type { VouchData } from "../types"; 5 5 6 - let { vouches }: { vouches: VouchData[] } = $props(); 6 + let { 7 + vouches, 8 + isSelf, 9 + profileHandle 10 + }: { vouches: VouchData[]; isSelf: boolean; profileHandle: string } = $props(); 11 + 12 + const profileLabel = $derived(isSelf ? "you" : profileHandle); 7 13 </script> 8 14 9 15 <section> ··· 11 17 {#if vouches.length === 0} 12 18 <EmptyState message="No vouches yet." /> 13 19 {:else} 14 - <div class="flex flex-col gap-4"> 20 + <div class="relative ml-5 flex flex-col gap-6 border border-transparent px-4"> 21 + <div class="absolute bottom-8 top-8 z-0 w-0.5 bg-border-default"></div> 15 22 {#each vouches as vouch (vouch.uri)} 16 - <VouchCard {vouch} /> 23 + <VouchCard {vouch} {profileLabel} /> 17 24 {/each} 18 25 </div> 19 26 {/if}