You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
173 lines
4.5 KiB
173 lines
4.5 KiB
import { Skeleton } from '@/components/ui/skeleton' |
|
import { useFetchProfile } from '@/hooks' |
|
import { toProfile } from '@/lib/link' |
|
import { formatPubkey, userIdToPubkey, pubkeyToNpub, formatNpub } from '@/lib/pubkey' |
|
import { cn } from '@/lib/utils' |
|
import { useSmartProfileNavigationOptional } from '@/PageManager' |
|
import { useMemo } from 'react' |
|
|
|
export default function Username({ |
|
userId, |
|
showAt = false, |
|
className, |
|
skeletonClassName, |
|
withoutSkeleton = false, |
|
style, |
|
onNavigate |
|
}: { |
|
userId: string |
|
showAt?: boolean |
|
className?: string |
|
skeletonClassName?: string |
|
withoutSkeleton?: boolean |
|
style?: React.CSSProperties |
|
onNavigate?: () => void |
|
}) { |
|
const { profile, isFetching } = useFetchProfile(userId) |
|
const { navigateToProfile } = useSmartProfileNavigationOptional() |
|
|
|
// Get pubkey from userId (works even if profile isn't loaded) |
|
const pubkey = useMemo(() => { |
|
if (profile?.pubkey) return profile.pubkey |
|
return userIdToPubkey(userId) || '' |
|
}, [userId, profile?.pubkey]) |
|
|
|
// Never block on profile fetch when we can already show npub/hex fallback (feeds batch-fetch profiles). |
|
const canShowWithoutProfile = Boolean(pubkey) |
|
if (isFetching && !withoutSkeleton && !canShowWithoutProfile) { |
|
return ( |
|
<div className="py-1"> |
|
<Skeleton className={cn('w-16', skeletonClassName)} /> |
|
</div> |
|
) |
|
} |
|
|
|
// If we have a profile, show the username |
|
if (profile) { |
|
const { username, pubkey: profilePubkey } = profile |
|
return ( |
|
<span |
|
data-username |
|
className={cn('truncate hover:underline cursor-pointer', className)} |
|
style={{ verticalAlign: 'baseline', ...style }} |
|
onClick={(e) => { |
|
e.stopPropagation() |
|
onNavigate?.() |
|
navigateToProfile(toProfile(profilePubkey)) |
|
}} |
|
> |
|
{showAt && '@'} |
|
{username} |
|
</span> |
|
) |
|
} |
|
|
|
// Fallback: show formatted npub (bech32) if we have a pubkey (even if profile fetch failed) |
|
if (pubkey) { |
|
// Convert to npub (bech32) format for display |
|
const npub = pubkeyToNpub(pubkey) |
|
const displayName = npub ? formatNpub(npub) : formatPubkey(pubkey) |
|
|
|
return ( |
|
<span |
|
data-username |
|
className={cn('truncate hover:underline cursor-pointer', className)} |
|
style={{ verticalAlign: 'baseline', ...style }} |
|
onClick={(e) => { |
|
e.stopPropagation() |
|
onNavigate?.() |
|
navigateToProfile(toProfile(pubkey)) |
|
}} |
|
> |
|
{showAt && '@'} |
|
{displayName} |
|
</span> |
|
) |
|
} |
|
|
|
// No pubkey available - return null or skeleton based on withoutSkeleton |
|
if (!withoutSkeleton) { |
|
return ( |
|
<div className="py-1"> |
|
<Skeleton className={cn('w-16', skeletonClassName)} /> |
|
</div> |
|
) |
|
} |
|
|
|
return null |
|
} |
|
|
|
export function SimpleUsername({ |
|
userId, |
|
showAt = false, |
|
className, |
|
skeletonClassName, |
|
withoutSkeleton = false, |
|
style |
|
}: { |
|
userId: string |
|
showAt?: boolean |
|
className?: string |
|
skeletonClassName?: string |
|
withoutSkeleton?: boolean |
|
style?: React.CSSProperties |
|
}) { |
|
const { profile, isFetching } = useFetchProfile(userId) |
|
|
|
// Get pubkey from userId (works even if profile isn't loaded) |
|
const pubkey = useMemo(() => { |
|
if (profile?.pubkey) return profile.pubkey |
|
return userIdToPubkey(userId) || '' |
|
}, [userId, profile?.pubkey]) |
|
|
|
const canShowWithoutProfile = Boolean(pubkey) |
|
if (isFetching && !withoutSkeleton && !canShowWithoutProfile) { |
|
return ( |
|
<div className="py-1"> |
|
<Skeleton className={cn('w-16', skeletonClassName)} /> |
|
</div> |
|
) |
|
} |
|
|
|
// If we have a profile, show the username |
|
if (profile) { |
|
const { username } = profile |
|
return ( |
|
<span |
|
className={cn('truncate', className)} |
|
style={{ verticalAlign: 'baseline', ...style }} |
|
> |
|
{showAt && '@'} |
|
{username} |
|
</span> |
|
) |
|
} |
|
|
|
// Fallback: show formatted npub (bech32) if we have a pubkey (even if profile fetch failed) |
|
if (pubkey) { |
|
// Convert to npub (bech32) format for display |
|
const npub = pubkeyToNpub(pubkey) |
|
const displayName = npub ? formatNpub(npub) : formatPubkey(pubkey) |
|
|
|
return ( |
|
<span |
|
className={cn('truncate', className)} |
|
style={{ verticalAlign: 'baseline', ...style }} |
|
> |
|
{showAt && '@'} |
|
{displayName} |
|
</span> |
|
) |
|
} |
|
|
|
// No pubkey available - return null or skeleton based on withoutSkeleton |
|
if (!withoutSkeleton) { |
|
return ( |
|
<div className="py-1"> |
|
<Skeleton className={cn('w-16', skeletonClassName)} /> |
|
</div> |
|
) |
|
} |
|
|
|
return null |
|
} |