Merge pull request #2 from Permissionless-Software-Foundation/ct-unstable

Nav Menu used to bring up different Views
This commit is contained in:
Chris Troutner
2022-07-03 11:41:13 -07:00
committed by GitHub
7 changed files with 150 additions and 72 deletions
+3 -1
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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
+55
View File
@@ -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
+7 -7
View File
@@ -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}`
}) })
+31 -30
View File
@@ -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
+30
View File
@@ -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