20 changed files with 5999 additions and 120 deletions
@ -1,60 +1,183 @@
@@ -1,60 +1,183 @@
|
||||
<script lang='ts'> |
||||
import { Avatar } from 'flowbite-svelte'; |
||||
import { type NDKUserProfile } from "@nostr-dev-kit/ndk"; |
||||
import NDK, { type NDKUserProfile } from "@nostr-dev-kit/ndk"; |
||||
import { ndkInstance } from '$lib/ndk'; |
||||
import { userBadge } from '$lib/snippets/UserSnippets.svelte'; |
||||
let { pubkey, name = null } = $props(); |
||||
|
||||
const externalProfileDestination = './events?id=' |
||||
// Component configuration types |
||||
type AvatarSize = 'sm' | 'md' | 'lg'; |
||||
|
||||
let loading = $state(true); |
||||
let anon = $state(false); |
||||
let npub = $state(''); |
||||
// Component props interface |
||||
interface $$Props { |
||||
pubkey: string; // Required: The Nostr public key of the user |
||||
name?: string | null; // Optional: Display name override |
||||
showAvatar?: boolean; // Optional: Whether to show the avatar (default: true) |
||||
avatarSize?: AvatarSize; // Optional: Size of the avatar (default: 'md') |
||||
} |
||||
|
||||
// Destructure and set default props |
||||
let { |
||||
pubkey, |
||||
name = null, |
||||
showAvatar = true, |
||||
avatarSize = 'md' as AvatarSize |
||||
} = $props(); |
||||
|
||||
console.log('[InlineProfile] Initialized with props:', { |
||||
pubkey, |
||||
name, |
||||
showAvatar, |
||||
avatarSize |
||||
}); |
||||
|
||||
// Constants |
||||
const EXTERNAL_PROFILE_DESTINATION = './events?id='; |
||||
|
||||
// Component state type definition |
||||
type ProfileState = { |
||||
loading: boolean; // Whether we're currently loading the profile |
||||
error: string | null; // Any error that occurred during loading |
||||
profile: NDKUserProfile | null; // The user's profile data |
||||
npub: string; // The user's npub (bech32 encoded pubkey) |
||||
}; |
||||
|
||||
// Initialize component state |
||||
let state = $state<ProfileState>({ |
||||
loading: true, |
||||
error: null, |
||||
profile: null, |
||||
npub: '' |
||||
}); |
||||
|
||||
// Derived values from state |
||||
const pfp = $derived(state.profile?.image); |
||||
const username = $derived(state.profile?.name); |
||||
const isAnonymous = $derived(!state.profile?.name && !name); |
||||
|
||||
// Log derived values reactively when they change |
||||
$effect(() => { |
||||
console.log('[InlineProfile] Derived values updated:', { |
||||
pfp, |
||||
username, |
||||
isAnonymous, |
||||
hasProfile: !!state.profile, |
||||
hasNpub: !!state.npub, |
||||
profileState: { |
||||
loading: state.loading, |
||||
error: state.error, |
||||
hasProfile: !!state.profile, |
||||
npub: state.npub |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
let profile = $state<NDKUserProfile | null>(null); |
||||
let pfp = $derived(profile?.image); |
||||
let username = $derived(profile?.name); |
||||
// Avatar size classes mapping |
||||
const avatarClasses: Record<AvatarSize, string> = { |
||||
sm: 'h-5 w-5', |
||||
md: 'h-7 w-7', |
||||
lg: 'h-9 w-9' |
||||
}; |
||||
|
||||
/** |
||||
* Fetches user data from NDK |
||||
* @param pubkey - The Nostr public key to fetch data for |
||||
*/ |
||||
async function fetchUserData(pubkey: string) { |
||||
let user; |
||||
user = $ndkInstance |
||||
.getUser({ pubkey: pubkey ?? undefined }); |
||||
console.log('[InlineProfile] fetchUserData called with pubkey:', pubkey); |
||||
|
||||
if (!pubkey) { |
||||
console.warn('[InlineProfile] No pubkey provided to fetchUserData'); |
||||
state.error = 'No pubkey provided'; |
||||
state.loading = false; |
||||
return; |
||||
} |
||||
|
||||
try { |
||||
console.log('[InlineProfile] Getting NDK instance'); |
||||
const ndk = $ndkInstance as NDK; |
||||
|
||||
npub = user.npub; |
||||
console.log('[InlineProfile] Creating NDK user object'); |
||||
const user = ndk.getUser({ pubkey }); |
||||
|
||||
user.fetchProfile() |
||||
.then(userProfile => { |
||||
profile = userProfile; |
||||
if (!profile?.name) anon = true; |
||||
loading = false; |
||||
console.log('[InlineProfile] Getting npub'); |
||||
state.npub = user.npub; |
||||
console.log('[InlineProfile] Got npub:', state.npub); |
||||
|
||||
console.log('[InlineProfile] Fetching user profile'); |
||||
const userProfile = await user.fetchProfile(); |
||||
console.log('[InlineProfile] Got user profile:', { |
||||
name: userProfile?.name, |
||||
displayName: userProfile?.displayName, |
||||
nip05: userProfile?.nip05, |
||||
hasImage: !!userProfile?.image |
||||
}); |
||||
|
||||
state.profile = userProfile; |
||||
state.loading = false; |
||||
} catch (error) { |
||||
console.error('[InlineProfile] Error fetching user data:', error); |
||||
state.error = error instanceof Error ? error.message : 'Failed to fetch profile'; |
||||
state.loading = false; |
||||
} |
||||
} |
||||
|
||||
// Fetch data when component mounts |
||||
/** |
||||
* Shortens an npub string for display |
||||
* @param long - The npub string to shorten |
||||
* @returns Shortened npub string |
||||
*/ |
||||
function shortenNpub(long: string | undefined): string { |
||||
if (!long) return ''; |
||||
const shortened = `${long.slice(0, 8)}…${long.slice(-4)}`; |
||||
console.log('[InlineProfile] Shortened npub:', { original: long, shortened }); |
||||
return shortened; |
||||
} |
||||
|
||||
// Effect to fetch user data when pubkey changes |
||||
$effect(() => { |
||||
console.log('[InlineProfile] Effect triggered, pubkey:', pubkey); |
||||
if (pubkey) { |
||||
fetchUserData(pubkey); |
||||
} else { |
||||
console.warn('[InlineProfile] No pubkey available for effect'); |
||||
} |
||||
}); |
||||
|
||||
function shortenNpub(long: string|undefined) { |
||||
if (!long) return ''; |
||||
return long.slice(0, 8) + '…' + long.slice(-4); |
||||
} |
||||
</script> |
||||
|
||||
{#if loading} |
||||
<!-- Component Template --> |
||||
{#if state.loading} |
||||
<!-- Loading state --> |
||||
<span class="animate-pulse" title="Loading profile..."> |
||||
{name ?? '…'} |
||||
{:else if anon } |
||||
{@render userBadge(npub, username)} |
||||
{:else if npub } |
||||
<a href={externalProfileDestination + npub} title={name ?? username}> |
||||
<Avatar rounded |
||||
class='h-7 w-7 mx-1 cursor-pointer inline bg-transparent' |
||||
</span> |
||||
{:else if state.error} |
||||
<!-- Error state --> |
||||
<span class="text-red-500" title={state.error}> |
||||
{name ?? shortenNpub(pubkey)} |
||||
</span> |
||||
{:else if isAnonymous} |
||||
<!-- Anonymous user state --> |
||||
{@render userBadge(pubkey, name)} |
||||
{:else if state.npub} |
||||
<!-- Authenticated user with profile --> |
||||
<a |
||||
href={EXTERNAL_PROFILE_DESTINATION + state.npub} |
||||
title={name ?? username} |
||||
class="inline-flex items-center hover:opacity-80 transition-opacity" |
||||
> |
||||
{#if showAvatar} |
||||
<Avatar |
||||
rounded |
||||
class={`${avatarClasses[avatarSize]} mx-1 cursor-pointer inline bg-transparent`} |
||||
src={pfp} |
||||
alt={username} /> |
||||
{@render userBadge(npub, username)} |
||||
alt={username ?? 'User avatar'} |
||||
/> |
||||
{/if} |
||||
{@render userBadge(pubkey, name)} |
||||
</a> |
||||
{:else} |
||||
{name ?? pubkey} |
||||
<!-- Fallback state --> |
||||
<span title="No profile data available"> |
||||
{name ?? shortenNpub(pubkey)} |
||||
</span> |
||||
{/if} |
||||
@ -1,15 +1,19 @@
@@ -1,15 +1,19 @@
|
||||
<script module lang='ts'> |
||||
import { createProfileLink, createProfileLinkWithVerification } from '$lib/utils/nostrUtils'; |
||||
import { createProfileLink, createProfileLinkWithVerification, toNpub } from '$lib/utils/nostrUtils'; |
||||
|
||||
export { userBadge }; |
||||
</script> |
||||
|
||||
{#snippet userBadge(identifier: string, displayText: string | undefined)} |
||||
{#await createProfileLinkWithVerification(identifier, displayText)} |
||||
{@html createProfileLink(identifier, displayText)} |
||||
{#if toNpub(identifier)} |
||||
{#await createProfileLinkWithVerification(toNpub(identifier) as string, displayText)} |
||||
{@html createProfileLink(toNpub(identifier) as string, displayText)} |
||||
{:then html} |
||||
{@html html} |
||||
{:catch} |
||||
{@html createProfileLink(identifier, displayText)} |
||||
{@html createProfileLink(toNpub(identifier) as string, displayText)} |
||||
{/await} |
||||
{:else} |
||||
{displayText ?? ''} |
||||
{/if} |
||||
{/snippet} |
||||
|
||||
Loading…
Reference in new issue