Compare commits

..
10 Commits
9 changed files with 137 additions and 69 deletions
+17 -2
View File
@@ -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,
+1
View File
@@ -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",
@@ -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>
+48 -48
View File
@@ -85,6 +85,53 @@ function Offers (props) {
console.warn('Error in getOffers() ', err)
}
}
const handleBuy = useCallback(async (event) => {
try {
console.log('Buy button clicked. Event: ', event)
const targetOfferEventId = event.target.id
console.log('targetOfferEventId: ', targetOfferEventId)
// Initialize modal
setShowModal(true)
setModalBody(['Generating Counter Offer...', '(This can take a couple minutes)'])
setHideSpinner(false)
setDenyClose(true)
// Generate a counter offer.
const bchDexLib = appData.dexLib
const { offerData, partialHex } = await bchDexLib.take.takeOffer(
targetOfferEventId
)
// Upload the counter offer to Nostr.
const nostr = appData.nostr
const { eventId, noteId } = await nostr.testNostrUpload({
offerData,
partialHex
})
console.log('eventId: ', eventId)
console.log('noteId: ', noteId)
// update output modal
const newModalBody = []
newModalBody.push('Success!')
newModalBody.push('What happens next:')
newModalBody.push('The money has not yet left your wallet! It is still under your control.')
newModalBody.push('If the sellers node is online, they will accept the Counter Offer you just generated in a few minutes.')
newModalBody.push('If the tokens never show up, you can sweep the funds back into your wallet.')
setModalBody(newModalBody)
setHideSpinner(true)
setDenyClose(false)
} catch (error) {
console.warn('Error in handleBuy() ', error)
setModalBody(['Buy failed: ', error.message])
setHideSpinner(true)
setDenyClose(false)
}
}, [appData]) // Dependencies: appData since it's used inside the callback
// Get Offer data and manipulate it for the sake of presentation.
const handleOffers = useCallback(async () => {
@@ -129,54 +176,7 @@ function Offers (props) {
setOffers(formattedOffers)
setIsLoading(false)
}, [appData])
const handleBuy = async (event) => {
try {
console.log('Buy button clicked. Event: ', event)
const targetOfferEventId = event.target.id
console.log('targetOfferEventId: ', targetOfferEventId)
// Initialize modal
setShowModal(true)
setModalBody(['Generating Counter Offer...', '(This can take a couple minutes)'])
setHideSpinner(false)
setDenyClose(true)
const options = {
method: 'post',
url: `${SERVER}offer/take`,
data: {
nostrEventId: targetOfferEventId
}
}
const result = await axios.request(options)
const { eventId, noteId } = result.data
console.log(`Event Id : ${eventId}`)
console.log(`Note Id ${noteId}`)
// Add link to output
const newModalBody = []
newModalBody.push('Success!')
// newModalBody.push(<a href={`https://p2wdb.fullstack.cash/entry/hash/${p2wdbHash}`} target='_blank' rel='noreferrer'>P2WDB Entry</a>)
newModalBody.push('What happens next:')
newModalBody.push('The money has not yet left your wallet! It is still under your control.')
newModalBody.push('If the sellers node is online, they will accept the Counter Offer you just generated in a few minutes.')
newModalBody.push('If the tokens never show up, you can sweep the funds back into your wallet.')
setModalBody(newModalBody)
setHideSpinner(true)
setDenyClose(false)
} catch (error) {
console.warn('Error in handleBuy() ', error)
setModalBody(['Buy failed: ', error.message])
setHideSpinner(true)
setDenyClose(false)
}
}
}, [appData, handleBuy])
useEffect(() => {
// Retrieve initial offer data
+26 -12
View File
@@ -5,7 +5,7 @@
// 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, Tabs, Tab, Spinner, Carousel } from 'react-bootstrap'
import ReactMarkdown from 'react-markdown'
function UserDataReview (props) {
@@ -59,17 +59,31 @@ function UserDataReview (props) {
<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>
<div className='mt-3 mb-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>
+4 -2
View File
@@ -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 {