mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex-taker-v2.git
synced 2026-09-21 16:52:01 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a982d2ef2 | ||
|
|
24bb18c77c | ||
|
|
913745ba9f | ||
|
|
f24c199dd6 | ||
|
|
0da9d4c567 | ||
|
|
e05948aa32 | ||
|
|
3cf69cbd95 | ||
|
|
45b28c168f | ||
|
|
3d622e9d99 | ||
|
|
a89d4c21cc |
Generated
+17
-2
@@ -21,6 +21,7 @@
|
||||
"bch-token-sweep": "2.2.1",
|
||||
"bootstrap": "5.2.0",
|
||||
"crypto-browserify": "3.12.1",
|
||||
"marked": "^15.0.12",
|
||||
"nostr-tools": "2.10.4",
|
||||
"p2wdb": "2.4.1",
|
||||
"process": "0.11.10",
|
||||
@@ -18223,8 +18224,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "12.0.2",
|
||||
"dev": true,
|
||||
"version": "15.0.12",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
|
||||
"integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
@@ -26496,6 +26498,19 @@
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/marked": {
|
||||
"version": "12.0.2",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz",
|
||||
"integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/npm-run-path": {
|
||||
"version": "6.0.0",
|
||||
"dev": true,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"bch-token-sweep": "2.2.1",
|
||||
"bootstrap": "5.2.0",
|
||||
"crypto-browserify": "3.12.1",
|
||||
"marked": "15.0.12",
|
||||
"nostr-tools": "2.10.4",
|
||||
"p2wdb": "2.4.1",
|
||||
"process": "0.11.10",
|
||||
|
||||
@@ -50,7 +50,7 @@ function AppBody (props) {
|
||||
<Route path='/nostr-post' element={<NostrPost appData={appData} />} />
|
||||
<Route path='/nostr-read' element={<NostrRead appData={appData} />} />
|
||||
<Route path='/global-feed' element={<GlobalFeed appData={appData} />} />
|
||||
<Route path='/user-data/:cid' element={<UserDataReview appData={appData} />} />
|
||||
<Route path='/user-data/:tokenId' element={<UserDataReview appData={appData} />} />
|
||||
<Route path='/offers' element={<Offers appData={appData} />} />
|
||||
</Routes>
|
||||
{/** Show in all paths except the servers view */}
|
||||
|
||||
@@ -102,7 +102,7 @@ function InfoButton (props) {
|
||||
<Col xs={4}><b>User Data</b>:</Col>
|
||||
<Col xs={8}>
|
||||
<a
|
||||
href={`/user-data/${mutableDataCid}`}
|
||||
href={`/user-data/${props.token.tokenId}`}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='btn btn-link p-0'
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Card, Spinner } from 'react-bootstrap'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faUser } from '@fortawesome/free-solid-svg-icons'
|
||||
import * as nip19 from 'nostr-tools/nip19'
|
||||
import NostrFormat from '../nostr-format'
|
||||
|
||||
function FeedCard (props) {
|
||||
const { post, appData, profiles } = props
|
||||
@@ -87,7 +88,7 @@ function FeedCard (props) {
|
||||
</div>
|
||||
<div className='mb-4'>
|
||||
<div className='fs-5 ms-5 text-dark' style={{ lineHeight: '1.3' }}>
|
||||
{post.content}
|
||||
<NostrFormat content={post.content} />
|
||||
</div>
|
||||
</div>
|
||||
</Card.Body>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Parse nostr content to html , support markdown , links , line breaks
|
||||
*/
|
||||
import React from 'react'
|
||||
import { marked } from 'marked' // https://www.npmjs.com/package/marked/v/0.3.9
|
||||
|
||||
const NostrFormat = ({ content }) => {
|
||||
const formatContent = (content) => {
|
||||
if (!content) return ''
|
||||
// create marked renderer
|
||||
const renderer = new marked.Renderer()
|
||||
// customize link renderer to open in new tab
|
||||
renderer.link = function ({ href, text }) {
|
||||
return `<a href="${href}" target="_blank" >${text}</a>`
|
||||
}
|
||||
// set marked options
|
||||
marked.setOptions({
|
||||
renderer,
|
||||
breaks: true
|
||||
|
||||
})
|
||||
// parse content to html
|
||||
const html = marked.parse(content)
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
return (
|
||||
<div dangerouslySetInnerHTML={{ __html: formatContent(content) }} />
|
||||
)
|
||||
}
|
||||
|
||||
export default NostrFormat
|
||||
@@ -7,7 +7,7 @@ import { Container, Button } from 'react-bootstrap'
|
||||
import CopyOnClick from '../../bch-wallet/copy-on-click.js'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faUser } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import NostrFormat from '../nostr-format'
|
||||
import { RelayPool } from 'nostr'
|
||||
|
||||
function ProfileRead (props) {
|
||||
@@ -74,7 +74,9 @@ function ProfileRead (props) {
|
||||
</span>
|
||||
<CopyOnClick walletProp='npub' appData={props.appData} value={bchWalletState.nostrKeyPair.npub} />
|
||||
</div>
|
||||
<div className='fs-6 text-secondary'>{post.about}</div>
|
||||
<div className='fs-6 text-secondary'>
|
||||
<NostrFormat content={post.about} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='d-flex gap-2 align-items-center flex-wrap justify-content-center'>
|
||||
<Button
|
||||
|
||||
@@ -6,7 +6,7 @@ import React, { useEffect, useState } from 'react'
|
||||
import { Container, Card, Spinner } from 'react-bootstrap'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faUser } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import NostrFormat from '../nostr-format'
|
||||
import { RelayPool } from 'nostr'
|
||||
|
||||
function PublicRead (props) {
|
||||
@@ -72,7 +72,7 @@ function PublicRead (props) {
|
||||
</div>
|
||||
<div className='mb-4'>
|
||||
<div className='fs-5 ms-5 text-dark' style={{ lineHeight: '1.3' }}>
|
||||
{post.content}
|
||||
<NostrFormat content={post.content} />
|
||||
</div>
|
||||
</div>
|
||||
</Card.Body>
|
||||
|
||||
@@ -5,25 +5,34 @@
|
||||
// Global npm libraries
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { Container, Row, Col, Tabs, Tab, Spinner } from 'react-bootstrap'
|
||||
import { Container, Row, Col, Spinner, Carousel } from 'react-bootstrap'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
|
||||
function UserDataReview (props) {
|
||||
const appData = props.appData
|
||||
const [media, setMedia] = useState([])
|
||||
const [markdown, setMarkdown] = useState('')
|
||||
const [activeTab, setActiveTab] = useState('media')
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
const [tokenData, setTokenData] = useState(null)
|
||||
// Get the id parameter from the URL
|
||||
const { cid } = useParams()
|
||||
const { tokenId } = useParams()
|
||||
|
||||
useEffect(() => {
|
||||
const loadMedia = async () => {
|
||||
setLoading(true)
|
||||
const loadData = async () => {
|
||||
try {
|
||||
const tokenData = await appData.wallet.getTokenData(tokenId)
|
||||
|
||||
setTokenData(tokenData)
|
||||
setLoading(true)
|
||||
|
||||
if (!tokenData.mutableData) throw new Error('Mutable data not found')
|
||||
|
||||
const cid = parseCid(tokenData.mutableData)
|
||||
|
||||
const { json } = await appData.wallet.cid2json({ cid })
|
||||
const userDataString = json.userData
|
||||
|
||||
if (userDataString) {
|
||||
const userData = JSON.parse(userDataString)
|
||||
setMedia(userData.media)
|
||||
@@ -34,60 +43,80 @@ function UserDataReview (props) {
|
||||
}
|
||||
setLoading(false)
|
||||
}
|
||||
loadMedia()
|
||||
}, [cid, appData.wallet])
|
||||
loadData()
|
||||
}, [tokenId, appData.wallet])
|
||||
|
||||
// Get Cid from url
|
||||
const parseCid = (url) => {
|
||||
// get the cid from the url format 'ipfs://bafybeicem27xbzs65uvbcgykcmscsgln3lmhbfrcoec3gdttkdgtxv5acq
|
||||
if (url && url.includes('ipfs://')) {
|
||||
const cid = url.split('ipfs://')[1]
|
||||
return cid
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Row>
|
||||
<Col>
|
||||
{/** Error message */}
|
||||
{error && <p className='text-danger'>{error}</p>}
|
||||
{loading
|
||||
? (
|
||||
|
||||
{/** Loading spinner */}
|
||||
{loading &&
|
||||
(
|
||||
<div className='text-center my-5'>
|
||||
<Spinner animation='border' role='status' variant='primary'>
|
||||
<span className='visually-hidden'>Loading...</span>
|
||||
</Spinner>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onSelect={(k) => setActiveTab(k)}
|
||||
className='mb-4'
|
||||
>
|
||||
<Tab eventKey='media' title='Media'>
|
||||
{media && media.length > 0
|
||||
? (
|
||||
<Row className='mt-3'>
|
||||
{media.map((item, index) => (
|
||||
<Col key={index} xs={12} sm={6} md={4} lg={4} className='mb-3'>
|
||||
<img
|
||||
src={item.url}
|
||||
alt={`Review media ${index + 1}`}
|
||||
style={{ width: '100%', height: '250px', objectFit: 'cover', borderRadius: '8px' }}
|
||||
/>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
)
|
||||
: (
|
||||
<p className='mt-3'>No media content available</p>
|
||||
)}
|
||||
</Tab>
|
||||
<Tab eventKey='markdown' title='Content'>
|
||||
{markdown
|
||||
? (
|
||||
<div className='markdown-content mt-3'>
|
||||
<ReactMarkdown>{markdown}</ReactMarkdown>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<p className='mt-3'>No content available</p>
|
||||
)}
|
||||
</Tab>
|
||||
</Tabs>
|
||||
)}
|
||||
)}
|
||||
{/** User data */}
|
||||
{!loading && !error && (
|
||||
<div className='text-center'>
|
||||
{/** Name */}
|
||||
<h1>{tokenData.genesisData.name}</h1>
|
||||
|
||||
{/** Media Content Carousel */}
|
||||
{media && media.length > 0
|
||||
? (
|
||||
<div className='my-5 '>
|
||||
<Carousel>
|
||||
{media.map((item, index) => (
|
||||
<Carousel.Item key={index}>
|
||||
<img
|
||||
className='d-block w-100'
|
||||
src={item.url}
|
||||
alt={`Review media ${index + 1}`}
|
||||
style={{ height: '400px', objectFit: 'contain' }}
|
||||
/>
|
||||
<Carousel.Caption>
|
||||
<p>Image {index + 1} of {media.length}</p>
|
||||
</Carousel.Caption>
|
||||
</Carousel.Item>
|
||||
))}
|
||||
</Carousel>
|
||||
<style>{'.carousel-control-next,.carousel-control-prev, .carousel-indicators {filter: invert(100%); } '}
|
||||
</style>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<p className='mt-3'>No media content available</p>
|
||||
)}
|
||||
|
||||
{/** Markdown Content */}
|
||||
{markdown
|
||||
? (
|
||||
<div className='markdown-content my-5 text-center'>
|
||||
<ReactMarkdown>{markdown}</ReactMarkdown>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<p className='mt-3'>No content available</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
|
||||
+4
-2
@@ -13,7 +13,8 @@ function useAppState () {
|
||||
ssr: true,
|
||||
defaultValue: {
|
||||
serverUrl: 'https://free-bch.fullstack.cash' // Default server
|
||||
}
|
||||
},
|
||||
nftData: {}
|
||||
})
|
||||
|
||||
console.log('lsState: ', lsState)
|
||||
@@ -38,7 +39,7 @@ function useAppState () {
|
||||
const [denyClose, setDenyClose] = useState(false)
|
||||
|
||||
// NFTs for sale stored data to improve performance
|
||||
const [nftForSaleCacheData, setNftForSaleCacheData] = useState({})
|
||||
const [nftForSaleCacheData, setNftForSaleCacheData] = useState(lsState.nftData || {})
|
||||
|
||||
// The wallet state makes this a true progressive web app (PWA). As
|
||||
// balances, UTXOs, and tokens are retrieved, this state is updated.
|
||||
@@ -107,6 +108,7 @@ function useAppState () {
|
||||
const newCacheData = Object.assign({}, cacheData, data) // Merge the new data with the cache data
|
||||
allCacheData[tokenId] = newCacheData // Update the cache data
|
||||
setNftForSaleCacheData(allCacheData) // Update the state
|
||||
updateLocalStorage({ nftData: allCacheData }) // Update the local storage
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user