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 #27 from Permissionless-Software-Foundation/dh-content-creators
feat(nostr): Created prototype for Social Media Content Creators
This commit is contained in:
@@ -26,6 +26,7 @@ import ServerSelectView from './configuration/select-server-view'
|
|||||||
import NostrPost from './nostr/nostr-post/index.js'
|
import NostrPost from './nostr/nostr-post/index.js'
|
||||||
import NostrRead from './nostr/nostr-read/index.js'
|
import NostrRead from './nostr/nostr-read/index.js'
|
||||||
import GlobalFeed from './nostr/global-feed/index.js'
|
import GlobalFeed from './nostr/global-feed/index.js'
|
||||||
|
import ContentCreators from './nostr/content-creators/index.js'
|
||||||
import UserDataReview from './user-data-review'
|
import UserDataReview from './user-data-review'
|
||||||
import Offers from './offers'
|
import Offers from './offers'
|
||||||
function AppBody (props) {
|
function AppBody (props) {
|
||||||
@@ -50,6 +51,7 @@ function AppBody (props) {
|
|||||||
<Route path='/nostr-post' element={<NostrPost appData={appData} />} />
|
<Route path='/nostr-post' element={<NostrPost appData={appData} />} />
|
||||||
<Route path='/nostr-read' element={<NostrRead appData={appData} />} />
|
<Route path='/nostr-read' element={<NostrRead appData={appData} />} />
|
||||||
<Route path='/global-feed' element={<GlobalFeed appData={appData} />} />
|
<Route path='/global-feed' element={<GlobalFeed appData={appData} />} />
|
||||||
|
<Route path='/content-creators' element={<ContentCreators appData={appData} />} />
|
||||||
<Route path='/user-data/:tokenId' element={<UserDataReview appData={appData} />} />
|
<Route path='/user-data/:tokenId' element={<UserDataReview appData={appData} />} />
|
||||||
<Route path='/offers' element={<Offers appData={appData} />} />
|
<Route path='/offers' element={<Offers appData={appData} />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
/**
|
||||||
|
* Content creators card
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from 'react'
|
||||||
|
import { Card } from 'react-bootstrap'
|
||||||
|
import Jdenticon from '@chris.troutner/react-jdenticon'
|
||||||
|
import CopyOnClick from '../../bch-wallet/copy-on-click.js'
|
||||||
|
import { RelayPool } from 'nostr'
|
||||||
|
|
||||||
|
function ContentCard (props) {
|
||||||
|
const [profile, setProfile] = useState([])
|
||||||
|
const [loaded, setLoaded] = useState(false)
|
||||||
|
const { creator } = props
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadProfile = async () => {
|
||||||
|
const psf = 'wss://nostr-relay.psfoundation.info'
|
||||||
|
|
||||||
|
const pool = RelayPool([psf])
|
||||||
|
pool.on('open', relay => {
|
||||||
|
relay.subscribe('subid', { limit: 5, kinds: [0], authors: [creator.pubkey] })
|
||||||
|
})
|
||||||
|
|
||||||
|
pool.on('eose', relay => {
|
||||||
|
console.log('Closing Relay')
|
||||||
|
relay.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
pool.on('event', (relay, subId, ev) => {
|
||||||
|
try {
|
||||||
|
const profile = JSON.parse(ev.content)
|
||||||
|
console.log('profile', profile)
|
||||||
|
setProfile(profile)
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Error parsing profile', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setLoaded(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loaded && creator.npub) {
|
||||||
|
loadProfile()
|
||||||
|
}
|
||||||
|
}, [loaded, creator])
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
<>
|
||||||
|
<Card className='mb-4 bg-light rounded-4 shadow-sm border-0'>
|
||||||
|
<Card.Body className='p-4'>
|
||||||
|
{/* Desktop Layout - Horizontal */}
|
||||||
|
<div className='d-flex justify-content-end mb-2'>
|
||||||
|
<small className='text-muted'>
|
||||||
|
{new Date(creator.createdAt).toLocaleString()}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div className='d-none d-md-flex align-items-center gap-4'>
|
||||||
|
{/* Profile Picture */}
|
||||||
|
<div className='flex-shrink-0'>
|
||||||
|
<Jdenticon size='140' value={creator.npub} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Creator Info */}
|
||||||
|
<div className='flex-grow-1'>
|
||||||
|
<h5 className='mb-2 fw-bold'>{profile.name}</h5>
|
||||||
|
<p className='text-muted medium mb-3'>{profile.about}</p>
|
||||||
|
|
||||||
|
{/* Nostr Public Key */}
|
||||||
|
<div className='mb-2'>
|
||||||
|
<div className='d-flex align-items-center gap-2 mb-1'>
|
||||||
|
<small className='text-muted fw-semibold'>Nostr Public Key:</small>
|
||||||
|
</div>
|
||||||
|
<div className='d-flex align-items-center gap-2'>
|
||||||
|
<div className='text-break small text-secondary'>
|
||||||
|
{creator.npub}
|
||||||
|
</div>
|
||||||
|
<CopyOnClick
|
||||||
|
walletProp='npub'
|
||||||
|
appData={props.appData}
|
||||||
|
value={creator.npub}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* BCH Address */}
|
||||||
|
<div className='mb-3'>
|
||||||
|
<div className='d-flex align-items-center gap-2 mb-1'>
|
||||||
|
<small className='text-muted fw-semibold'>BCH Address:</small>
|
||||||
|
</div>
|
||||||
|
<div className='d-flex align-items-center gap-2'>
|
||||||
|
<div className='text-break small text-secondary'>
|
||||||
|
{creator.bchAddr}
|
||||||
|
</div>
|
||||||
|
<CopyOnClick
|
||||||
|
walletProp='bchAddress'
|
||||||
|
appData={props.appData}
|
||||||
|
value={creator.bchAddr}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<div className='flex-shrink-0 d-flex flex-column gap-2'>
|
||||||
|
<button
|
||||||
|
className='btn btn-outline-danger btn-sm rounded-pill px-3'
|
||||||
|
style={{ fontSize: '0.875rem', minWidth: '100px' }}
|
||||||
|
>
|
||||||
|
<i className='bi bi-person-plus me-1' />
|
||||||
|
Follow
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='btn btn-primary btn-sm rounded-pill px-3'
|
||||||
|
style={{ fontSize: '0.875rem', minWidth: '100px' }}
|
||||||
|
>
|
||||||
|
<i className='bi bi-chat-dots me-1' />
|
||||||
|
Message
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Layout - Vertical */}
|
||||||
|
<div className='d-flex d-md-none flex-column align-items-center text-center'>
|
||||||
|
{/* Profile Picture */}
|
||||||
|
<div className='mb-3'>
|
||||||
|
<Jdenticon size='100' value={creator.npub} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Creator Info */}
|
||||||
|
<div className='w-100 mb-3'>
|
||||||
|
<h5 className='mb-2 fw-bold'>{profile.name}</h5>
|
||||||
|
<p className='text-muted small mb-3'>{profile.about}</p>
|
||||||
|
|
||||||
|
{/* Nostr Public Key */}
|
||||||
|
<div className='mb-3'>
|
||||||
|
<div className='mb-1'>
|
||||||
|
<small className='text-muted fw-semibold'>Nostr Public Key:</small>
|
||||||
|
</div>
|
||||||
|
<div className='d-flex align-items-center justify-content-center gap-2'>
|
||||||
|
<div className='text-break small text-secondary' style={{ maxWidth: '200px' }}>
|
||||||
|
{creator.npub}
|
||||||
|
</div>
|
||||||
|
<CopyOnClick
|
||||||
|
walletProp='npub'
|
||||||
|
appData={props.appData}
|
||||||
|
value={creator.npub}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* BCH Address */}
|
||||||
|
<div className='mb-3'>
|
||||||
|
<div className='mb-1'>
|
||||||
|
<small className='text-muted fw-semibold'>BCH Address:</small>
|
||||||
|
</div>
|
||||||
|
<div className='d-flex align-items-center justify-content-center gap-2'>
|
||||||
|
<div className='text-break small text-secondary' style={{ maxWidth: '200px' }}>
|
||||||
|
{creator.bchAddr}
|
||||||
|
</div>
|
||||||
|
<CopyOnClick
|
||||||
|
walletProp='bchAddress'
|
||||||
|
appData={props.appData}
|
||||||
|
value={creator.bchAddr}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<div className='d-flex gap-2'>
|
||||||
|
<button
|
||||||
|
className='btn btn-outline-danger btn-sm rounded-pill px-3'
|
||||||
|
style={{ fontSize: '0.875rem' }}
|
||||||
|
>
|
||||||
|
<i className='bi bi-person-plus me-1' />
|
||||||
|
Follow
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='btn btn-primary btn-sm rounded-pill px-3'
|
||||||
|
style={{ fontSize: '0.875rem' }}
|
||||||
|
>
|
||||||
|
<i className='bi bi-chat-dots me-1' />
|
||||||
|
Message
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ContentCard
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import React, { useState, useEffect } from 'react'
|
||||||
|
import { Container, Spinner } from 'react-bootstrap'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
// Local libraries
|
||||||
|
import config from '../../../../config'
|
||||||
|
import ContentCard from './content-card'
|
||||||
|
// Global variables and constants
|
||||||
|
const SERVER = config.dexServer
|
||||||
|
function ContentCreators (props) {
|
||||||
|
const [creators, setCreators] = useState([])
|
||||||
|
const [loaded, setLoaded] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadCreators = async () => {
|
||||||
|
try {
|
||||||
|
const creators = await axios.get(`${SERVER}/sm/list/all/0`)
|
||||||
|
console.log(creators.data)
|
||||||
|
setCreators(creators.data)
|
||||||
|
setLoaded(true)
|
||||||
|
} catch (error) {
|
||||||
|
setLoaded(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loaded) {
|
||||||
|
loadCreators()
|
||||||
|
}
|
||||||
|
}, [loaded])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className='mt-4 mb-5'>
|
||||||
|
<div className='mb-4'>
|
||||||
|
<h2 className='text-center mb-3'>Content Creators</h2>
|
||||||
|
<p className='text-center text-muted'>
|
||||||
|
Discover amazing content creators in the Bitcoin Cash ecosystem
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!loaded && (
|
||||||
|
<div className='text-center text-muted py-5 bg-light rounded-4 shadow-sm'>
|
||||||
|
<Spinner animation='border' />
|
||||||
|
<div className='mt-3'>Loading content creators...</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{loaded && (
|
||||||
|
<div>
|
||||||
|
{creators.map((creator, i) => (
|
||||||
|
<ContentCard key={`creator-key${i}`} creator={creator} appData={props.appData} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{loaded && creators.length === 0 && (
|
||||||
|
<div className='text-center text-muted py-5 bg-light rounded-4 shadow-sm'>
|
||||||
|
<i className='bi bi-people-fill fs-1 mb-3 d-block opacity-50' />
|
||||||
|
<div>No content creators found</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ContentCreators
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Container } from 'react-bootstrap'
|
||||||
|
import ContentCreatorsList from './content-creators-list.js'
|
||||||
|
|
||||||
|
function ContentCreators (props) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Container>
|
||||||
|
<ContentCreatorsList {...props} />
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ContentCreators
|
||||||
@@ -77,6 +77,13 @@ function NavMenu (props) {
|
|||||||
>
|
>
|
||||||
Nostr Profile
|
Nostr Profile
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
<NavLink
|
||||||
|
className={currentPath === '/content-creators' ? 'nav-link-active' : 'nav-link-inactive'}
|
||||||
|
to='/content-creators'
|
||||||
|
onClick={handleClickEvent}
|
||||||
|
>
|
||||||
|
Content Creators
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user