Compare commits

...
11 Commits
Author SHA1 Message Date
Chris Troutner d708d88c3c Merge pull request #41 from Permissionless-Software-Foundation/dh-loading-time
feat(nostr): used '#' to reduce page loading time on profile page
2025-07-30 08:03:39 -07:00
Daniel Gonzalez f80c43f35e feat(nostr): used '#' to reduce page loading time on profile page 2025-07-29 20:00:14 -04:00
Daniel Gonzalez 3d73b7943d Fixed public read npub 2025-07-29 18:22:18 -04:00
Daniel Gonzalez 13ad04cde1 Fixed profile npub 2025-07-29 18:15:50 -04:00
Chris Troutner f3dd9f3d07 Merge pull request #40 from Permissionless-Software-Foundation/dh-pfp
feat(nostr): Added PFPs on /content-creators page
2025-07-29 10:44:58 -07:00
Daniel Gonzalez 5e6a90f560 feat(nostr): Added PFPs on /content-creators page 2025-07-28 16:47:18 -04:00
Chris Troutner ce410ddc9c Merge pull request #39 from Permissionless-Software-Foundation/dh-like-posts
feat(nostr): Implemented 'Like' buttons functionality
2025-07-26 06:06:34 -07:00
Daniel Gonzalez 67bd90be8f feat(nostr): Implemented 'Like' buttons functionality 2025-07-25 18:01:09 -04:00
Chris Troutner 10ef73f542 Merge pull request #38 from Permissionless-Software-Foundation/dh-extra-data
feat(nostr): Implemented extra profile data
2025-07-25 06:45:44 -07:00
Chris Troutner 0ad9147e25 Merge branch 'master' into dh-extra-data 2025-07-25 06:43:08 -07:00
Daniel Gonzalez 323f9e4b0a feat(nostr): Implemented extra profile data 2025-07-22 20:26:51 -04:00
9 changed files with 506 additions and 148 deletions
+36 -3
View File
@@ -14,6 +14,7 @@ import NavMenu from './components/nav-menu'
import useAppState from './hooks/state'
import { UninitializedView, InitializedView } from './components/starter-views'
const sigleViewPaths = ['/profile']
function App (props) {
// Load all the app state into a single object that can be passed to child
// components.
@@ -30,12 +31,44 @@ function App (props) {
})
}, [])
const isSignleView = useCallback(async () => {
// Get current path
const currentPath = window.location.pathname
// Get hash from url
const hash = window.location.hash
const allowedPath = sigleViewPaths.find((val) => { return currentPath.match(val) })
if (hash === '#single-view' && allowedPath) {
addToModal('Loading minimal-slp-wallet', appData)
const asyncLoad = new AsyncLoad()
if (!appData.wallet) {
await asyncLoad.loadWalletLib()
const walletTemp = await asyncLoad.initStarterWallet(appData.serverUrl, appData.lsState.mnemonic, appData)
appData.setWallet(walletTemp)
}
// Update Modal State
appData.setIsSingleView(true)
appData.setHideSpinner(true)
appData.setShowStartModal(false)
appData.setDenyClose(false)
/* // Update the startup state.
appData.setAsyncInitFinished(true)
appData.setAsyncInitSucceeded(false) */
return true
}
return false
}, [appData, addToModal])
/** Load all required data before component start. */
useEffect(() => {
async function asyncEffect () {
console.log('asyncInitStarted: ', appData.asyncInitStarted)
const singleView = await isSignleView()
if (!appData.asyncInitStarted) {
console.log('asyncInitStarted: ', appData.asyncInitStarted)
console.log('is single view : ', singleView)
if (!appData.asyncInitStarted && !singleView) {
try {
// Instantiate the async load object.
const asyncLoad = new AsyncLoad()
@@ -112,7 +145,7 @@ function App (props) {
}
}
asyncEffect()
}, [appData, addToModal])
}, [appData, addToModal, isSignleView])
return (
<>
@@ -3,7 +3,7 @@
*/
import React, { useState, useEffect } from 'react'
import { Card } from 'react-bootstrap'
import { Card, Spinner } from 'react-bootstrap'
import Jdenticon from '@chris.troutner/react-jdenticon'
import CopyOnClick from '../../bch-wallet/copy-on-click.js'
import FollowBtn from './follow-btn.js'
@@ -11,16 +11,21 @@ import FollowBtn from './follow-btn.js'
function ContentCard (props) {
const { creator, followList, refreshFollowList } = props
const [profile, setProfile] = useState(null)
const [imageError, setImageError] = useState(false)
useEffect(() => {
setProfile(creator?.profile || {})
setProfile(creator?.profile)
}, [creator.profile])
const goToProfile = () => {
const profileUrl = `${window.location.origin}/profile/${creator.npub}`
const profileUrl = `${window.location.origin}/profile/${creator.npub}#single-view`
window.open(profileUrl, '_blank')
}
const handleImageError = (type) => {
setImageError(true)
}
return (
<>
@@ -34,9 +39,33 @@ function ContentCard (props) {
</div>
<div className='d-none d-md-flex align-items-center gap-4 cursor-pointer'>
{/* Profile Picture */}
<div className='flex-shrink-0' onClick={goToProfile}>
<Jdenticon size='140' value={creator.npub} />
</div>
{(profile && profile.picture && !imageError) && (
<div className='flex-shrink-0 max-w-50' onClick={goToProfile}>
<img
src={profile.picture}
alt='Profile'
className='rounded-circle shadow'
style={{ objectFit: 'cover', width: '140px' }}
onError={() => handleImageError('picture')}
/>
</div>
)}
{/* Default profile Picture */}
{((profile && !profile.picture) || imageError) && (
<div className='flex-shrink-0' onClick={goToProfile}>
<Jdenticon size='140' value={creator.npub} />
</div>
)}
{/** */}
{!profile && (
<div
className='flex-shrink-0'
style={{ width: '140px', height: '140px', display: 'flex', justifyContent: 'center', alignItems: 'center' }}
>
<Spinner size='140' />
</div>
)}
{/* Creator Info */}
<div className='flex-grow-1'>
@@ -113,9 +142,33 @@ function ContentCard (props) {
{/* 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} onClick={goToProfile} />
</div>
{(profile && profile.picture && !imageError) && (
<div className='flex-shrink-0 max-w-50 mb-2' onClick={goToProfile}>
<img
src={profile.picture}
alt='Profile'
className='rounded-circle shadow'
style={{ objectFit: 'cover', width: '100px' }}
onError={() => handleImageError('picture')}
/>
</div>
)}
{/* Default profile Picture */}
{((profile && !profile.picture) || imageError) && (
<div className='flex-shrink-0 mb-2' onClick={goToProfile}>
<Jdenticon size='100' value={creator.npub} />
</div>
)}
{/** */}
{!profile && (
<div
className='flex-shrink-0'
style={{ width: '100px', height: '100px', display: 'flex', justifyContent: 'center', alignItems: 'center' }}
>
<Spinner size='100' />
</div>
)}
{/* Creator Info */}
<div className='w-100 mb-3'>
@@ -5,22 +5,87 @@
import React, { useCallback, useEffect, useState } from 'react'
import { Card, Spinner } from 'react-bootstrap'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faUser } from '@fortawesome/free-solid-svg-icons'
import { faUser, faHeart as faHeartSolid } from '@fortawesome/free-solid-svg-icons'
import { faHeart } from '@fortawesome/free-regular-svg-icons'
import * as nip19 from 'nostr-tools/nip19'
import NostrFormat from '../nostr-format'
import { finalizeEvent } from 'nostr-tools/pure'
import { Relay } from 'nostr-tools/relay'
import { hexToBytes } from '@noble/hashes/utils' // already an installed dependency
import { RelayPool } from 'nostr'
function FeedCard (props) {
const { post, appData, profiles } = props
const { nostrKeyPair } = appData.bchWalletState
const [profile, setProfile] = useState(profiles[post.pubkey])
const [npub, setNpub] = useState('')
const [isClicked, setIsClicked] = useState(false)
const [isLiked, setIsLiked] = useState(false)
const [likesCount, setLikesCount] = useState(null)
const [likesFetched, setLikesFetched] = useState(false)
// function to fetch a post likes reaction
const handleLikes = useCallback(async (post, userPubKey) => {
try {
const psf = 'wss://nostr-relay.psfoundation.info'
const res = await new Promise((resolve) => {
let likesCnt = 0
let userLikedCnt = 0
const pool = RelayPool([psf])
pool.on('open', relay => {
relay.subscribe('subid', { kinds: [7], '#e': [post.id] })
})
pool.on('eose', relay => {
relay.close()
resolve({
count: likesCnt,
userLiked: userLikedCnt > 0
})
})
pool.on('event', (relay, subId, ev) => {
try {
// Count likes
if (ev.content === '+') {
likesCnt++
// Count user likes
if (ev.pubkey === userPubKey) {
userLikedCnt++
}
}
// Count dislikes
if (ev.content === '-') {
likesCnt--
// Count user dislikes.
if (ev.pubkey === userPubKey) {
userLikedCnt--
}
}
} catch (error) {
// skip error
}
})
})
setLikesCount(res.count)
setIsLiked(res.userLiked)
setLikesFetched(true)
} catch (error) {
console.warn(error)
}
}, [])
// Get npub from pubkey
useEffect(() => {
const npub = nip19.npubEncode(post.pubkey)
setNpub(npub)
}, [post])
// Handle post likes
if (!likesFetched && nostrKeyPair.pubHex) {
handleLikes(post, nostrKeyPair.pubHex)
}
}, [post, nostrKeyPair, handleLikes, likesFetched])
// Verify if pubkey profile exists into the profiles state
useEffect(() => {
@@ -41,6 +106,58 @@ function FeedCard (props) {
return npub.slice(0, 8) + '...' + npub.slice(-5)
}, [])
const submitLike = async (e) => {
// Post on nostr network
e.preventDefault()
try {
const { nostrKeyPair } = appData.bchWalletState
setLikesFetched(false)
// Convert private key to binary
const privateKeyBin = hexToBytes(nostrKeyPair.privHex)
// Relay list
const psf = 'wss://nostr-relay.psfoundation.info'
// Define if like or dislike
let content = '+'
if (isLiked) { content = '-' }
// Generate a post.
const eventTemplate = {
kind: 7,
created_at: Math.floor(Date.now() / 1000),
tags: [
['e', post.id, psf],
['p', psf, nostrKeyPair.pubHex]
],
content
}
console.log(`eventTemplate: ${JSON.stringify(eventTemplate, null, 2)}`)
// Sign the post
const signedEvent = finalizeEvent(eventTemplate, privateKeyBin)
console.log('signedEvent: ', signedEvent)
// Connect to a relay.
const relay = await Relay.connect(psf)
console.log(`connected to ${relay.url}`)
// Publish the message to the relay.
const result = await relay.publish(signedEvent)
console.log('result: ', result)
// Close the connection to the relay.
relay.close()
await handleLikes(post, nostrKeyPair.pubHex)
setLikesFetched(true)
} catch (error) {
console.warn(error)
setLikesFetched(true)
}
}
return (
<Card className='mb-4 bg-light rounded-4 shadow-sm border-0'>
<Card.Body className='p-3'>
@@ -91,6 +208,50 @@ function FeedCard (props) {
<NostrFormat content={post.content} />
</div>
</div>
<div className='d-flex justify-content-end'>
{likesFetched && (
<button
onClick={submitLike}
className='btn btn-link text-decoration-none p-0 d-flex align-items-center gap-2'
style={{
border: 'none',
background: 'transparent',
transition: 'all 0.2s ease',
color: isLiked ? '#e31b23' : '#536471',
fontSize: '14px',
fontWeight: '400'
}}
onMouseEnter={(e) => {
e.target.style.transform = 'scale(1.05)'
}}
onMouseLeave={(e) => {
e.target.style.transform = 'scale(1)'
}}
>
<FontAwesomeIcon
icon={isLiked ? faHeartSolid : faHeart}
style={{
fontSize: '16px',
color: isLiked ? '#e31b23' : '#536471',
transition: 'all 0.2s ease'
}}
/>
<span
style={{
fontSize: '14px',
fontWeight: '400',
color: isLiked ? '#e31b23' : '#536471',
transition: 'all 0.2s ease'
}}
>
{likesCount || 0}
</span>
</button>
)}
{!likesFetched && (
<Spinner animation='border' size='sm' className='ms-2' />
)}
</div>
</Card.Body>
</Card>
@@ -6,24 +6,22 @@ import React, { useEffect, useState } from 'react'
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 { faUser, faGlobe } from '@fortawesome/free-solid-svg-icons'
import NostrFormat from '../nostr-format'
import { RelayPool } from 'nostr'
import * as nip19 from 'nostr-tools/nip19'
function ProfileRead (props) {
const { npub } = props
const { bchWalletState } = props.appData
console.log('npub prop', npub)
const { setProfile } = props
const [post, setPost] = useState({})
const [loaded, setLoaded] = useState(false)
const [imageError, setImageError] = useState({ picture: false, banner: false })
useEffect(() => {
const start = () => {
const pubHexData = nip19.decode(npub)
const pubHex = pubHexData.data
console.log('pubhex', pubHex)
const psf = 'wss://nostr-relay.psfoundation.info'
const pool = RelayPool([psf])
@@ -39,10 +37,14 @@ function ProfileRead (props) {
pool.on('event', (relay, subId, ev) => {
console.log('Received event:', ev)
const profile = JSON.parse(ev.content)
console.log('Profile:', profile)
setPost(profile)
setProfile(profile)
try {
const profile = JSON.parse(ev.content)
console.log('Profile:', profile)
setPost(profile)
setProfile(profile)
} catch (error) {
console.error('Error parsing profile:', error)
}
})
setLoaded(true)
@@ -51,40 +53,101 @@ function ProfileRead (props) {
if (!loaded && npub) {
start()
}
}, [bchWalletState, loaded, setProfile, npub])
}, [loaded, setProfile, npub])
const handleImageError = (type) => {
setImageError(prev => ({ ...prev, [type]: true }))
}
const handleImageLoad = (type) => {
setImageError(prev => ({ ...prev, [type]: false }))
}
return (
<Container>
<div className='d-flex flex-column flex-md-row align-items-center gap-4 mb-5 p-3 p-md-5 bg-light rounded-4 shadow-sm'>
<div style={{ width: '100px', height: '100px' }} className='mb-3 mb-md-0'>
<div
className='rounded-circle bg-gradient shadow d-flex align-items-center justify-content-center'
style={{
width: '100%',
height: '100%',
background: 'linear-gradient(45deg, #6c757d, #495057)'
}}
>
<FontAwesomeIcon icon={faUser} size='3x' color='#7c7c7d' />
</div>
{/* Banner Section */}
{post.banner && !imageError.banner && (
<div className='position-relative'>
<img
src={post.banner}
alt='Profile banner'
className='w-100 rounded-4 shadow-sm'
style={{ height: '200px', objectFit: 'cover' }}
onError={() => handleImageError('banner')}
onLoad={() => handleImageLoad('banner')}
/>
</div>
)}
<div className='d-flex flex-column flex-md-row align-items-center gap-4 mb-5 p-3 p-md-5 bg-light rounded-4 shadow-sm'>
{/* Profile Picture */}
<div style={{ width: '120px', height: '120px' }} className='mb-3 mb-md-0'>
{post.picture && !imageError.picture
? (
<img
src={post.picture}
alt='Profile'
className='rounded-circle shadow w-100 h-100'
style={{ objectFit: 'cover' }}
onError={() => handleImageError('picture')}
onLoad={() => handleImageLoad('picture')}
/>
)
: (
<div
className='rounded-circle bg-gradient shadow d-flex align-items-center justify-content-center'
style={{
width: '100%',
height: '100%',
background: 'linear-gradient(45deg, #6c757d, #495057)'
}}
>
<FontAwesomeIcon icon={faUser} size='3x' color='#7c7c7d' />
</div>
)}
</div>
{/* Profile Information */}
<div className='flex-grow-1 text-center text-md-start mb-3 mb-md-0 d-flex flex-column align-items-center align-items-md-start'>
<h3 className='mb-2 fw-bold'>{post.name}</h3>
<h3 className='mb-2 fw-bold'>{post.name || 'username'}</h3>
<div className='text-muted small mb-3 d-flex align-items-center flex-column flex-md-row'>
<span className='text-truncate me-2 mb-2 mb-md-0'>
<span className='d-md-none'>
{`${bchWalletState.nostrKeyPair.npub.slice(0, 8)}...${bchWalletState.nostrKeyPair.npub.slice(-5)}`}
{`${npub?.slice(0, 8)}...${npub?.slice(-5)}`}
</span>
<span className='d-none d-md-inline'>
{bchWalletState.nostrKeyPair.npub}
{npub}
</span>
</span>
<CopyOnClick walletProp='npub' appData={props.appData} value={bchWalletState.nostrKeyPair.npub} />
</div>
<div className='fs-6 text-secondary'>
<NostrFormat content={post.about} />
<CopyOnClick walletProp='npub' appData={props.appData} value={npub} />
</div>
{/* About Section */}
{post.about && (
<div className='fs-6 text-secondary'>
<NostrFormat content={post.about} />
</div>
)}
{/* Website Link */}
{post.website && (
<div className='mb-3 d-flex align-items-center gap-2'>
<FontAwesomeIcon icon={faGlobe} className='text-muted' size='sm' />
<a
href={post.website.startsWith('http') ? post.website : `https://${post.website}`}
target='_blank'
rel='noopener noreferrer'
className='text-decoration-none text-muted small'
style={{ wordBreak: 'break-all' }}
>
{post.website}
</a>
</div>
)}
</div>
{/* Action Buttons */}
<div className='d-flex gap-2 align-items-center flex-wrap justify-content-center'>
<Button
variant='outline-danger'
@@ -79,7 +79,7 @@ function PublicRead (props) {
<div className='flex-grow-1'>
<div className='fw-bold mb-1'>{props.profile?.name}</div>
<small className='text-muted'>
{`${bchWalletState.nostrKeyPair.npub.slice(0, 8)}...${bchWalletState.nostrKeyPair.npub.slice(-5)}`}
{`${npub.slice(0, 8)}...${npub.slice(-5)}`}
</small>
</div>
<small className='text-muted'>
@@ -91,7 +91,7 @@ function SlpTokensDisplay (props) {
const getTokens = async () => {
try {
const { nostrKeyPair } = bchWalletState
if (nostrKeyPair.npub === npub) {
if (nostrKeyPair?.npub === npub) {
setTokens(bchWalletState.slpTokens)
setLoaded(true)
return
+107 -103
View File
@@ -15,8 +15,7 @@ import Logo from './psf-logo.png'
function NavMenu (props) {
// Get the current path
const { currentPath, bchWalletState } = props.appData
const { currentPath, bchWalletState, isSingleView } = props.appData
// Navbar state
const [expanded, setExpanded] = useState(false)
@@ -25,125 +24,130 @@ function NavMenu (props) {
// Collapse the navbar
setExpanded(false)
}
const goToHome = () => {
if (isSingleView) {
window.location.href = '/'
}
}
return (
<>
<Navbar expanded={expanded} onToggle={setExpanded} expand='xxxl' bg='dark' variant='dark' style={{ paddingRight: '20px' }}>
<Navbar.Brand href='#home' style={{ paddingLeft: '20px' }}>
<Navbar.Brand href='#home' style={{ paddingLeft: '20px' }} onClick={goToHome}>
<Image src={Logo} thumbnail width='50' />{' '}
DEX
</Navbar.Brand>
<Navbar.Toggle aria-controls='responsive-navbar-nav' />
<Navbar.Collapse id='responsive-navbar-nav'>
<Nav className='mr-auto'>
{!isSingleView && <Navbar.Toggle aria-controls='responsive-navbar-nav' />}
{!isSingleView && (
<Navbar.Collapse id='responsive-navbar-nav'>
<Nav className='mr-auto'>
<NavLink
className={(currentPath === '/nfts-for-sale' || currentPath === '/') ? 'nav-link-active' : 'nav-link-inactive'}
to='/nfts-for-sale'
onClick={handleClickEvent}
>
NFTs
</NavLink>
<NavLink
className={(currentPath === '/nfts-for-sale' || currentPath === '/') ? 'nav-link-active' : 'nav-link-inactive'}
to='/nfts-for-sale'
onClick={handleClickEvent}
>
NFTs
</NavLink>
<NavLink
className={currentPath === '/offers' ? 'nav-link-active' : 'nav-link-inactive'}
to='/offers'
onClick={handleClickEvent}
>
Fungible Tokens
</NavLink>
<NavLink
className={currentPath === '/offers' ? 'nav-link-active' : 'nav-link-inactive'}
to='/offers'
onClick={handleClickEvent}
>
Fungible Tokens
</NavLink>
<hr />
<hr />
<NavLink
className={currentPath === '/global-feed' ? 'nav-link-active' : 'nav-link-inactive'}
to='/feeds'
onClick={handleClickEvent}
>
Feeds
</NavLink>
<NavLink
className={currentPath === '/nostr-post' ? 'nav-link-active' : 'nav-link-inactive'}
to='/nostr-post'
onClick={handleClickEvent}
>
Nostr Post
</NavLink>
<NavLink
className={currentPath === '/profile' ? 'nav-link-active' : 'nav-link-inactive'}
to={`/profile/${bchWalletState?.nostrKeyPair?.npub}`}
onClick={handleClickEvent}
>
Nostr Profile
</NavLink>
<NavLink
className={currentPath === '/content-creators' ? 'nav-link-active' : 'nav-link-inactive'}
to='/content-creators'
onClick={handleClickEvent}
>
Content Creators
</NavLink>
<NavLink
className={currentPath === '/global-feed' ? 'nav-link-active' : 'nav-link-inactive'}
to='/feeds'
onClick={handleClickEvent}
>
Feeds
</NavLink>
<NavLink
className={currentPath === '/nostr-post' ? 'nav-link-active' : 'nav-link-inactive'}
to='/nostr-post'
onClick={handleClickEvent}
>
Nostr Post
</NavLink>
<NavLink
className={currentPath === '/profile' ? 'nav-link-active' : 'nav-link-inactive'}
to={`/profile/${bchWalletState?.nostrKeyPair?.npub}`}
onClick={handleClickEvent}
>
Nostr Profile
</NavLink>
<NavLink
className={currentPath === '/content-creators' ? 'nav-link-active' : 'nav-link-inactive'}
to='/content-creators'
onClick={handleClickEvent}
>
Content Creators
</NavLink>
<hr />
<hr />
<NavLink
className={(currentPath === '/bch') ? 'nav-link-active' : 'nav-link-inactive'}
to='/bch'
onClick={handleClickEvent}
>
BCH
</NavLink>
<NavLink
className={(currentPath === '/bch') ? 'nav-link-active' : 'nav-link-inactive'}
to='/bch'
onClick={handleClickEvent}
>
BCH
</NavLink>
<NavLink
className={currentPath === '/slp-tokens' ? 'nav-link-active' : 'nav-link-inactive'}
to='/slp-tokens'
onClick={handleClickEvent}
>
Tokens
</NavLink>
<NavLink
className={currentPath === '/slp-tokens' ? 'nav-link-active' : 'nav-link-inactive'}
to='/slp-tokens'
onClick={handleClickEvent}
>
Tokens
</NavLink>
<NavLink
className={currentPath === '/wallet' ? 'nav-link-active' : 'nav-link-inactive'}
to='/wallet'
onClick={handleClickEvent}
>
Wallet
</NavLink>
<NavLink
className={currentPath === '/wallet' ? 'nav-link-active' : 'nav-link-inactive'}
to='/wallet'
onClick={handleClickEvent}
<NavLink
className={(currentPath === '/balance') ? 'nav-link-active' : 'nav-link-inactive'}
to='/balance'
onClick={handleClickEvent}
>
Check Balance
</NavLink>
>
Wallet
</NavLink>
<NavLink
className={(currentPath === '/sweep') ? 'nav-link-active' : 'nav-link-inactive'}
to='/sweep'
onClick={handleClickEvent}
>
Sweep
</NavLink>
<NavLink
className={(currentPath === '/sign') ? 'nav-link-active' : 'nav-link-inactive'}
to='/sign'
onClick={handleClickEvent}
>
Sign
</NavLink>
<NavLink
className={currentPath === '/configuration' ? 'nav-link-active' : 'nav-link-inactive'}
to='/configuration'
onClick={handleClickEvent}
>
Configuration
</NavLink>
<NavLink
className={(currentPath === '/balance') ? 'nav-link-active' : 'nav-link-inactive'}
to='/balance'
onClick={handleClickEvent}
>
Check Balance
</NavLink>
<NavLink
className={(currentPath === '/sweep') ? 'nav-link-active' : 'nav-link-inactive'}
to='/sweep'
onClick={handleClickEvent}
>
Sweep
</NavLink>
<NavLink
className={(currentPath === '/sign') ? 'nav-link-active' : 'nav-link-inactive'}
to='/sign'
onClick={handleClickEvent}
>
Sign
</NavLink>
<NavLink
className={currentPath === '/configuration' ? 'nav-link-active' : 'nav-link-inactive'}
to='/configuration'
onClick={handleClickEvent}
>
Configuration
</NavLink>
</Nav>
</Navbar.Collapse>
</Nav>
</Navbar.Collapse>
)}
</Navbar>
</>
)
+4 -1
View File
@@ -39,6 +39,7 @@ function useAppState () {
const [modalBody, setModalBody] = useState([])
const [hideSpinner, setHideSpinner] = useState(false)
const [denyClose, setDenyClose] = useState(false)
const [isSingleView, setIsSingleView] = useState(false)
// NFTs for sale stored data to improve performance
const [nftForSaleCacheData, setNftForSaleCacheData] = useState(lsState.nftData || {})
@@ -153,7 +154,9 @@ function useAppState () {
setNftForSaleCacheData,
updateNFTCachedData,
lastFeedTab,
setLastFeedTab
setLastFeedTab,
isSingleView,
setIsSingleView
}
}
+41
View File
@@ -283,6 +283,47 @@ class AsyncLoad {
npub
}
}
// Initialize the BCH wallet without initialize() promise
async initStarterWallet (restURL, mnemonic, appData) {
try {
const options = {
interface: 'consumer-api',
restURL,
noUpdate: true
}
let wallet
if (mnemonic) {
// Load the wallet from the mnemonic, if it's available from local storage.
wallet = new this.BchWallet(mnemonic, options)
} else {
// Generate a new mnemonic and wallet.
wallet = new this.BchWallet(null, options)
}
await wallet.walletInfoPromise
// Get Nostr key pair from WIF
const nostrKeyPair = this.nostrKeyPairFromWIF(wallet.walletInfo.privateKey)
const walletInfo = wallet.walletInfo
walletInfo.nostrKeyPair = nostrKeyPair
// Update the state of the wallet.
appData.updateBchWalletState({ walletObj: walletInfo, appData })
// Save the mnemonic to local storage.
if (!mnemonic) {
const newMnemonic = wallet.walletInfo.mnemonic
appData.updateLocalStorage({ mnemonic: newMnemonic })
}
this.wallet = wallet
return wallet
} catch (error) {
console.error('Error initStarterWallet: ', error)
throw error
}
}
}
function sleep (ms) {