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 #53 from Permissionless-Software-Foundation/dh-profile-todos
feat(nostr): Completed profile feed TODOs
This commit is contained in:
@@ -17,7 +17,7 @@ function Profile (props) {
|
||||
return (
|
||||
<>
|
||||
<Container>
|
||||
<ProfileRead {...props} setProfile={setProfile} npub={npub} />
|
||||
<ProfileRead {...props} onProfileRead={setProfile} npub={npub} />
|
||||
<PublicRead {...props} profile={profile} npub={npub} />
|
||||
<SlpTokensDisplay {...props} npub={npub} />
|
||||
<NFTForSale {...props} npub={npub} />
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
/**
|
||||
* Component to read nostr information kind 0 (normal posts)
|
||||
*
|
||||
* TODO:
|
||||
* - Currently feeds are retrieved from only one relay. It should retrieve Kind
|
||||
* 0 posts from all relays. It should then remove any duplicate entries.
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState, useCallback } from 'react'
|
||||
import { Container, Button } from 'react-bootstrap'
|
||||
import CopyOnClick from '../../bch-wallet/copy-on-click.js'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
@@ -21,47 +18,66 @@ import { RelayPool } from 'nostr'
|
||||
|
||||
function ProfileRead (props) {
|
||||
const { npub } = props
|
||||
const { setProfile } = props
|
||||
const [post, setPost] = useState({})
|
||||
const { onProfileRead } = props
|
||||
const [profile, setProfile] = useState({})
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const [imageError, setImageError] = useState({ picture: false, banner: false })
|
||||
|
||||
// Load profile from nostr relays
|
||||
// It uses multiple relays. It will exit after the first successful retrieval
|
||||
// from any relay. If one relay fails, it will move on to the next one.
|
||||
const fetchProfile = useCallback(async (pubKey) => {
|
||||
console.log('fetching profile for pubkey ', pubKey)
|
||||
// Looking for the profile in each relay sequentially
|
||||
for (let i = 0; i < config.nostrRelays.length; i++) {
|
||||
const profile = await new Promise((resolve) => {
|
||||
const relay = config.nostrRelays[i]
|
||||
// const pool = RelayPool(config.nostrRelays)
|
||||
const pool = RelayPool([relay])
|
||||
pool.on('open', relay => {
|
||||
relay.subscribe('subid', { limit: 5, kinds: [0], authors: [pubKey] })
|
||||
})
|
||||
|
||||
pool.on('eose', relay => {
|
||||
console.log('Closing Relay')
|
||||
relay.close()
|
||||
resolve(false)
|
||||
})
|
||||
|
||||
pool.on('event', (relay, subId, ev) => {
|
||||
try {
|
||||
const profile = JSON.parse(ev.content)
|
||||
// console.log('profile', profile)
|
||||
console.log(`Profile found for ${pubKey} at ${relay.url}`)
|
||||
resolve(profile)
|
||||
} catch (error) {
|
||||
resolve(false)
|
||||
}
|
||||
relay.close()
|
||||
})
|
||||
})
|
||||
// Stop looking for profile if found
|
||||
if (profile) {
|
||||
return profile
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const start = () => {
|
||||
const start = async () => {
|
||||
const pubHexData = nip19.decode(npub)
|
||||
const pubHex = pubHexData.data
|
||||
|
||||
// const pool = RelayPool(config.nostrRelays)
|
||||
const pool = new RelayPool([config.nostrRelay])
|
||||
pool.on('open', relay => {
|
||||
relay.subscribe('subid', { limit: 5, kinds: [0], authors: [pubHex] })
|
||||
setLoaded(true)
|
||||
})
|
||||
|
||||
pool.on('eose', relay => {
|
||||
console.log('Closing Relay')
|
||||
relay.close()
|
||||
})
|
||||
|
||||
pool.on('event', (relay, subId, ev) => {
|
||||
console.log('Received event:', ev)
|
||||
try {
|
||||
const profile = JSON.parse(ev.content)
|
||||
console.log('Profile:', profile)
|
||||
setPost(profile)
|
||||
setProfile(profile)
|
||||
} catch (error) {
|
||||
console.error('Error parsing profile:', error)
|
||||
}
|
||||
})
|
||||
|
||||
const profile = await fetchProfile(pubHex)
|
||||
onProfileRead(profile)
|
||||
setProfile(profile)
|
||||
setLoaded(true)
|
||||
}
|
||||
|
||||
if (!loaded && npub) {
|
||||
start()
|
||||
}
|
||||
}, [loaded, setProfile, npub])
|
||||
}, [loaded, onProfileRead, npub, fetchProfile])
|
||||
|
||||
const handleImageError = (type) => {
|
||||
setImageError(prev => ({ ...prev, [type]: true }))
|
||||
@@ -74,10 +90,10 @@ function ProfileRead (props) {
|
||||
return (
|
||||
<Container>
|
||||
{/* Banner Section */}
|
||||
{post.banner && !imageError.banner && (
|
||||
{profile.banner && !imageError.banner && (
|
||||
<div className='position-relative'>
|
||||
<img
|
||||
src={post.banner}
|
||||
src={profile.banner}
|
||||
alt='Profile banner'
|
||||
className='w-100 rounded-4 shadow-sm'
|
||||
style={{ height: '200px', objectFit: 'cover' }}
|
||||
@@ -90,10 +106,10 @@ function ProfileRead (props) {
|
||||
<div className='d-flex flex-column flex-md-row align-items-center gap-4 mb-5 p-3 p-md-5 bg-light rounded-4 shadow-sm'>
|
||||
{/* Profile Picture */}
|
||||
<div style={{ width: '120px', height: '120px' }} className='mb-3 mb-md-0'>
|
||||
{post.picture && !imageError.picture
|
||||
{profile.picture && !imageError.picture
|
||||
? (
|
||||
<img
|
||||
src={post.picture}
|
||||
src={profile.picture}
|
||||
alt='Profile'
|
||||
className='rounded-circle shadow w-100 h-100'
|
||||
style={{ objectFit: 'cover' }}
|
||||
@@ -117,7 +133,7 @@ function ProfileRead (props) {
|
||||
|
||||
{/* Profile Information */}
|
||||
<div className='flex-grow-1 text-center text-md-start mb-3 mb-md-0 d-flex flex-column align-items-center align-items-md-start'>
|
||||
<h3 className='mb-2 fw-bold'>{post.name || 'username'}</h3>
|
||||
<h3 className='mb-2 fw-bold'>{profile.name || 'username'}</h3>
|
||||
|
||||
<div className='text-muted small mb-3 d-flex align-items-center flex-column flex-md-row'>
|
||||
<span className='text-truncate me-2 mb-2 mb-md-0'>
|
||||
@@ -132,24 +148,24 @@ function ProfileRead (props) {
|
||||
</div>
|
||||
|
||||
{/* About Section */}
|
||||
{post.about && (
|
||||
{profile.about && (
|
||||
<div className='fs-6 text-secondary'>
|
||||
<NostrFormat content={post.about} />
|
||||
<NostrFormat content={profile.about} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Website Link */}
|
||||
{post.website && (
|
||||
{profile.website && (
|
||||
<div className='mb-3 d-flex align-items-center gap-2'>
|
||||
<FontAwesomeIcon icon={faGlobe} className='text-muted' size='sm' />
|
||||
<a
|
||||
href={post.website.startsWith('http') ? post.website : `https://${post.website}`}
|
||||
href={profile.website.startsWith('http') ? profile.website : `https://${profile.website}`}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='text-decoration-none text-muted small'
|
||||
style={{ wordBreak: 'break-all' }}
|
||||
>
|
||||
{post.website}
|
||||
{profile.website}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Component for read nostr information kind 1
|
||||
*/
|
||||
// Global npm libraries
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState, useCallback } from 'react'
|
||||
import { Container, Card, Spinner } from 'react-bootstrap'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faUser } from '@fortawesome/free-solid-svg-icons'
|
||||
@@ -20,35 +20,60 @@ function PublicRead (props) {
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const [profilePictureError, setProfilePictureError] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
// Get Last post from a author
|
||||
const start = () => {
|
||||
const getUserFeeds = useCallback(async () => {
|
||||
let feeds = await new Promise((resolve) => {
|
||||
let list = []
|
||||
let closedRelays = 0
|
||||
const pubHexData = nip19.decode(npub)
|
||||
const pubHex = pubHexData.data
|
||||
console.log('pubhex', pubHex)
|
||||
|
||||
const pool = RelayPool(config.nostrRelays)
|
||||
|
||||
pool.on('open', relay => {
|
||||
relay.subscribe('subid', { limit: 5, kinds: [1], authors: [pubHex] })
|
||||
setLoaded(true)
|
||||
})
|
||||
|
||||
pool.on('eose', relay => {
|
||||
console.log('Closing Relay')
|
||||
setLoaded(true)
|
||||
relay.close()
|
||||
closedRelays++
|
||||
// Resolve list if all relays are closed
|
||||
if (closedRelays === config.nostrRelays.length) {
|
||||
resolve(list)
|
||||
}
|
||||
})
|
||||
|
||||
pool.on('event', (relay, subId, ev) => {
|
||||
console.log('Received event:', ev)
|
||||
setPosts(currentPosts => [...currentPosts, ev])
|
||||
list = [...list, ev]
|
||||
})
|
||||
})
|
||||
console.log('feeds', feeds)
|
||||
// Remove duplicated feeds
|
||||
feeds = feeds.filter((val, i, list) => {
|
||||
const existingIndex = list.findIndex(value => value.id === val.id)
|
||||
return existingIndex === i
|
||||
})
|
||||
|
||||
// Sort from newest to oldest
|
||||
feeds.sort((a, b) => b.created_at - a.created_at)
|
||||
|
||||
setPosts(feeds)
|
||||
}, [npub])
|
||||
|
||||
useEffect(() => {
|
||||
// Get Last post from a author
|
||||
const start = async () => {
|
||||
setLoaded(true)
|
||||
await getUserFeeds()
|
||||
setLoaded(false)
|
||||
}
|
||||
|
||||
if (!loaded && npub) {
|
||||
start()
|
||||
}
|
||||
}, [bchWalletState, loaded, npub])
|
||||
}, [bchWalletState, loaded, npub, getUserFeeds])
|
||||
|
||||
const handleProfilePictureError = () => {
|
||||
setProfilePictureError(true)
|
||||
|
||||
Reference in New Issue
Block a user