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
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>