diff --git a/config-overrides.js b/config-overrides.js
index 62b24b0..cf363b2 100644
--- a/config-overrides.js
+++ b/config-overrides.js
@@ -1,18 +1,18 @@
-const webpack = require('webpack');
+const webpack = require('webpack')
-module.exports = function override(config) {
+module.exports = function override (config) {
config.resolve.fallback = {
...config.resolve.fallback,
crypto: require.resolve('crypto-browserify'),
stream: require.resolve('stream-browserify'),
- vm: require.resolve('vm-browserify'),
- };
+ vm: require.resolve('vm-browserify')
+ }
config.plugins = (config.plugins || []).concat([
new webpack.ProvidePlugin({
- process: 'process/browser',
- }),
- ]);
+ process: 'process/browser'
+ })
+ ])
- return config;
-};
+ return config
+}
diff --git a/src/components/app-body/nfts-for-sale/index.js b/src/components/app-body/nfts-for-sale/index.js
index 11cfb38..0d6328d 100644
--- a/src/components/app-body/nfts-for-sale/index.js
+++ b/src/components/app-body/nfts-for-sale/index.js
@@ -1,16 +1,21 @@
/*
Shows NFTs for sale on the DEX.
+
+ workflow:
+ 1. Load offers
+ 2. display token cards with current offers data
+ 3. Load tokens data
+ 4. add data to offers and update card with new data
+ 5. Load tokens icons
+ 6. add icons to offers and update card with new icons
*/
// Global npm libraries
-import React, { useState, useEffect } from 'react'
-import { Container, Row, Card, Col, Button, Spinner } from 'react-bootstrap'
+import React, { useState, useEffect, useCallback } from 'react'
+import { Container, Row, Col, Button, Spinner } from 'react-bootstrap'
import axios from 'axios'
-import Jdenticon from '@chris.troutner/react-jdenticon'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faRedo } from '@fortawesome/free-solid-svg-icons'
-// import BchDexLib from 'bch-dex-lib'
-// import RetryQueue from '@chris.troutner/retry-queue'
// Local libraries
import config from '../../../config'
@@ -21,111 +26,188 @@ const SERVER = config.dexServer
function NftsForSale (props) {
// Dependency injection through props
const appData = props.appData
- console.log('NftsForSale() appData: ', appData)
- // State
const [offers, setOffers] = useState([])
+ const [isLoading, setIsLoading] = useState(false)
+ const [offersAreLoaded, setOffersAreLoaded] = useState(false)
+ const [iconsAreLoaded, setIconsAreLoaded] = useState(false)
+ const [dataAreLoaded, setDataAreLoaded] = useState(false)
- async function getNftOffers(page = 0) {
+ // Handler for refresh button
+ const handleRefresh = () => {
+ console.log('handling refresh')
+ loadNftOffers()
+ }
+
+ // Function to process token data
+ const processTokenData = useCallback(async (offer) => {
try {
- const options = {
- method: 'GET',
- url: `${SERVER}/offer/list/nft/${page}`,
- data: {}
- }
- const result = await axios.request(options)
- console.log('result.data: ', result.data)
+ const { wallet, bchWalletState } = appData
+ const { bchjs } = wallet
+ // Calculate USD price
+ const rateInSats = parseInt(offer.rateInBaseUnit)
+ const bchCost = bchjs.BitcoinCash.toBitcoinCash(rateInSats)
+ const usdPrice = bchCost * bchWalletState.bchUsdPrice * offer.numTokens
+ offer.usdPrice = `$${usdPrice.toFixed(3)}`
+
+ return offer
+ } catch (err) {
+ console.error('Error processing token:', err)
+ return offer
+ }
+ }, [appData])
+
+ // Fetch offers
+ const getNftOffers = useCallback(async (page = 0) => {
+ try {
+ setOffersAreLoaded(false)
+
+ const result = await axios.get(`${SERVER}/offer/list/nft/${page}`)
const rawOffers = result.data
+ console.log('rawOffers: ', rawOffers)
- const bchjs = appData.wallet.bchjs
- const wallet = appData.wallet
-
- // Instantiate the BchDexLib object.
- // const dexLib = new BchDexLib({bchWallet: wallet, p2wdbRead: {}, p2wdbWrite: {}})
-
- const offerDataCallback = (offer) => {
- console.log('offerDataCallback() offer: ', offer)
- }
-
- // Add a default icon.
+ // Process each offer
+ const processedOffers = []
for (let i = 0; i < rawOffers.length; i++) {
- const thisOffer = rawOffers[i]
-
- thisOffer.icon = ( )
- thisOffer.iconDownloaded = false
-
- // Convert sats to BCH, and then calculate cost in USD.
- const rateInSats = parseInt(thisOffer.rateInBaseUnit)
- // console.log('rateInSats: ', rateInSats)
- const bchCost = bchjs.BitcoinCash.toBitcoinCash(rateInSats)
- // console.log('bchCost: ', bchCost)
- // console.log('bchUsdPrice: ', this.state.appData.bchWalletState.bchUsdPrice)
- const usdPrice = bchCost * appData.bchWalletState.bchUsdPrice * thisOffer.numTokens
- // usdPrice = bchjs.Util.floor2(usdPrice)
- // console.log(`usdPrice: ${usdPrice}`)
- const priceStr = `$${usdPrice.toFixed(3)}`
- thisOffer.usdPrice = priceStr
-
- // Download token data
- // await dexLib.tokenData.getTokenData(thisOffer, offerDataCallback)
- const tokenData = await wallet.getTokenData(thisOffer.tokenId)
- console.log('complete tokenData: ', tokenData)
-
- // const mutableCid = tokenData.mutableData.slice(7)
- // const url1 = `https://free-bch.fullstack.cash/ipfs/file-info/${mutableCid}`
- // console.log('url1: ', url1)
- // const resp1 = await axios.get(url1)
- // console.log('resp1.data: ', resp1.data)
-
- try {
- const mutableCid = tokenData.mutableData.slice(7)
- console.log(`mutable CID for token ${thisOffer.tokenId}: ${mutableCid}`)
- let mutableData = await wallet.cid2json({ cid: mutableCid })
- console.log('mutableData: ', mutableData)
- mutableData = mutableData.json
-
- } catch(err) {
- console.error(`Could not download mutable data for token ${thisOffer.tokenId}: ${err.message}`)
- }
+ const offer = rawOffers[i]
+ const processedOffer = await processTokenData(offer)
+ processedOffers.push(processedOffer)
}
- return rawOffers
- } catch(err) {
- console.error('NftsForSale() getNftOffers() error: ', err)
- return []
+ setOffersAreLoaded(true)
+
+ return processedOffers
+ } catch (err) {
+ console.error('getNftOffers error:', err)
+ setOffersAreLoaded(true)
+ throw err
}
- }
+ }, [processTokenData])
- async function handleOffers() {
- let offers = await getNftOffers()
- console.log('offers: ', offers)
-
- return offers
- }
-
- async function handleStartProcessingTokens() {
- return await handleOffers()
- }
-
- // This is an async startup function that is called by the useEffect hook.
- const asyncStartup = async () => {
+ // This function loads the token data .
+ const lazyLoadTokenData = useCallback(async (tokens) => {
try {
- const offers = await handleStartProcessingTokens()
- setOffers(offers)
+ setDataAreLoaded(false)
+ // map each token and fetch the token data
+ for (let i = 0; i < tokens.length; i++) {
+ const thisToken = tokens[i]
+
+ // data does not need to be downloaded, so continue with the next one
+ if (thisToken.dataAlreadyDownloaded) continue
+
+ // Try to get token data.
+ const tokenData = await appData.wallet.getTokenData(thisToken.tokenId)
+
+ if (tokenData) {
+ // Set data to the token object , this can be used to display the token name in the token card component.
+ thisToken.tokenData = tokenData
+ }
+
+ // Mark token to prevent fetch token data again.
+ thisToken.dataAlreadyDownloaded = true
+ }
+
+ setDataAreLoaded(true)
} catch (error) {
- console.error('NftsForSale() asyncStartup() error: ', error)
+ setDataAreLoaded(true)
}
- }
+ }, [appData])
- // Load NFT data when the page loads from the server or from the cache.
+ // Fetch mutable data if it exist and get the token icon url
+ const fetchTokenInfo = useCallback(async (token) => {
+ try {
+ // Get the token data
+ const tokenData = token.tokenData
+
+ if (!tokenData.mutableData) return false // Return false if no mutable data
+
+ // Get the token icon from the mutable data
+ const cid = parseCid(tokenData.mutableData)
+ console.log('mutable data cid', cid)
+
+ const { json } = await appData.wallet.cid2json({ cid })
+ console.log('json: ', json)
+ if (!json) return false
+
+ let iconUrl = json.tokenIcon
+
+ if (json.fullSizedUrl && json.fullSizedUrl.includes('http')) {
+ iconUrl = json.fullSizedUrl
+ }
+ // Return icon url
+ return iconUrl
+ } catch (error) {
+ return false
+ }
+ }, [appData])
+
+ // This function loads the token icons from the ipfs gateways.
+ const lazyLoadTokenIcons = useCallback(async (tokens) => {
+ try {
+ setIconsAreLoaded(false)
+ // map each token and fetch the icon url
+ for (let i = 0; i < tokens.length; i++) {
+ const thisToken = tokens[i]
+
+ // Incon does not need to be downloaded, so continue with the next one
+ if (thisToken.iconAlreadyDownloaded) continue
+
+ // Try to get token icon url from mutable data.
+ const iconUrl = await fetchTokenInfo(thisToken)
+ console.log('iconUrl', iconUrl)
+ if (iconUrl) {
+ // Set the icon url to the token , this can be used to display the icon in the token card component.
+ thisToken.icon = iconUrl
+ }
+
+ // Mark token to prevent fetch token icon again.
+ thisToken.iconAlreadyDownloaded = true
+ }
+
+ setIconsAreLoaded(true)
+ } catch (error) {
+ setIconsAreLoaded(true)
+ }
+ }, [fetchTokenInfo])
+
+ // Main function to load NFT offers
+ const loadNftOffers = useCallback(async () => {
+ try {
+ setIsLoading(true)
+ // Get tokens in offers
+ const offers = await getNftOffers()
+ console.log('offers: ', offers)
+ setOffers(offers)
+ // Load tokens data
+ await lazyLoadTokenData(offers)
+ // Load tokens icons
+ await lazyLoadTokenIcons(offers)
+ setIsLoading(false)
+ } catch (err) {
+ setIsLoading(false)
+ console.error('Error loading NFT offers:', err)
+ }
+ }, [lazyLoadTokenData, lazyLoadTokenIcons, getNftOffers])
+
+ // Effect to load NFTs on component mount
useEffect(() => {
- asyncStartup()
- }, [])
+ console.log('loading nfts for sale')
+ loadNftOffers()
+ }, [loadNftOffers])
+ // 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
+ }
// This function generates a Token Card for each token in the wallet.
- function generateCards () {
- // console.log('generateCards() offerData: ', offerData)
+ function generateCards (offers) {
+ console.log('generateCards() offerData: ', offers)
const tokens = offers
@@ -133,13 +215,12 @@ function NftsForSale (props) {
for (let i = 0; i < tokens.length; i++) {
const thisToken = tokens[i]
- // console.log(`thisToken: ${JSON.stringify(thisToken, null, 2)}`)
const thisTokenCard = (
)
tokenCards.push(thisTokenCard)
@@ -155,18 +236,53 @@ function NftsForSale (props) {
NFTs for Sale
-
-
-
- Refresh
-
+
+ {/** Show spinner info if tokens are loaded but data is not loaded */
+ offersAreLoaded && !dataAreLoaded && (
+
+ Loading Tokens Data
+
+
+ )
+ }
+ {/** Show spinner info if tokens are loaded but icons are not loaded */
+ dataAreLoaded && !iconsAreLoaded && (
+
+ Loading Tokens Icons
+
+
+ )
+ }
+
+ {!isLoading && (
+
+
+
+ Refresh
+
+
+
+ )}
+
+ {!offersAreLoaded && (
+
+
+
+ )}
- {generateCards()}
+ {offersAreLoaded && generateCards(offers)}
+
+ {/** Display a message if no tokens are found */}
+ {offersAreLoaded && offers.length === 0 && (
+
+ No Offers found.
+
+ )}
)
}
diff --git a/src/components/app-body/nfts-for-sale/info-button.js b/src/components/app-body/nfts-for-sale/info-button.js
index da71b68..271e9fb 100644
--- a/src/components/app-body/nfts-for-sale/info-button.js
+++ b/src/components/app-body/nfts-for-sale/info-button.js
@@ -23,8 +23,6 @@ function InfoButton (props) {
setShow(true)
}
- console.log('props.token: ', props.token)
-
// Replace with dummy button until token data is loaded.
if (!props.token.tokenData) {
return (
@@ -34,15 +32,9 @@ function InfoButton (props) {
)
}
- // Convert the url property of the token to a link, if it matches common patterns.
- // let url = props.token.tokenurl
- // url = linkIfUrl(props.token.url)
-
- // console.log('props.token: ', props.token)
-
return (
<>
- Info
+ Info
Token Information
@@ -56,7 +48,7 @@ function InfoButton (props) {
Name :
- {props.token.tokenData.tokenStats.name}
+ {props.token.tokenData.genesisData.name}
@@ -70,19 +62,14 @@ function InfoButton (props) {
Decimals :
- {props.token.tokenData.tokenStats.decimals}
+ {props.token.tokenData.genesisData.decimals}
Token Type :
{props.token.tokenType}
- {/*
-
- URL :
- {url}
-
- */}
+
@@ -91,4 +78,4 @@ function InfoButton (props) {
)
}
-export default InfoButton
\ No newline at end of file
+export default InfoButton
diff --git a/src/components/app-body/nfts-for-sale/token-card.js b/src/components/app-body/nfts-for-sale/token-card.js
index 2cb74a4..139f8f3 100644
--- a/src/components/app-body/nfts-for-sale/token-card.js
+++ b/src/components/app-body/nfts-for-sale/token-card.js
@@ -3,63 +3,70 @@
*/
// Global npm libraries
-import React from 'react'
+import React, { useState, useEffect } from 'react'
import { Container, Row, Col, Card } from 'react-bootstrap'
-// import BuyNftButton from './buy-button'
import InfoButton from './info-button'
-// import FlagButton from './flag-button'
-
-// Local libraries
-// import InfoButton from './info-button'
-// import SendTokenButton from './send-token-button'
-// import SellButton from './sell-button'
+import Jdenticon from '@chris.troutner/react-jdenticon'
function TokenCard (props) {
- let imageLink = ''
- if (props.token.mutableData) {
- imageLink = props.token.mutableData.tokenIcon
+ const { token } = props
+ const [icon, setIcon] = useState(token.icon)
+ const [tokenData, setTokenData] = useState(token.tokenData)
- if (props.token.mutableData.fullSizedUrl && props.token.mutableData.fullSizedUrl.includes('http')) {
- imageLink = props.token.mutableData.fullSizedUrl
- }
- }
-
- console.log('TokenCard props.token: ', props.token)
+ // Update icon state every token.icon and token.tokenData changes
+ useEffect(() => {
+ setIcon(token.icon)
+ setTokenData(token.tokenData)
+ }, [token.icon, token.tokenData])
return (
<>
-
- {props.token.icon}
-
-
- {props.token.ticker}
+ {/** If the icon is loaded, display it */
+ icon && (
+ {
+ setIcon(null) // Set the icon to null if it fails to load the image url.
+ }}
+ />
+ )
+ }
+
+ {/** If the icon is not loaded, display the Jdenticon */
+ !icon && (
+
+ )
+ }
+
+ {token.ticker}
- {props.token.tokenData ? props.token.tokenData.tokenStats.name : null}
+ {tokenData && tokenData.genesisData ? tokenData.genesisData.name : null}
Price:
- {props.token.usdPrice}
+ {token.usdPrice}
-
+
-
+
-
+
@@ -69,7 +76,7 @@ function TokenCard (props) {
>
)
}
-
+// eslint-disable-next-line
{/*
@@ -80,6 +87,6 @@ function TokenCard (props) {
- */}
+ */ }
export default TokenCard
diff --git a/src/components/nav-menu/index.js b/src/components/nav-menu/index.js
index 4871352..1e5f8ed 100644
--- a/src/components/nav-menu/index.js
+++ b/src/components/nav-menu/index.js
@@ -68,7 +68,7 @@ function NavMenu (props) {
>
Wallet
-
+
Placeholder2
-
+