Compare commits

...
5 Commits
Author SHA1 Message Date
Chris Troutner bd3798f5aa Merge pull request #47 from Permissionless-Software-Foundation/dh-feed-pfps
feat(nostr): Added profile photos on feeds
2025-08-06 19:37:25 -07:00
Daniel Gonzalez 5f0103ba81 Merge branch 'master' into dh-feed-pfps 2025-08-06 21:54:44 -04:00
Daniel Gonzalez 55ebb7b25d feat(nostr): Added profile photos on feeds 2025-08-06 21:53:11 -04:00
Chris Troutner 44246a78d5 Merge pull request #46 from Permissionless-Software-Foundation/ct-unstable
fix(content creators): Fixing bug. Adding TODO items
2025-08-06 12:23:09 -07:00
Chris Troutner b32bbdae0a fix(content creators): Fixing bug. Adding TODO items 2025-08-06 12:21:03 -07:00
5 changed files with 72 additions and 6 deletions
@@ -1,3 +1,15 @@
/*
This component is used to display the list of content creators.
TODO:
- getFollowList() should aggregate all the followers from all the relays.
Currently each relay overwrites the last one.
- loadProfile() should use multiple relays. It should exit after the first
successful retrieval of data from a relay. If the relay fails to give data,
it should move on to the next relay.
*/
import React, { useState, useEffect, useCallback } from 'react'
import { Container, Spinner, Dropdown } from 'react-bootstrap'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
@@ -25,6 +37,7 @@ function ContentCreators (props) {
const { nostrKeyPair } = appData.bchWalletState
const pool = RelayPool(config.nostrRelays)
// const pool = RelayPool([config.nostrRelay])
pool.on('open', relay => {
relay.subscribe('subid', { limit: 1, kinds: [3], authors: [nostrKeyPair.pubHex] })
})
@@ -51,7 +64,8 @@ function ContentCreators (props) {
const loadProfile = useCallback(async (pubKey) => {
return new Promise((resolve) => {
const pool = RelayPool(config.nostrRelays)
// const pool = RelayPool(config.nostrRelays)
const pool = RelayPool([config.nostrRelay])
pool.on('open', relay => {
relay.subscribe('subid', { limit: 5, kinds: [0], authors: [pubKey] })
})
@@ -25,6 +25,7 @@ function FeedCard (props) {
const [isLiked, setIsLiked] = useState(false)
const [likesCount, setLikesCount] = useState(null)
const [likesFetched, setLikesFetched] = useState(false)
const [profilePictureError, setProfilePictureError] = useState(false)
// function to fetch a post likes reaction
const handleLikes = useCallback(async (post, userPubKey) => {
@@ -76,6 +77,15 @@ function FeedCard (props) {
console.warn(error)
}
}, [])
const handleProfilePictureError = () => {
setProfilePictureError(true)
}
const handleProfilePictureLoad = () => {
setProfilePictureError(false)
}
// Get npub from pubkey
useEffect(() => {
const npub = nip19.npubEncode(post.pubkey)
@@ -170,7 +180,20 @@ function FeedCard (props) {
background: 'linear-gradient(45deg, #6c757d, #495057)'
}}
>
<FontAwesomeIcon icon={faUser} size='1x' color='#7c7c7d' />
{profile?.picture && !profilePictureError
? (
<img
src={profile.picture}
alt='Profile'
className='rounded-circle w-100 h-100'
style={{ objectFit: 'cover' }}
onError={handleProfilePictureError}
onLoad={handleProfilePictureLoad}
/>
)
: (
<FontAwesomeIcon icon={faUser} size='1x' color='#7c7c7d' />
)}
</div>
<div className='flex-grow-1'>
<div className='fw-bold mb-1'>
+9 -2
View File
@@ -22,14 +22,20 @@ function Feeds (props) {
const [profiles, setProfiles] = useState({})
// function to fetch profile and set it to profiles state
const fetchProfile = useCallback((pubkey) => {
const fetchProfile = useCallback(async (pubkey) => {
// no fetch profile again if it exist
let hasProfileRequest = false
setProfiles(currentProfiles => {
if (currentProfiles[pubkey]) {
hasProfileRequest = true
return currentProfiles
} else {
const newProfiles = { ...currentProfiles }
newProfiles[pubkey] = { loaded: false }
return newProfiles
}
return currentProfiles
})
if (hasProfileRequest) return
const pool = RelayPool(config.nostrRelays)
pool.on('open', relay => {
@@ -49,6 +55,7 @@ function Feeds (props) {
return currentProfiles
})
} catch (error) {
console.warn(error)
// skip error
}
})
@@ -26,7 +26,7 @@ const NostrFormat = ({ content }) => {
}
return (
<div dangerouslySetInnerHTML={{ __html: formatContent(content) }} />
<div style={{ overflow: 'auto' }} dangerouslySetInnerHTML={{ __html: formatContent(content) }} />
)
}
@@ -18,6 +18,7 @@ function PublicRead (props) {
const { bchWalletState } = props.appData
const [posts, setPosts] = useState([])
const [loaded, setLoaded] = useState(false)
const [profilePictureError, setProfilePictureError] = useState(false)
useEffect(() => {
// Get Last post from a author
@@ -49,6 +50,14 @@ function PublicRead (props) {
}
}, [bchWalletState, loaded, npub])
const handleProfilePictureError = () => {
setProfilePictureError(true)
}
const handleProfilePictureLoad = () => {
setProfilePictureError(false)
}
return (
<Container className='mt-4 mb-5'>
{/* Nostr Feed Section */}
@@ -76,7 +85,20 @@ function PublicRead (props) {
background: 'linear-gradient(45deg, #6c757d, #495057)'
}}
>
<FontAwesomeIcon icon={faUser} size='1x' color='#7c7c7d' />
{props.profile?.picture && !profilePictureError
? (
<img
src={props.profile.picture}
alt='Profile'
className='rounded-circle w-100 h-100'
style={{ objectFit: 'cover' }}
onError={handleProfilePictureError}
onLoad={handleProfilePictureLoad}
/>
)
: (
<FontAwesomeIcon icon={faUser} size='1x' color='#7c7c7d' />
)}
</div>
<div className='flex-grow-1'>
<div className='fw-bold mb-1'>{props.profile?.name}</div>