From e55da45f791cf46e54c2712a24e58e0db91eb066 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sat, 1 Nov 2025 21:29:02 +0100 Subject: [PATCH] remove profile pics from fallback cards --- src/components/UserAvatar/index.tsx | 119 ++++++++++++++++++---------- src/components/WebPreview/index.tsx | 7 -- src/lib/pubkey.ts | 13 ++- 3 files changed, 88 insertions(+), 51 deletions(-) diff --git a/src/components/UserAvatar/index.tsx b/src/components/UserAvatar/index.tsx index 47937aa..2c0ee78 100644 --- a/src/components/UserAvatar/index.tsx +++ b/src/components/UserAvatar/index.tsx @@ -1,11 +1,9 @@ -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { Skeleton } from '@/components/ui/skeleton' import { useFetchProfile } from '@/hooks' -import { generateImageByPubkey } from '@/lib/pubkey' +import { generateImageByPubkey, userIdToPubkey } from '@/lib/pubkey' import { toProfile } from '@/lib/link' import { cn } from '@/lib/utils' import { useSmartProfileNavigation } from '@/PageManager' -import { nip19 } from 'nostr-tools' import { useMemo, useState, useEffect } from 'react' const UserAvatarSizeCnMap = { @@ -30,33 +28,96 @@ export default function UserAvatar({ }) { const { profile } = useFetchProfile(userId) const { navigateToProfile } = useSmartProfileNavigation() + + // Extract pubkey from userId if it's npub/nprofile format + const pubkey = useMemo(() => { + if (!userId) return '' + const decodedPubkey = userIdToPubkey(userId) + return decodedPubkey || profile?.pubkey || '' + }, [userId, profile?.pubkey]) + const defaultAvatar = useMemo( - () => (profile?.pubkey ? generateImageByPubkey(profile.pubkey) : ''), - [profile] + () => (pubkey ? generateImageByPubkey(pubkey) : ''), + [pubkey] ) + + // Use profile avatar if available, otherwise use default avatar + const avatarSrc = profile?.avatar || defaultAvatar || '' - if (!profile) { + // All hooks must be called before any early returns + const [imgError, setImgError] = useState(false) + const [currentSrc, setCurrentSrc] = useState(avatarSrc) + const [imageLoaded, setImageLoaded] = useState(false) + + // Reset error state when src changes + useEffect(() => { + setImgError(false) + setImageLoaded(false) + setCurrentSrc(avatarSrc) + }, [avatarSrc]) + + const handleImageError = () => { + if (profile?.avatar && defaultAvatar && currentSrc === profile.avatar) { + // Try default avatar if profile avatar fails + setCurrentSrc(defaultAvatar) + setImgError(false) + } else { + // Both failed, show placeholder + setImgError(true) + setImageLoaded(true) + } + } + + const handleImageLoad = () => { + setImageLoaded(true) + setImgError(false) + } + + // Use pubkey from decoded userId if profile isn't loaded yet + const displayPubkey = profile?.pubkey || pubkey || '' + + // If we have a pubkey (from decoding npub/nprofile or profile), show avatar even without profile + // Otherwise show skeleton while loading + if (!profile && !pubkey) { return ( ) } - const { avatar, pubkey } = profile - + // Render image directly instead of using Radix UI Avatar for better reliability return ( - { e.stopPropagation() - navigateToProfile(toProfile(pubkey)) + navigateToProfile(toProfile(displayPubkey)) }} > - - - {pubkey} - - + {!imgError && currentSrc ? ( + <> + {!imageLoaded && ( +
+ )} + {displayPubkey} + + ) : ( + // Show initials or placeholder when image fails +
+ {displayPubkey ? displayPubkey.slice(0, 2).toUpperCase() : ''} +
+ )} +
) } @@ -73,30 +134,8 @@ export function SimpleUserAvatar({ // Always generate default avatar from userId/pubkey, even if profile isn't loaded yet const pubkey = useMemo(() => { if (!userId) return '' - try { - // Try to extract pubkey from userId (handles npub, nprofile, or hex pubkey) - if (userId.length === 64 && /^[0-9a-f]+$/i.test(userId)) { - return userId - } - // Try to decode npub/nprofile to get pubkey - try { - const decoded = nip19.decode(userId) - if (decoded.type === 'npub') { - return decoded.data - } else if (decoded.type === 'nprofile') { - return decoded.data.pubkey - } - } catch { - // Not a valid npub/nprofile, continue - } - // Use profile pubkey if available - if (profile?.pubkey) { - return profile.pubkey - } - return '' - } catch { - return '' - } + const decodedPubkey = userIdToPubkey(userId) + return decodedPubkey || profile?.pubkey || '' }, [userId, profile?.pubkey]) const defaultAvatar = useMemo( diff --git a/src/components/WebPreview/index.tsx b/src/components/WebPreview/index.tsx index 7224d3c..2cccec8 100644 --- a/src/components/WebPreview/index.tsx +++ b/src/components/WebPreview/index.tsx @@ -10,7 +10,6 @@ import { ExternalLink } from 'lucide-react' import { nip19, kinds } from 'nostr-tools' import { useMemo } from 'react' import Image from '../Image' -import { SimpleUserAvatar } from '../UserAvatar' import Username from '../Username' // Helper function to get event type name @@ -168,7 +167,6 @@ export default function WebPreview({ url, className }: { url: string; className?
{fetchedEvent ? ( <> - {eventTypeName} @@ -211,11 +209,6 @@ export default function WebPreview({ url, className }: { url: string; className? window.open(url, '_blank') }} > - {fetchedProfile ? ( - - ) : ( -
- )}
{fetchedProfile ? ( diff --git a/src/lib/pubkey.ts b/src/lib/pubkey.ts index 309bda3..a225166 100644 --- a/src/lib/pubkey.ts +++ b/src/lib/pubkey.ts @@ -59,12 +59,17 @@ export function isValidPubkey(pubkey: string) { } const pubkeyImageCache = new LRUCache({ max: 1000 }) + +// Version identifier to force cache invalidation when algorithm changes +const CACHE_VERSION = 'v2' + export function generateImageByPubkey(pubkey: string): string { - if (pubkeyImageCache.has(pubkey)) { - return pubkeyImageCache.get(pubkey)! + const cacheKey = `${CACHE_VERSION}:${pubkey}` + if (pubkeyImageCache.has(cacheKey)) { + return pubkeyImageCache.get(cacheKey)! } - const paddedPubkey = pubkey.padEnd(2, '0') + const paddedPubkey = pubkey.padEnd(66, '0') // Split into 3 parts for colors and the rest for control points const colors: string[] = [] @@ -104,7 +109,7 @@ export function generateImageByPubkey(pubkey: string): string { ` const imageData = `data:image/svg+xml;base64,${btoa(image)}` - pubkeyImageCache.set(pubkey, imageData) + pubkeyImageCache.set(cacheKey, imageData) return imageData }