From fa3c71f0a10dd922891e1846108cfa133b414867 Mon Sep 17 00:00:00 2001 From: Daniel Gonzalez Date: Thu, 13 Nov 2025 17:07:41 -0400 Subject: [PATCH] feat(sell): Added link to Seller profile --- .../app-body/nfts-for-sale/seller-profile.js | 184 ++++++++++++++++++ .../app-body/nfts-for-sale/token-card.js | 15 +- 2 files changed, 193 insertions(+), 6 deletions(-) create mode 100644 src/components/app-body/nfts-for-sale/seller-profile.js diff --git a/src/components/app-body/nfts-for-sale/seller-profile.js b/src/components/app-body/nfts-for-sale/seller-profile.js new file mode 100644 index 0000000..5505755 --- /dev/null +++ b/src/components/app-body/nfts-for-sale/seller-profile.js @@ -0,0 +1,184 @@ +/* + This component displays the seller's profile information for an NFT listing. +*/ + +// Global npm libraries +import React, { useState, useEffect } from 'react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faUser } from '@fortawesome/free-solid-svg-icons' + +function SellerProfile (props) { + const { npub, appData } = props + const [profile, setProfile] = useState(null) + const [imageError, setImageError] = useState(false) + const [isLoading, setIsLoading] = useState(true) + + const { nostrQueries } = appData + + // Get profile if npub is provided. + useEffect(() => { + const start = async () => { + setIsLoading(true) + try { + const pubKey = nostrQueries.npubToHex(npub) + const profile = await nostrQueries.getProfile(pubKey) + setProfile(profile) + } catch (error) { + console.warn('Error fetching seller profile:', error) + } finally { + setIsLoading(false) + } + } + if (npub) { + start() + } else { + setIsLoading(false) + } + }, [npub, nostrQueries]) + + // handle img url errors + const handleImageError = (type) => { + setImageError(true) + } + + // go to profile + const goToProfile = () => { + const profileUrl = `${window.location.origin}/profile/${npub}#single-view` + window.open(profileUrl, '_blank') + } + + // Get display name - use profile name, npub short form, or "Anonymous User" + const getDisplayName = () => { + if (profile?.name) return profile.name + if (npub) { + return npub.slice(0, 8) + '...' + npub.slice(-6) + } + return 'Anonymous User' + } + + return ( +
{ + e.currentTarget.style.backgroundColor = '#e9ecef' + e.currentTarget.style.borderColor = '#dee2e6' + }} + onMouseLeave={(e) => { + e.currentTarget.style.backgroundColor = '#f8f9fa' + e.currentTarget.style.borderColor = '#e9ecef' + }} + > +
+ {/* Seller Label */} + + Seller: + + + {/* Profile Picture or Placeholder */} +
{ + e.stopPropagation() + goToProfile() + }} + onMouseEnter={(e) => { + e.currentTarget.style.transform = 'scale(1.1)' + }} + onMouseLeave={(e) => { + e.currentTarget.style.transform = 'scale(1)' + }} + > + {isLoading + ? ( + + ) + : (profile?.picture && !imageError) + ? ( + Seller profile handleImageError('picture')} + /> + ) + : ( + + )} +
+ + {/* Seller Name */} + { + e.stopPropagation() + goToProfile() + }} + className='cursor-pointer fw-medium' + style={{ + color: '#495057', + fontSize: '0.8rem', + transition: 'color 0.2s ease', + maxWidth: '120px', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap' + }} + onMouseEnter={(e) => { + e.currentTarget.style.color = '#007bff' + }} + onMouseLeave={(e) => { + e.currentTarget.style.color = '#495057' + }} + title={profile?.name || npub} + > + {getDisplayName()} + +
+
+ ) +} + +export default SellerProfile diff --git a/src/components/app-body/nfts-for-sale/token-card.js b/src/components/app-body/nfts-for-sale/token-card.js index 3c7b80a..e562354 100644 --- a/src/components/app-body/nfts-for-sale/token-card.js +++ b/src/components/app-body/nfts-for-sale/token-card.js @@ -10,14 +10,12 @@ import Jdenticon from '@chris.troutner/react-jdenticon' // Local libraries import InfoButton from './info-button' import BuyButton from './buy-button' - +import SellerProfile from './seller-profile' function TokenCard (props) { const { token, appData, handleRefresh, hideBuyBtn } = props const [icon, setIcon] = useState(token.icon) const [tokenData, setTokenData] = useState(token.tokenData) - console.log('TokenCard() appData: ', appData) - // Update icon state every token.icon and token.tokenData changes useEffect(() => { console.log('setting icon') @@ -28,8 +26,14 @@ function TokenCard (props) { return ( <> - - + + + {/* Seller Profile Section */} + + + + + {/** If the icon is loaded, display it */ icon && (

{token.ticker}

-