mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex-taker-v2.git
synced 2026-09-21 16:52:01 -07:00
Merge pull request #76 from Permissionless-Software-Foundation/dh-seller-link
feat(sell): Added link to Seller profile
This commit is contained in:
@@ -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 (
|
||||
<div
|
||||
onClick={goToProfile}
|
||||
className='seller-profile-container'
|
||||
style={{
|
||||
padding: '4px 8px',
|
||||
backgroundColor: '#f8f9fa',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid #e9ecef',
|
||||
transition: 'all 0.2s ease',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
width: '100%'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#e9ecef'
|
||||
e.currentTarget.style.borderColor = '#dee2e6'
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#f8f9fa'
|
||||
e.currentTarget.style.borderColor = '#e9ecef'
|
||||
}}
|
||||
>
|
||||
<div className='d-flex align-items-center justify-content-center gap-1'>
|
||||
{/* Seller Label */}
|
||||
<small
|
||||
className='text-muted fw-semibold'
|
||||
style={{
|
||||
fontSize: '0.65rem',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.3px'
|
||||
}}
|
||||
>
|
||||
Seller:
|
||||
</small>
|
||||
|
||||
{/* Profile Picture or Placeholder */}
|
||||
<div
|
||||
className='d-flex align-items-center justify-content-center'
|
||||
style={{
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: '#e9ecef',
|
||||
border: '1.5px solid #dee2e6',
|
||||
overflow: 'hidden',
|
||||
flexShrink: 0,
|
||||
cursor: 'pointer',
|
||||
transition: 'transform 0.2s ease'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
goToProfile()
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = 'scale(1.1)'
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = 'scale(1)'
|
||||
}}
|
||||
>
|
||||
{isLoading
|
||||
? (
|
||||
<FontAwesomeIcon
|
||||
icon={faUser}
|
||||
style={{
|
||||
color: '#adb5bd',
|
||||
fontSize: '12px',
|
||||
opacity: 0.5
|
||||
}}
|
||||
/>
|
||||
)
|
||||
: (profile?.picture && !imageError)
|
||||
? (
|
||||
<img
|
||||
src={profile.picture}
|
||||
alt='Seller profile'
|
||||
className='w-100 h-100'
|
||||
style={{ objectFit: 'cover' }}
|
||||
onError={() => handleImageError('picture')}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<FontAwesomeIcon
|
||||
icon={faUser}
|
||||
style={{
|
||||
color: '#6c757d',
|
||||
fontSize: '12px'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Seller Name */}
|
||||
<span
|
||||
onClick={(e) => {
|
||||
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()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SellerProfile
|
||||
@@ -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 (
|
||||
<>
|
||||
<Col xs={12} sm={6} lg={4} style={{ padding: '25px' }}>
|
||||
<Card>
|
||||
<Card.Body style={{ textAlign: 'center' }}>
|
||||
<Card className='shadow-sm'>
|
||||
<Card.Body style={{ textAlign: 'center', padding: '10px' }}>
|
||||
{/* Seller Profile Section */}
|
||||
<Row className='mb-2'>
|
||||
<Col className='text-center mb-2'>
|
||||
<SellerProfile npub={token.makerNpub} appData={appData} />
|
||||
</Col>
|
||||
</Row>
|
||||
{/** If the icon is loaded, display it */
|
||||
icon && (
|
||||
<Card.Img
|
||||
@@ -50,7 +54,6 @@ function TokenCard (props) {
|
||||
<Card.Title style={{ textAlign: 'center', marginTop: '10px' }}>
|
||||
<h4>{token.ticker}</h4>
|
||||
</Card.Title>
|
||||
|
||||
<Container>
|
||||
<Row>
|
||||
<Col>
|
||||
|
||||
Reference in New Issue
Block a user