Merge pull request #6 from Permissionless-Software-Foundation/dh-wallet-view

feat(wallet): Added minimal-slp-wallet and redux integration
This commit is contained in:
Chris Troutner
2020-06-18 08:19:25 -07:00
committed by GitHub
19 changed files with 3503 additions and 120 deletions
+2 -7
View File
@@ -1,7 +1,2 @@
/**
* Implement Gatsby's Browser APIs in this file.
*
* See: https://www.gatsbyjs.org/docs/browser-apis/
*/
// You can delete this file if you're not using it
import wrapWithProvider from "./wrap-with-provider"
export const wrapRootElement = wrapWithProvider
+7 -6
View File
@@ -1,7 +1,8 @@
/**
* Implement Gatsby's Node APIs in this file.
*
* See: https://www.gatsbyjs.org/docs/node-apis/
*/
// You can delete this file if you're not using it
exports.onCreateWebpackConfig = ({ actions }) => {
actions.setWebpackConfig({
node: {
fs: 'empty'
},
})
}
+2 -7
View File
@@ -1,7 +1,2 @@
/**
* Implement Gatsby's SSR (Server Side Rendering) APIs in this file.
*
* See: https://www.gatsbyjs.org/docs/ssr-apis/
*/
// You can delete this file if you're not using it
import wrapWithProvider from "./wrap-with-provider"
export const wrapRootElement = wrapWithProvider
+2888 -5
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -15,11 +15,13 @@
"gatsby-plugin-sharp": "^2.6.9",
"gatsby-source-filesystem": "^2.3.8",
"gatsby-transformer-sharp": "^2.5.3",
"minimal-slp-wallet": "^1.3.1",
"prop-types": "^15.7.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-helmet": "^6.0.0",
"react-qr-reader": "^2.2.1"
"react-qr-reader": "^2.2.1",
"react-redux": "^7.2.0"
},
"devDependencies": {
"prettier": "2.0.5"
+69 -16
View File
@@ -1,36 +1,40 @@
import React from "react"
import PropTypes from "prop-types"
import Audit from "./audit"
import Configure from "./configure"
import Icons from "./icons"
import Settings from "./settings"
import Wallet from "./wallet"
import AdminLTE, { Sidebar } from "adminlte-2-react"
import ScannerModal from "../qr-scanner/modal"
import Layout from "../layout"
import "./admin-lte.css"
import BchWallet from "minimal-slp-wallet"
import { BrowserRouter as Router } from "react-router-dom"
const { Item } = Sidebar
// Screen width to hide the side menu on click
const MENU_HIDE_WIDTH = 770
const MENU_HIDE_WIDTH = 770
let _this
class AdminLTEPage extends React.Component {
constructor(props) {
super(props)
_this = this
this.state = {
bchBalance: Math.random().toFixed(8),
bchBalance: 0,
showScannerModal: false,
section: "Settings",
section: "Wallet",
menuIsHide: false,
walletInfo: {},
}
_this.BchWallet = BchWallet
}
sidebar = [
<Item icon="fas-cogs" key="Portfolio" text="Settings" activeOn="/" />,
<Item icon="fa-wallet" key="Wallet" text="Wallet" activeOn="/" />,
<Item icon="fas-icons" key="Icons" text="Icons" />,
<Item icon="fas-cog" key="Configure" text="Configure" />,
<Item icon="fa-tablet-alt" key="Audit" text="Audit" />,
@@ -49,7 +53,7 @@ class AdminLTEPage extends React.Component {
<>
<AdminLTE title={["FullStack.cash"]} titleShort={["PSF"]} theme="blue">
<Sidebar.Core>
<Item key="Balance" text="Balance" icon="fab-bitcoin" >
<Item key="Balance" text="Balance" icon="fab-bitcoin">
<div className="sidebar-balance">
<div>
<h3>BCH Balance </h3>
@@ -63,10 +67,16 @@ class AdminLTEPage extends React.Component {
</Sidebar.Core>
<Layout path="/">
<div className="components-container">
{_this.state.section === "Settings" && <Settings />}
{_this.state.section === "Icons" && <Icons />}
{_this.state.section === "Configure" && <Configure />}
{_this.state.section === "Audit" && <Audit />}
{_this.state.section === "Wallet" && (
<Wallet
setWallet={_this.props.setWallet}
walletInfo={_this.props.walletInfo}
updateBalance={_this.props.updateBalance}
/>
)}
{_this.state.section === "Icons" && <Icons />}
{_this.state.section === "Configure" && <Configure />}
{_this.state.section === "Audit" && <Audit />}
</div>
</Layout>
</AdminLTE>
@@ -80,12 +90,49 @@ class AdminLTEPage extends React.Component {
</>
)
}
componentDidMount() {
// Get wallet balance
async getBalance() {
try {
const { mnemonic } = _this.props.walletInfo
if (mnemonic) {
const bchWalletLib = new _this.BchWallet(mnemonic)
await bchWalletLib.walletInfoPromise
const myBalance = await bchWalletLib.getBalance()
_this.props.updateBalance(myBalance)
}
} catch (error) {
console.error(error)
}
}
async componentDidMount() {
_this.customMenuItems()
_this.dropDownBalance()
_this.addOnClickEventToScanner()
_this.activeItemById("Settings")
_this.activeItemById("Wallet")
await _this.updateState()
setTimeout(() => {
_this.getBalance()
}, 250)
}
componentDidUpdate() {
_this.updateState()
}
// Update component state when props change
updateState() {
if (_this.props.walletInfo.mnemonic !== _this.state.walletInfo.mnemonic) {
_this.setState({
walletInfo: _this.props.walletInfo,
})
}
if (_this.props.bchBalance !== _this.state.bchBalance) {
_this.setState({
bchBalance: _this.props.bchBalance,
})
}
}
// Due to that it is not possible to add the "onClick" method
@@ -153,13 +200,12 @@ class AdminLTEPage extends React.Component {
try {
const windowWidth = window.innerWidth
//console.log("Window Width : ",windowWidth)
if(windowWidth > MENU_HIDE_WIDTH) return
if (windowWidth > MENU_HIDE_WIDTH) return
const toggleEle = document.getElementsByClassName("sidebar-toggle")
toggleEle[0].click()
} catch (error) {
console.error(error)
}
}
// Adds the "onClick" event to the QR scanner item
@@ -170,7 +216,6 @@ class AdminLTEPage extends React.Component {
} catch (error) {
console.error(error)
}
}
// Controller to show the QR scanner
@@ -182,6 +227,14 @@ class AdminLTEPage extends React.Component {
showScannerModal: !_this.state.showScannerModal,
})
}
}
// Props prvided by redux
AdminLTEPage.propTypes = {
walletInfo: PropTypes.object.isRequired,
bchBalance: PropTypes.number.isRequired,
setWallet: PropTypes.func.isRequired,
updateBalance: PropTypes.func.isRequired,
}
export default AdminLTEPage
@@ -1,41 +0,0 @@
import React from "react"
import { Row, Col, Box, Button } from "adminlte-2-react"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
class NewWallet extends React.Component {
state = {}
render() {
return (
<Row>
<Col sm={2} />
<Col sm={8}>
<Box className="hover-shadow border-none mt-2">
<Row>
<Col sm={12} className="text-center">
<h1>
<FontAwesomeIcon
className="title-icon"
size="xs"
icon={"plus"}
/>
<span>New Wallet</span>
</h1>
</Col>
<Col sm={12} className="text-center mt-2 mb-2">
<Button
text="Create Wallet"
type="primary"
className="btn-lg"
/>
</Col>
</Row>
</Box>
</Col>
<Col sm={2} />
</Row>
)
}
}
export default NewWallet
@@ -1,24 +0,0 @@
import React from "react"
import { Content } from "adminlte-2-react"
import ImportWallet from "./import"
import NewWallet from "./create"
import InfoWallets from "./info"
class Settings extends React.Component {
state = {}
render() {
return (
<>
<Content>
<NewWallet />
<ImportWallet />
<InfoWallets />
</Content>
</>
)
}
}
export default Settings
+74
View File
@@ -0,0 +1,74 @@
import React from "react"
import PropTypes from "prop-types"
import { Row, Col, Box, Button } from "adminlte-2-react"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import BchWallet from "minimal-slp-wallet"
let _this
class NewWallet extends React.Component {
constructor(props) {
super(props)
_this = this
this.state = {}
_this.BchWallet = BchWallet
}
render() {
return (
<>
<Row>
<Col sm={2} />
<Col sm={8}>
<Box className="hover-shadow border-none mt-2">
<Row>
<Col sm={12} className="text-center">
<h1>
<FontAwesomeIcon
className="title-icon"
size="xs"
icon={"plus"}
/>
<span>New Wallet</span>
</h1>
</Col>
<Col sm={12} className="text-center mt-2 mb-2">
<Button
text="Create Wallet"
type="primary"
className="btn-lg"
onClick={_this.createWallet}
/>
</Col>
</Row>
</Box>
</Col>
<Col sm={2} />
</Row>
</>
)
}
async createWallet() {
try {
const bchWalletLib = new _this.BchWallet()
await bchWalletLib.walletInfoPromise // Wait for wallet to be created.
const walletInfo = bchWalletLib.walletInfo
walletInfo.from = "created"
const myBalance = await bchWalletLib.getBalance()
// Update redux state
_this.props.setWallet(walletInfo)
_this.props.updateBalance(myBalance)
} catch (error) {
console.error(error)
}
}
}
NewWallet.propTypes = {
setWallet: PropTypes.func.isRequired,
updateBalance: PropTypes.func.isRequired,
}
export default NewWallet
@@ -1,7 +1,9 @@
import React from "react"
import PropTypes from "prop-types"
import { Row, Col, Box, Button, Inputs } from "adminlte-2-react"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import BchWallet from "minimal-slp-wallet"
const { Text } = Inputs
let _this
@@ -14,7 +16,9 @@ class ImportWallet extends React.Component {
this.state = {
mnemonic: "",
privateKey: "",
errMsg: "",
}
_this.BchWallet = BchWallet
}
render() {
@@ -44,7 +48,6 @@ class ImportWallet extends React.Component {
placeholder="12 word mnemonic"
label="12 word mnemonic"
labelPosition="above"
maxlength={12}
onChange={_this.handleUpdate}
/>
<Text
@@ -59,9 +62,19 @@ class ImportWallet extends React.Component {
</Col>
</Row>
</Col>
<Col sm={12} className="text-center">
{_this.state.errMsg && (
<p className="error-color">{_this.state.errMsg}</p>
)}
</Col>
<Col sm={12} className="text-center mt-2 mb-2">
<Button text="Import" type="primary" className="btn-lg" />
<Button
text="Import"
type="primary"
className="btn-lg"
onClick={_this.importWallet}
/>
</Col>
</Row>
</Box>
@@ -70,10 +83,9 @@ class ImportWallet extends React.Component {
</Row>
)
}
componentDidMount(){
componentDidMount() {
// add max length property to mnemonic input
document.getElementById("import-mnemonic").maxLength = "12";
//document.getElementById("import-mnemonic").maxLength = "12"
}
handleUpdate(event) {
let value = event.target.value
@@ -83,8 +95,66 @@ class ImportWallet extends React.Component {
_this.setState({
[event.target.name]: value,
})
//console.log(_this.state)
}
async importWallet() {
try {
_this.validateInputs()
const bchWalletLib = new _this.BchWallet(_this.state.mnemonic)
await bchWalletLib.walletInfoPromise // Wait for wallet to be created.
const walletInfo = bchWalletLib.walletInfo
walletInfo.from = "imported"
const myBalance = await bchWalletLib.getBalance()
// Update redux state
_this.props.setWallet(walletInfo)
_this.props.updateBalance(myBalance)
// Reset form and component state
_this.resetValues()
} catch (error) {
console.warn(error)
_this.setState({
errMsg: error.message,
})
}
}
// Reset form and component state
resetValues() {
_this.setState({
mnemonic: "",
privateKey: "",
errMsg: "",
})
const mnemonicEle = document.getElementById("import-mnemonic")
mnemonicEle.value = ""
const privateKeyEle = document.getElementById("privateKey")
privateKeyEle.value = ""
}
validateInputs() {
try {
const { mnemonic } = _this.state
if (mnemonic) {
const spaceCount = mnemonic.split(" ").length //mnemonic.match(/ /g).length
if (spaceCount !== 12) {
console.log("reject")
throw new Error("mnemonic must contain 12 words")
}
} else {
throw new Error("12 word mnemonic is required")
}
} catch (error) {
throw error
}
}
}
ImportWallet.propTypes = {
setWallet: PropTypes.func.isRequired,
updateBalance: PropTypes.func.isRequired,
}
export default ImportWallet
+54
View File
@@ -0,0 +1,54 @@
import React from "react"
import PropTypes from "prop-types"
import { Content } from "adminlte-2-react"
import ImportWallet from "./import"
import NewWallet from "./create"
import InfoWallets from "./info"
import WalletInfo from "./wallet-info"
let _this
class Wallet extends React.Component {
constructor(props) {
super(props)
_this = this
this.state = {}
}
render() {
return (
<>
<Content>
<NewWallet
updateBalance={_this.props.updateBalance}
setWallet={_this.props.setWallet}
/>
{_this.props.walletInfo.mnemonic &&
_this.props.walletInfo.from === "created" && (
<WalletInfo walletInfo={_this.props.walletInfo} />
)}
<ImportWallet
updateBalance={_this.props.updateBalance}
setWallet={_this.props.setWallet}
/>
{_this.props.walletInfo.mnemonic &&
_this.props.walletInfo.from === "imported" && (
<WalletInfo walletInfo={_this.props.walletInfo} />
)}
<InfoWallets />
</Content>
</>
)
}
}
Wallet.propTypes = {
setWallet: PropTypes.func.isRequired,
walletInfo: PropTypes.object.isRequired,
updateBalance: PropTypes.func.isRequired,
}
export default Wallet
@@ -0,0 +1,193 @@
import React from "react"
import PropTypes from "prop-types"
import { Row, Col, Box } from "adminlte-2-react"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import "./wallet.css"
let _this
class WalletInfo extends React.Component {
constructor(props) {
super(props)
_this = this
this.state = {
walletInfo: _this.props.walletInfo,
mnemonic: "",
privateKey: "",
cashAddress: "",
address: "",
slpAddress: "",
legacyAddress: "",
hdPath: "",
}
}
render() {
return (
<Row>
<Col sm={2} />
<Col sm={8}>
<Box className="hover-shadow border-none mt-2">
<Row>
<Col sm={12} className="text-center">
<h1>
<FontAwesomeIcon
className="title-icon"
size="xs"
icon={"wallet"}
/>
<span>My Wallet</span>
</h1>
</Col>
<Col sm={12} className="text-center wallet-info-container">
<Row className="wallet-info-content mt-1 text-left">
<Col xs={10} sm={11}>
<span>
<b>Mnemonic:</b> {_this.state.mnemonic}
</span>
</Col>
<Col xs={2} sm={1}>
<FontAwesomeIcon
className="icon btn-animation"
size="lg"
onClick={() => _this.copyToClipBoard("mnemonic")}
icon={"copy"}
/>
</Col>
</Row>
<Row className="wallet-info-content mt-1 text-left">
<Col xs={10} sm={11}>
<span>
<b>Private Key: </b> {_this.state.privateKey}
</span>
</Col>
<Col xs={2} sm={1}>
<FontAwesomeIcon
className="icon btn-animation"
size="lg"
onClick={() => _this.copyToClipBoard("privateKey")}
icon={"copy"}
/>
</Col>
</Row>
<Row className="wallet-info-content mt-1 text-left">
<Col xs={10} sm={11}>
<span>
<b>Cash Address: </b> {_this.state.cashAddress}
</span>
</Col>
<Col xs={2} sm={1}>
<FontAwesomeIcon
className="icon btn-animation"
size="lg"
onClick={() => _this.copyToClipBoard("cashAddress")}
icon={"copy"}
/>
</Col>
</Row>
<Row className="wallet-info-content mt-1 text-left">
<Col xs={10} sm={11}>
<span>
<b>Address: </b> {_this.state.address}
</span>
</Col>
<Col xs={2} sm={1}>
<FontAwesomeIcon
className="icon btn-animation"
size="lg"
onClick={() => _this.copyToClipBoard("address")}
icon={"copy"}
/>
</Col>
</Row>
<Row className="wallet-info-content mt-1 text-left">
<Col xs={10} sm={11}>
<span>
<b>Slp Address: </b> {_this.state.slpAddress}
</span>
</Col>
<Col xs={2} sm={1}>
<FontAwesomeIcon
className="icon btn-animation"
size="lg"
onClick={() => _this.copyToClipBoard("slpAddress")}
icon={"copy"}
/>
</Col>
</Row>
<Row className="wallet-info-content mt-1 text-left">
<Col xs={10} sm={11}>
<span>
<b>Legacy Address: </b> {_this.state.legacyAddress}
</span>
</Col>
<Col xs={2} sm={1}>
<FontAwesomeIcon
className="icon btn-animation"
size="lg"
onClick={() => _this.copyToClipBoard("legacyAddress")}
icon={"copy"}
/>
</Col>
</Row>
<Row className="wallet-info-content mt-1 text-left">
<Col xs={10} sm={11}>
<span>
<b>HD Path: </b>
<span id="hdpathValue">{_this.state.hdPath}</span>
</span>
</Col>
<Col xs={2} sm={1}>
<FontAwesomeIcon
className="icon btn-animation"
size="lg"
onClick={() => _this.copyToClipBoard("hdPath")}
icon={"copy"}
/>
</Col>
</Row>
</Col>
</Row>
</Box>
</Col>
<Col sm={2} />
</Row>
)
}
// copy info to clipboard
copyToClipBoard(key) {
const val = _this.state[key]
var textArea = document.createElement("textarea")
textArea.value = val //copyText.textContent;
document.body.appendChild(textArea)
textArea.select()
document.execCommand("Copy")
textArea.remove()
}
componentDidMount() {
// set component state
const wallet = _this.props.walletInfo
Object.entries(wallet).forEach(([key, value]) => {
_this.setState({
[key]: value,
})
})
}
componentDidUpdate() {
// set component state
if (_this.props.walletInfo.mnemonic !== _this.state.mnemonic) {
const wallet = _this.props.walletInfo
Object.entries(wallet).forEach(([key, value]) => {
_this.setState({
[key]: value,
})
})
}
}
}
WalletInfo.propTypes = {
walletInfo: PropTypes.object.isRequired,
}
export default WalletInfo
@@ -0,0 +1,17 @@
.wallet-info-content{
/* border:0.7px solid var(--main-color); */
border-radius: 5px;
max-width: 100%;
padding-top: 4px;
padding-bottom: 4px;
margin-right: 0px;
margin-left: 0px;
border-top: none;
border-right: none;
border-left: none;
}
.wallet-info-content div{
overflow:hidden;
white-space:nowrap;
text-overflow: ellipsis;
}
+34 -1
View File
@@ -621,6 +621,12 @@ pre tt:after {
}
}
.text-right{
text-align: right;
}
.text-left{
text-align: left;
}
.text-center{
text-align: center;
}
@@ -661,7 +667,34 @@ pre tt:after {
.mb-3{
margin-bottom: 3em;
}
.mr-1{
margin-right: 1em;
}
.mr-2{
margin-right: 2em;
}
.ml-3{
margin-right: 3em;
}
.ml-1{
margin-left: 1em;
}
.ml-2{
margin-left: 2em;
}
.ml-3{
margin-left: 3em;
}
.title-icon{
margin-right: 7px;
color: var(--main-color);
}
}
.icon{
cursor: pointer;
color: var(--main-color);
}
.btn-animation:active {
box-shadow: 0 5px #666;
transform: translateY(4px);
}
+15
View File
@@ -0,0 +1,15 @@
// Local storage handler
const localStorageWalletKey = "fullstack-wallet-info"
// Detect if the app is running in a browser.
export const isBrowser = () => typeof window !== "undefined"
export const getWalletInfo = () =>
isBrowser() && window.localStorage.getItem(localStorageWalletKey)
? JSON.parse(window.localStorage.getItem(localStorageWalletKey))
: {}
export const setWalletInfo = wallet =>
window.localStorage.setItem(localStorageWalletKey, JSON.stringify(wallet))
+18 -4
View File
@@ -1,16 +1,30 @@
import React from 'react'
import React from "react"
import { connect } from "react-redux"
let AdminLTE =
typeof window !== `undefined`
? require('../components/admin-lte').default
? require("../components/admin-lte").default
: null
const mapStateToProps = ({ walletInfo, bchBalance }) => {
return { walletInfo,bchBalance }
}
const mapDispatchToProps = dispatch => {
return {
setWallet: value => dispatch({ type: `SET_WALLET`, value }),
updateBalance: value =>dispatch({ type: `UPDATE_BALANCE`, value })
}
}
let ConnectedDashboard = AdminLTE
? connect(mapStateToProps, mapDispatchToProps)(AdminLTE)
: null
class AdminLTEPage extends React.Component {
state = {}
render() {
return <>{AdminLTE && <AdminLTE />}</>
return <>{ConnectedDashboard && <ConnectedDashboard />}</>
}
}
+36
View File
@@ -0,0 +1,36 @@
import { createStore as reduxCreateStore } from "redux"
// Wallet from localStorage
import { getWalletInfo, setWalletInfo } from "../components/localWallet"
const reducer = (state, action) => {
// Update walletInfo state property
if (action.type === `SET_WALLET`) {
setWalletInfo(action.value) // Add wallet to local storage
return Object.assign({}, state, {
walletInfo: action.value,
})
}
// Update bchBalance state property
if (action.type === `UPDATE_BALANCE`) {
// Convert satoshis to bch
const satoshis = action.value
const bch = satoshis / 100000000
return Object.assign({}, state, {
bchBalance: Number(bch.toFixed(8)),
})
}
return state
}
// Wallet info from local storage
const localWallet = getWalletInfo()
const initialState = {
walletInfo: localWallet.mnemonic ? localWallet : {},
bchBalance: 0,
}
const createStore = () => reduxCreateStore(reducer, initialState)
export default createStore
+13
View File
@@ -0,0 +1,13 @@
import React from "react"
import { Provider } from "react-redux"
import createStore from "./src/redux/createStore"
// eslint-disable-next-line react/display-name,react/prop-types
export default ({ element }) => {
// Instantiating store in `wrapRootElement` handler ensures:
// - there is fresh store for each SSR page
// - it will be called only once in browser, when React mounts
const store = createStore()
return <Provider store={store}>{element}</Provider>
}