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 / ProfileCard.svelte
4.3 kB 142 lines
1<script lang="ts"> 2 import FollowButton from "./FollowButton.svelte"; 3 import ProfileEditForm from "./ProfileEditForm.svelte"; 4 import FollowerFollowing from "./FollowerFollowing.svelte"; 5 import MapPin from "$icon/map-pin"; 6 import Link from "$icon/link"; 7 import Pencil from "$icon/pencil"; 8 import Rss from "$icon/rss"; 9 import Avatar from "$lib/components/ui/Avatar.svelte"; 10 import Button from "$lib/components/ui/Button.svelte"; 11 import { getAuth } from "$lib/auth.svelte"; 12 import type { ProfileRecord } from "$lib/api/records"; 13 14 interface Identity { 15 did: string; 16 handle: string; 17 } 18 19 interface Props { 20 identity: Identity; 21 profile: ProfileRecord | null; 22 followers: number; 23 following: number; 24 viewerFollowRkey?: string | null; 25 } 26 27 let { identity, profile, followers, following, viewerFollowRkey }: Props = $props(); 28 29 const auth = getAuth(); 30 const isSelf = $derived(auth.currentDid === identity.did); 31 32 let editing = $state(false); 33 let saved = $state<ProfileRecord | null>(null); 34 const shown = $derived(saved ?? profile); 35 36 const links = $derived((shown?.links ?? []).filter((l) => l.trim().length > 0)); 37 const blueskyUrl = $derived(shown?.bluesky ? `https://bsky.app/profile/${identity.did}` : null); 38</script> 39 40<div class="grid grid-cols-3 items-center gap-1 md:grid-cols-1"> 41 <div class="col-span-1 flex items-center justify-center"> 42 <div class="relative aspect-square w-3/4"> 43 <Avatar 44 did={identity.did} 45 handle={identity.handle} 46 size="h-full w-full" 47 class="absolute inset-0 p-2" 48 /> 49 </div> 50 </div> 51 52 <div class="col-span-2"> 53 <div class="flex flex-row flex-nowrap items-center gap-2"> 54 <h1 55 title={identity.handle} 56 class="overflow-hidden typography-heading-4 font-bold text-ellipsis whitespace-nowrap" 57 > 58 {identity.handle} 59 </h1> 60 {#if shown?.pronouns} 61 <p class="text-foreground-subtle">{shown.pronouns}</p> 62 {/if} 63 </div> 64 <div class="md:hidden"> 65 <FollowerFollowing handle={identity.handle} {followers} {following} /> 66 </div> 67 </div> 68 69 <div class="col-span-3 md:col-span-full"> 70 {#if editing} 71 <ProfileEditForm 72 profile={shown} 73 onSaved={(record) => { 74 saved = record; 75 editing = false; 76 }} 77 onCancel={() => (editing = false)} 78 /> 79 {:else} 80 <div class="space-y-2 text-sm"> 81 {#if shown?.description} 82 <p class="pb-4 typography-paragraph-large md:pb-2">{shown.description}</p> 83 {/if} 84 85 <div class="hidden md:block"> 86 <FollowerFollowing handle={identity.handle} {followers} {following} /> 87 </div> 88 89 {#if shown?.location || links.length > 0 || blueskyUrl} 90 <div 91 class="mb-2 flex max-w-full flex-col gap-2 overflow-hidden text-ellipsis whitespace-nowrap" 92 > 93 {#if shown?.location} 94 <div class="flex items-center gap-2"> 95 <span class="flex-shrink-0"><MapPin class="size-4" aria-hidden="true" /></span> 96 <span>{shown.location}</span> 97 </div> 98 {/if} 99 {#if blueskyUrl} 100 <div class="flex items-center gap-2"> 101 <span class="flex-shrink-0"><Link class="size-4" aria-hidden="true" /></span> 102 <a 103 href={blueskyUrl} 104 target="_blank" 105 rel="external noopener noreferrer" 106 class="truncate no-underline hover:underline">{identity.handle}</a 107 > 108 </div> 109 {/if} 110 {#each links as link, index (index)} 111 <div class="flex items-center gap-2"> 112 <span class="flex-shrink-0"><Link class="size-4" aria-hidden="true" /></span> 113 <a 114 href={link} 115 target="_blank" 116 rel="external nofollow me noopener noreferrer" 117 class="truncate no-underline hover:underline">{link}</a 118 > 119 </div> 120 {/each} 121 </div> 122 {/if} 123 124 <div class="my-2 flex items-center gap-2"> 125 {#if isSelf} 126 <Button variant="default" class="w-full gap-2" onclick={() => (editing = true)}> 127 <Pencil class="size-3" aria-hidden="true" /> 128 Edit 129 </Button> 130 {:else} 131 <div class="w-full"> 132 <FollowButton profileDid={identity.did} initialRkey={viewerFollowRkey} /> 133 </div> 134 {/if} 135 <Button href={`/${identity.handle}/feed.atom`} variant="default" aria-label="RSS feed"> 136 <Rss class="size-4" aria-hidden="true" /> 137 </Button> 138 </div> 139 </div> 140 {/if} 141 </div> 142</div>