This repository has no description
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 did={identity.did} handle={identity.handle} size="full" class="absolute inset-0" />
44 </div>
45 </div>
46
47 <div class="col-span-2">
48 <div class="flex flex-row flex-nowrap items-center gap-2">
49 <h1 title={identity.handle} class="truncate typography-heading-4 font-bold">
50 {identity.handle}
51 </h1>
52 {#if shown?.pronouns}
53 <p class="text-foreground-subtle">{shown.pronouns}</p>
54 {/if}
55 </div>
56 <div class="md:hidden">
57 <FollowerFollowing handle={identity.handle} {followers} {following} />
58 </div>
59 </div>
60
61 <div class="col-span-3 md:col-span-full">
62 {#if editing}
63 <ProfileEditForm
64 profile={shown}
65 onSaved={(record) => {
66 saved = record;
67 editing = false;
68 }}
69 onCancel={() => (editing = false)}
70 />
71 {:else}
72 <div class="space-y-2 typography-paragraph-small">
73 {#if shown?.description}
74 <p class="pb-4 typography-paragraph-large md:pb-2">{shown.description}</p>
75 {/if}
76
77 <div class="hidden md:block">
78 <FollowerFollowing handle={identity.handle} {followers} {following} />
79 </div>
80
81 {#if shown?.location || links.length > 0 || blueskyUrl}
82 <div class="mb-2 flex max-w-full flex-col gap-2 truncate">
83 {#if shown?.location}
84 <div class="flex items-center gap-2">
85 <span class="shrink-0"><MapPin class="size-4" aria-hidden="true" /></span>
86 <span>{shown.location}</span>
87 </div>
88 {/if}
89 {#if blueskyUrl}
90 <div class="flex items-center gap-2">
91 <span class="shrink-0"><Link class="size-4" aria-hidden="true" /></span>
92 <a
93 href={blueskyUrl}
94 target="_blank"
95 rel="external noopener noreferrer"
96 class="truncate no-underline hover:underline">{identity.handle}</a
97 >
98 </div>
99 {/if}
100 {#each links as link, index (index)}
101 <div class="flex items-center gap-2">
102 <span class="shrink-0"><Link class="size-4" aria-hidden="true" /></span>
103 <a
104 href={link}
105 target="_blank"
106 rel="external nofollow me noopener noreferrer"
107 class="truncate no-underline hover:underline">{link}</a
108 >
109 </div>
110 {/each}
111 </div>
112 {/if}
113
114 <div class="my-2 flex items-center gap-2">
115 {#if isSelf}
116 <Button variant="default" class="w-full gap-2" onclick={() => (editing = true)}>
117 <Pencil class="size-3" aria-hidden="true" />
118 Edit
119 </Button>
120 {:else}
121 <div class="w-full">
122 <FollowButton profileDid={identity.did} initialRkey={viewerFollowRkey} />
123 </div>
124 {/if}
125 <Button href={`/${identity.handle}/feed.atom`} variant="default" aria-label="RSS feed">
126 <Rss class="size-4" aria-hidden="true" />
127 </Button>
128 </div>
129 </div>
130 {/if}
131 </div>
132</div>