mirror of
https://github.com/Permissionless-Software-Foundation/psf-memo-client.git
synced 2026-09-21 16:52:02 -07:00
Merge pull request #2 from Permissionless-Software-Foundation/ct-unstable
Nav Menu used to bring up different Views
This commit is contained in:
@@ -10,8 +10,10 @@ This app can be compiled and uploaded to Filecoin via [web3.storage](https://web
|
|||||||
- [react-bootstrap](https://react-bootstrap.github.io/) is used for general style and layout control.
|
- [react-bootstrap](https://react-bootstrap.github.io/) is used for general style and layout control.
|
||||||
- An easily customized *waiting modal* component can be invoked while waiting for network calls to complete.
|
- An easily customized *waiting modal* component can be invoked while waiting for network calls to complete.
|
||||||
- [minimal-slp-wallet](https://www.npmjs.com/package/minimal-slp-wallet) is used to access tokens and BCH on the Bitcoin Cash blockchain.
|
- [minimal-slp-wallet](https://www.npmjs.com/package/minimal-slp-wallet) is used to access tokens and BCH on the Bitcoin Cash blockchain.
|
||||||
- A 'server selection' dropdown allows the user to select from an array of redundant back end servers.
|
- A 'server selection' dropdown allows the user to select from an array of redundant [web3 back end servers](https://cashstack.info).
|
||||||
- This site is statically compiled, uploaded to Filecoin, and served over IPFS for censorship resistance and version control.
|
- This site is statically compiled, uploaded to Filecoin, and served over IPFS for censorship resistance and version control.
|
||||||
|
- A collapsible navigation menu is used to load different views.
|
||||||
|
- This app can be compiled into an native Android app using [react-bootstrap-web3-android](https://github.com/Permissionless-Software-Foundation/react-bootstrap-web3-android).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "nft-browser-v2",
|
"name": "react-bootstrap-web3-spa",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "nft-browser-v2",
|
"name": "react-bootstrap-web3-spa",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "0.27.2",
|
"axios": "0.27.2",
|
||||||
|
|||||||
+22
-32
@@ -10,13 +10,12 @@ import { useQueryParam, StringParam } from 'use-query-params'
|
|||||||
// Local libraries
|
// Local libraries
|
||||||
import './App.css'
|
import './App.css'
|
||||||
import LoadScripts from './components/load-scripts'
|
import LoadScripts from './components/load-scripts'
|
||||||
// import NFTs from './components/nfts'
|
|
||||||
import WaitingModal from './components/waiting-modal'
|
import WaitingModal from './components/waiting-modal'
|
||||||
import AsyncLoad from './services/async-load'
|
import AsyncLoad from './services/async-load'
|
||||||
import ServerSelect from './components/servers'
|
import ServerSelect from './components/servers'
|
||||||
import Footer from './components/footer'
|
import Footer from './components/footer'
|
||||||
import GetBalance from './components/balance'
|
|
||||||
import NavMenu from './components/nav-menu'
|
import NavMenu from './components/nav-menu'
|
||||||
|
import AppBody from './components/app-body'
|
||||||
|
|
||||||
// Default restURL for a back-end server.
|
// Default restURL for a back-end server.
|
||||||
let serverURL = 'https://free-bch.fullstack.cash'
|
let serverURL = 'https://free-bch.fullstack.cash'
|
||||||
@@ -32,13 +31,13 @@ class App extends React.Component {
|
|||||||
|
|
||||||
// Working array for storing modal output.
|
// Working array for storing modal output.
|
||||||
this.modalBody = []
|
this.modalBody = []
|
||||||
this.tokenData = {}
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
walletInitialized: false,
|
walletInitialized: false,
|
||||||
wallet: false,
|
wallet: false,
|
||||||
modalBody: this.modalBody,
|
modalBody: this.modalBody,
|
||||||
hideSpinner: false
|
hideSpinner: false,
|
||||||
|
menuState: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cnt = 0
|
this.cnt = 0
|
||||||
@@ -56,27 +55,6 @@ class App extends React.Component {
|
|||||||
|
|
||||||
const wallet = await this.asyncLoad.initWallet(serverURL)
|
const wallet = await this.asyncLoad.initWallet(serverURL)
|
||||||
|
|
||||||
// this.addToModal('Getting Group Token Information')
|
|
||||||
|
|
||||||
// Get Group Token info
|
|
||||||
// const groupData = await this.asyncLoad.getGroupData(groupTokenId)
|
|
||||||
// console.log(`groupData: ${JSON.stringify(groupData, null, 2)}`)
|
|
||||||
|
|
||||||
// this.addToModal('Getting NFT Information')
|
|
||||||
|
|
||||||
/// Get NFT child info
|
|
||||||
// const nftData = []
|
|
||||||
// for (let i = 0; i < groupData.nfts.length; i++) {
|
|
||||||
// const tokenData = await this.asyncLoad.getTokenData(groupData.nfts[i])
|
|
||||||
// nftData.push(tokenData)
|
|
||||||
// }
|
|
||||||
// console.log(`nft data: ${JSON.stringify(nftData, null, 2)}`)
|
|
||||||
|
|
||||||
// this.tokenData = {
|
|
||||||
// groupData,
|
|
||||||
// nftData
|
|
||||||
// }
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
wallet,
|
wallet,
|
||||||
walletInitialized: true
|
walletInitialized: true
|
||||||
@@ -96,13 +74,14 @@ class App extends React.Component {
|
|||||||
|
|
||||||
render () {
|
render () {
|
||||||
// console.log('App component rendered. this.state.wallet: ', this.state.wallet)
|
// console.log('App component rendered. this.state.wallet: ', this.state.wallet)
|
||||||
|
// console.log(`App component menuState: ${this.state.menuState}`)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<GetRestUrl />
|
<GetRestUrl />
|
||||||
<LoadScripts />
|
<LoadScripts />
|
||||||
<NavMenu />
|
<NavMenu menuHandler={this.onMenuClick} />
|
||||||
{this.state.walletInitialized ? <InitializedView wallet={this.state.wallet} tokens={this.tokenData} /> : <UninitializedView modalBody={this.state.modalBody} hideSpinner={this.state.hideSpinner} />}
|
{this.state.walletInitialized ? <InitializedView wallet={this.state.wallet} menuState={this.state.menuState} /> : <UninitializedView modalBody={this.state.modalBody} hideSpinner={this.state.hideSpinner} />}
|
||||||
<ServerSelect />
|
<ServerSelect />
|
||||||
<Footer />
|
<Footer />
|
||||||
</>
|
</>
|
||||||
@@ -117,6 +96,17 @@ class App extends React.Component {
|
|||||||
modalBody: this.modalBody
|
modalBody: this.modalBody
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This handler is passed into the child menu component. When an item in the
|
||||||
|
// nav menu is clicked, this handler will update the state. The state is
|
||||||
|
// used by the AppBody component to determine which View component to display.
|
||||||
|
onMenuClick (menuState) {
|
||||||
|
// console.log('menuState: ', menuState)
|
||||||
|
|
||||||
|
_this.setState({
|
||||||
|
menuState
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is rendered *before* the BCH wallet is initialized.
|
// This is rendered *before* the BCH wallet is initialized.
|
||||||
@@ -132,14 +122,18 @@ function UninitializedView (props) {
|
|||||||
|
|
||||||
// This is rendered *after* the BCH wallet is initialized.
|
// This is rendered *after* the BCH wallet is initialized.
|
||||||
function InitializedView (props) {
|
function InitializedView (props) {
|
||||||
|
// console.log(`InitializedView props.menuState: ${props.menuState}`)
|
||||||
|
// console.log(`InitializedView _this.state.menuState: ${_this.state.menuState}`)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<br />
|
<br />
|
||||||
<GetBalance wallet={_this.state.wallet} />
|
<AppBody menuState={_this.state.menuState} wallet={props.wallet} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the restURL query parameter.
|
||||||
function GetRestUrl (props) {
|
function GetRestUrl (props) {
|
||||||
const [restURL] = useQueryParam('restURL', StringParam)
|
const [restURL] = useQueryParam('restURL', StringParam)
|
||||||
// console.log('restURL: ', restURL)
|
// console.log('restURL: ', restURL)
|
||||||
@@ -149,8 +143,4 @@ function GetRestUrl (props) {
|
|||||||
return (<></>)
|
return (<></>)
|
||||||
}
|
}
|
||||||
|
|
||||||
// function sleep (ms) {
|
|
||||||
// return new Promise(resolve => setTimeout(resolve, ms))
|
|
||||||
// }
|
|
||||||
|
|
||||||
export default App
|
export default App
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
This Body component is a container for all the different Views of the app.
|
||||||
|
Views are equivalent to 'pages' in a multi-page app. Views are hidden or
|
||||||
|
displayed to simulate the use of pages in an SPA.
|
||||||
|
The Body app contains all the Views and chooses which to show, based on
|
||||||
|
the state of the Menu component.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Global npm libraries
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
// Local libraries
|
||||||
|
import GetBalance from '../balance'
|
||||||
|
import TakePicture from '../take-picture'
|
||||||
|
|
||||||
|
let _this
|
||||||
|
|
||||||
|
class AppBody extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
activeView: 0,
|
||||||
|
menuState: props.menuState,
|
||||||
|
wallet: props.wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
_this = this
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
// console.log(`AppBody menu state: ${this.props.menuState}`)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{this.chooseView(this.props.menuState)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
chooseView (menuState) {
|
||||||
|
// console.log(`chooseView() menuState: ${menuState}`)
|
||||||
|
|
||||||
|
switch (menuState) {
|
||||||
|
case 0:
|
||||||
|
return (<GetBalance wallet={_this.state.wallet} />)
|
||||||
|
case 1:
|
||||||
|
return (<TakePicture />)
|
||||||
|
default:
|
||||||
|
return (<GetBalance wallet={_this.state.wallet} />)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AppBody
|
||||||
@@ -9,7 +9,7 @@ import { Container, Row, Col, Form, Button, Spinner } from 'react-bootstrap'
|
|||||||
let _this
|
let _this
|
||||||
|
|
||||||
class GetBalance extends React.Component {
|
class GetBalance extends React.Component {
|
||||||
constructor(props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -31,10 +31,10 @@ class GetBalance extends React.Component {
|
|||||||
<Form>
|
<Form>
|
||||||
<Form.Group className='mb-3' controlId='formBasicEmail'>
|
<Form.Group className='mb-3' controlId='formBasicEmail'>
|
||||||
<Form.Label>Enter a BCH address to check the balance.</Form.Label>
|
<Form.Label>Enter a BCH address to check the balance.</Form.Label>
|
||||||
<Form.Control type='text' placeholder='bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d' onChange={e => this.setState({textInput: e.target.value})} />
|
<Form.Control type='text' placeholder='bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d' onChange={e => this.setState({ textInput: e.target.value })} />
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
|
||||||
<Button variant='primary' onClick={this.getBalance}>
|
<Button variant='primary' onClick={this.handleGetBalance}>
|
||||||
Check Balance
|
Check Balance
|
||||||
</Button>
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
@@ -51,13 +51,13 @@ class GetBalance extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBalance(event) {
|
async handleGetBalance (event) {
|
||||||
try {
|
try {
|
||||||
const textInput = _this.state.textInput
|
const textInput = _this.state.textInput
|
||||||
|
|
||||||
// Exit on invalid input
|
// Exit on invalid input
|
||||||
if(!textInput) return
|
if (!textInput) return
|
||||||
if(!textInput.includes('bitcoincash:')) return
|
if (!textInput.includes('bitcoincash:')) return
|
||||||
|
|
||||||
_this.setState({
|
_this.setState({
|
||||||
balance: (<span>Retrieving balance... <Spinner animation='border' /></span>)
|
balance: (<span>Retrieving balance... <Spinner animation='border' /></span>)
|
||||||
@@ -71,7 +71,7 @@ class GetBalance extends React.Component {
|
|||||||
_this.setState({
|
_this.setState({
|
||||||
balance: `Balance: ${balance} sats, ${bchBalance} BCH`
|
balance: `Balance: ${balance} sats, ${bchBalance} BCH`
|
||||||
})
|
})
|
||||||
} catch(err) {
|
} catch (err) {
|
||||||
_this.setState({
|
_this.setState({
|
||||||
balance: `Error: ${err.message}`
|
balance: `Error: ${err.message}`
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,52 +7,53 @@
|
|||||||
|
|
||||||
// Global npm libraries
|
// Global npm libraries
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Nav, Navbar, NavDropdown, Image } from "react-bootstrap"
|
import { Nav, Navbar, Image } from 'react-bootstrap'
|
||||||
import Logo from './psf-logo.png'
|
import Logo from './psf-logo.png'
|
||||||
// import { ReactComponent as Logo } from "./psf-logo.png"
|
|
||||||
|
|
||||||
class NavMenu extends React.Component {
|
class NavMenu extends React.Component {
|
||||||
constructor(props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
console.log('NavMenu')
|
|
||||||
|
// This is the event handler passed in from the Parent component.
|
||||||
|
this.parentMenuHandler = props.menuHandler
|
||||||
|
|
||||||
|
// this.state = {
|
||||||
|
// selectedMenuItem: 0
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render () {
|
||||||
return(
|
// console.log(`selectedMenuItem: ${this.state.selectedMenuItem}`)
|
||||||
|
// this.handleMenuClick(this.state.selectedMenuItem)
|
||||||
|
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
<Navbar collapseOnSelect expand="xxxl" bg="dark" variant="dark" style={{paddingRight: '20px'}} >
|
<Navbar collapseOnSelect expand='xxxl' bg='dark' variant='dark' style={{ paddingRight: '20px' }}>
|
||||||
<Navbar.Brand href="#home" style={{paddingLeft: '20px'}}>
|
<Navbar.Brand href='#home' style={{ paddingLeft: '20px' }}>
|
||||||
<Image src={Logo} thumbnail width="50" />{' '}
|
<Image src={Logo} thumbnail width='50' />{' '}
|
||||||
PSF Web3 Demo
|
PSF Web3 Demo
|
||||||
</Navbar.Brand>
|
</Navbar.Brand>
|
||||||
|
|
||||||
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
|
<Navbar.Toggle aria-controls='responsive-navbar-nav' />
|
||||||
<Navbar.Collapse id="responsive-navbar-nav">
|
<Navbar.Collapse id='responsive-navbar-nav'>
|
||||||
<Nav className="mr-auto">
|
<Nav className='mr-auto'>
|
||||||
<Nav.Link href="#features">Placeholder 1</Nav.Link>
|
<Nav.Link href='#' onClick={() => this.handleClickEvent(0)}>Check Balance</Nav.Link>
|
||||||
<NavDropdown title="Dropdown" id="collasible-nav-dropdown">
|
<Nav.Link href='#' onClick={() => this.handleClickEvent(1)}>Take Picture</Nav.Link>
|
||||||
<NavDropdown.Item href="#action/3.1">Action</NavDropdown.Item>
|
<Nav.Link href='#'>Placeholder 3</Nav.Link>
|
||||||
<NavDropdown.Item href="#action/3.2">
|
|
||||||
Another action
|
|
||||||
</NavDropdown.Item>
|
|
||||||
<NavDropdown.Item href="#action/3.3">Something</NavDropdown.Item>
|
|
||||||
<NavDropdown.Divider />
|
|
||||||
<NavDropdown.Item href="#action/3.4">
|
|
||||||
Separated link
|
|
||||||
</NavDropdown.Item>
|
|
||||||
</NavDropdown>
|
|
||||||
</Nav>
|
|
||||||
<Nav>
|
|
||||||
<Nav.Link href="#deets">Placeholder 2</Nav.Link>
|
|
||||||
<Nav.Link eventKey={2} href="#memes">
|
|
||||||
Placeholder 3
|
|
||||||
</Nav.Link>
|
|
||||||
</Nav>
|
</Nav>
|
||||||
</Navbar.Collapse>
|
</Navbar.Collapse>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleClickEvent (menuItem) {
|
||||||
|
// Update the
|
||||||
|
// this.setState({selectedMenuItem: menuItem})
|
||||||
|
|
||||||
|
// Pass the selected menu item up to the parent component.
|
||||||
|
this.parentMenuHandler(menuItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NavMenu
|
export default NavMenu
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
This is a View component that allows the user to take a picture with their
|
||||||
|
webcam or phone.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Global npm libraries
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
class TakePicture extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
console.log('Take Picture component loaded')
|
||||||
|
|
||||||
|
// this.state = {
|
||||||
|
// activeView: 0,
|
||||||
|
// menuState: props.menuState
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p>Take Picture Component Placeholder</p>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TakePicture
|
||||||
Reference in New Issue
Block a user