feat(token): Added token view to the app

This commit is contained in:
Daniel Gonzalez
2020-06-27 00:12:07 -04:00
parent bc74b76cd8
commit a9dcd367a4
12 changed files with 462 additions and 51 deletions
+24
View File
@@ -6151,6 +6151,11 @@
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001084.tgz",
"integrity": "sha512-ftdc5oGmhEbLUuMZ/Qp3mOpzfZLCxPYKcvGv6v2dJJ+8EdqcvZRbAGOiLmkM/PV1QGta/uwBs8/nCl6sokDW6w=="
},
"canvas-renderer": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/canvas-renderer/-/canvas-renderer-2.1.1.tgz",
"integrity": "sha512-/V0XetN7s1Mk3NO7x2wxPZYv0pLMQtGAhecuOuKR88beiYCUle1AbCcFZNLu+4NVzi9RVHS0rXtIgzPEaKidLw=="
},
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
@@ -24855,6 +24860,15 @@
"resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz",
"integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg=="
},
"jdenticon": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/jdenticon/-/jdenticon-2.2.0.tgz",
"integrity": "sha512-WGqwpjN9pab/Sah9pGnFH5tQc3HF3WbLV/tPVbykvk5nuAkxG/zhzQYWC2owvpnS+/A0HmlSx35rtY8kyN+x7Q==",
"requires": {
"@types/node": "*",
"canvas-renderer": "~2.1.1"
}
},
"jest-diff": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz",
@@ -29646,6 +29660,16 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"react-jdenticon": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/react-jdenticon/-/react-jdenticon-0.0.8.tgz",
"integrity": "sha512-+oVYV1Py3JBaCZIvOeOiuj9wmpLLDFGjsp7SCDXtai9l5khhiE2caDYubt/xZEcqlO8DyALcb+ZgRgPgXY70Zg==",
"requires": {
"jdenticon": "^2.2.0",
"prop-types": "^15.7.2",
"react": "^16.8.0"
}
},
"react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
+1
View File
@@ -20,6 +20,7 @@
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-helmet": "^6.0.0",
"react-jdenticon": "0.0.8",
"react-qr-reader": "^2.2.1",
"react-redux": "^7.2.0"
},
+1 -1
View File
@@ -31,7 +31,7 @@
text-transform: lowercase;
}
.components-container{
min-height: calc(100vh - 100px);
min-height: calc(100vh - var(--footer-height) - 50px);
}
.sidebar-toggle:hover{
-44
View File
@@ -1,44 +0,0 @@
import React from "react"
import { Content, Row, Col, Box } from "adminlte-2-react"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
class Icons extends React.Component {
state = {}
render() {
return (
<Content>
<Row>
<Col sm={3} />
<Col sm={6}>
<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>Upload your token icon</span>
</h1>
</Col>
<Col sm={12} className="text-center mt-2 mb-2">
<p>
You currently have 0 BCH. Go to the Portfolio page and
Create a Wallet. Then deposit some BCH to use the SLP Icons
tool. Get free BCH from the Bitcoin.com Faucet!
</p>
</Col>
</Row>
</Box>
</Col>
<Col sm={3} />
</Row>
</Content>
)
}
}
export default Icons
+6 -2
View File
@@ -2,7 +2,7 @@ import React from "react"
import PropTypes from "prop-types"
import Audit from "./audit"
import Configure from "./configure"
import Tokens from "./icons"
import Tokens from "./tokens"
import Wallet from "./wallet"
import AdminLTE, { Sidebar, Navbar } from "adminlte-2-react"
@@ -71,7 +71,11 @@ class AdminLTEPage extends React.Component {
setBchWallet={_this.props.setBchWallet}
/>
)}
{_this.state.section === "Tokens" && <Tokens />}
{_this.state.section === "Tokens" &&
<Tokens
walletInfo={_this.props.walletInfo}
bchWallet={_this.props.bchWallet} />
}
{_this.state.section === "Configure" && (
<Configure
walletInfo={_this.props.walletInfo}
+134
View File
@@ -0,0 +1,134 @@
import React from "react"
import PropTypes from "prop-types"
import { Content, Row, Col, Box } from "adminlte-2-react"
import TokenCard from "./token-card"
import TokenModal from "./token-modal"
import Spinner from "../../../images/loader.gif"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
let _this
class Tokens extends React.Component {
constructor(props) {
super(props)
_this = this
this.state = {
tokens: [],
selectedToken: "",
showModal: false,
inFetch: true,
onEmptyTokensMsg: "No tokens found on this wallet..",
}
}
render() {
const { JWT } = _this.props.walletInfo
return (
<>
{_this.state.inFetch ? (
<div className="spinner">
<img alt="Loading..." src={Spinner} width={100} />
</div>
) : (
<Content>
{!JWT && (
<Box padding="true" className="container-nofound">
<Row>
<Col xs={12}>
<FontAwesomeIcon
className="icon btn-animation"
size="lg"
icon={"exclamation-triangle"}
/>
</Col>
<Col xs={12}>
<em>
You don't have a registered JWT, you could encounter
errors viewing some of your tokens.
</em>
</Col>
</Row>
</Box>
)}
<br></br>
{!_this.state.tokens.length && (
<Box padding="true" className="container-nofound">
<Row>
<Col xs={12}>
<em>{_this.state.onEmptyTokensMsg}</em>
</Col>
</Row>
</Box>
)}
{_this.state.tokens.length && (
<Row>
{_this.state.tokens.map((val, i) => {
return (
<Col sm={4} key={`token-${i}`}>
<TokenCard
key={`token-${i}`}
id={`token-${i}`}
token={val}
showToken={_this.showToken}
/>
</Col>
)
})}
</Row>
)}
</Content>
)}
<TokenModal
token={_this.state.selectedToken ? _this.state.selectedToken : {}}
onHide={_this.toggleModal}
show={_this.state.showModal}
/>
</>
)
}
async componentDidMount() {
const { mnemonic } = _this.props.walletInfo
const bchWallet = _this.props.bchWallet
let tokens = []
let { onEmptyTokensMsg } = _this.state
if (mnemonic && bchWallet) {
try {
await bchWallet.walletInfoPromise
tokens = await bchWallet.listTokens()
} catch (error) {
console.error(error)
if (error.error.match("rate limits")) onEmptyTokensMsg = error.error
}
} else {
onEmptyTokensMsg =
"You need to create or import a wallet first, to view tokens"
}
_this.setState({
tokens,
inFetch: false,
onEmptyTokensMsg,
})
}
showToken(selectedToken) {
console.log("selectedToken", selectedToken)
_this.setState({
selectedToken,
})
_this.toggleModal()
}
toggleModal() {
_this.setState({
showModal: !_this.state.showModal,
})
}
}
Tokens.propTypes = {
walletInfo: PropTypes.object.isRequired, // wallet info
bchWallet: PropTypes.object, // get minimal-slp-wallet instance
}
export default Tokens
@@ -0,0 +1,64 @@
import React from "react"
import PropTypes from "prop-types"
import { Row, Col, Box, Button } from "adminlte-2-react"
import Jdenticon from "react-jdenticon"
import "./token.css"
let _this
class TokenCard extends React.Component {
constructor(props) {
super(props)
_this = this
this.state = {}
}
render() {
const token = _this.props.token
return (
<>
<Box className="hover-shadow border-none mt-2" id="token-card">
<Row className="text-center">
<Col sm={12} className="text-center mt-2 ">
<Jdenticon size="100" value={token.tokenId} />
<hr></hr>
</Col>
<Col sm={12} className="flex justify-content-center ">
<div className="info-container">
<p className="info-content">
<b>Ticker: </b>
<span> {token.ticker}</span>
</p>
<p className="info-content">
<b>Name: </b>
<span>{token.name}</span>
</p>
<p className="info-content">
<b>Balance</b>
<span>{token.qty}</span>
</p>
</div>
</Col>
<Col sm={12} className="text-center ">
<Button
text="Show Token"
type="primary"
className="btn-lg"
onClick={() => {
_this.props.showToken(token)
}}
/>
</Col>
</Row>
</Box>
</>
)
}
shouldComponentUpdate() {
return false
}
}
TokenCard.propTypes = {
token: PropTypes.object.isRequired,
showToken: PropTypes.func.isRequired,
}
export default TokenCard
@@ -0,0 +1,163 @@
import React from "react"
import PropTypes from "prop-types"
import { Content, Row, Col, Box, Button } from "adminlte-2-react"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import "./token.css"
let _this
class TokenModal extends React.Component {
constructor(props) {
super(props)
_this = this
this.state = {}
}
modalFooter = (
<React.Fragment>
<Button text="Close" pullLeft onClick={this.props.onHide} />
</React.Fragment>
)
render() {
const token = _this.props.token
return (
<>
<Content
title={token.name}
modal
modalFooter={this.modalFooter}
show={_this.props.show}
modalCloseButton={true}
onHide={_this.props.onHide}
>
<Row>
<Col sm={12}>
<Box className=" border-none ">
<Row>
<Col
sm={12}
className="text-center tokenModal-info-container"
>
<Row className="tokenModal-info-content mt-1 text-left">
<Col xs={12}>
<Row>
<Col xs={12} sm={3}>
<b>TokenId:</b>
</Col>
<Col xs={10} sm={8}>
{token.tokenId}
</Col>
<Col xs={2} sm={1}>
<FontAwesomeIcon
className="icon btn-animation"
size="lg"
onClick={() => _this.copyToClipBoard("tokenId")}
icon={"copy"}
/>
</Col>
</Row>
</Col>
</Row>
<Row className="tokenModal-info-content mt-1 text-left">
<Col xs={12}>
<Row>
<Col xs={12} sm={3}>
<b>URL:</b>
</Col>
<Col xs={12} sm={9}>
<a
href={`http://${token.url}`}
target="_blank"
rel="noreferrer"
>
{token.url}
</a>
</Col>
</Row>
</Col>
</Row>
<Row className="tokenModal-info-content mt-1 text-left">
<Col xs={12}>
<Row>
<Col xs={12} sm={3}>
<b>Ticker:</b>
</Col>
<Col xs={12} sm={9}>
{token.ticker}
</Col>
</Row>
</Col>
</Row>
<Row className="tokenModal-info-content mt-1 text-left">
<Col xs={12}>
<Row>
<Col xs={12} sm={3}>
<b>Name:</b>
</Col>
<Col xs={12} sm={9}>
{token.name}
</Col>
</Row>
</Col>
</Row>
<Row className="tokenModal-info-content mt-1 text-left">
<Col xs={12}>
<Row>
<Col xs={12} sm={3}>
<b>Balance:</b>
</Col>
<Col xs={12} sm={9}>
{token.qty}
</Col>
</Row>
</Col>
</Row>
<Row className="tokenModal-info-content mt-1 text-left">
<Col xs={12}>
<Row>
<Col xs={12} sm={3}>
<b>Decimals:</b>
</Col>
<Col xs={12} sm={9}>
{token.decimals}
</Col>
</Row>
</Col>
</Row>
<Row className="tokenModal-info-content mt-1 text-left">
<Col xs={12}>
<Row>
<Col xs={12} sm={3}>
<b>TokenType:</b>
</Col>
<Col xs={12} sm={9}>
{token.tokenType}
</Col>
</Row>
</Col>
</Row>
</Col>
</Row>
</Box>
</Col>
</Row>
</Content>
</>
)
}
// copy info to clipboard
copyToClipBoard(key) {
const val = _this.props.token[key]
var textArea = document.createElement("textarea")
textArea.value = val //copyText.textContent;
document.body.appendChild(textArea)
textArea.select()
document.execCommand("Copy")
textArea.remove()
}
}
TokenModal.propTypes = {
token: PropTypes.object.isRequired,
show: PropTypes.bool.isRequired,
onHide: PropTypes.func.isRequired,
}
export default TokenModal
+50
View File
@@ -0,0 +1,50 @@
#token-card .info-container{
width: fit-content;
}
#token-card .info-container .info-content{
display: flex;
justify-content: space-between;
font-size: medium;
}
#token-card .info-container .info-content b{
margin-right: 15px;
}
#token-card .divider{
width: 100%;
border-bottom: 1px solid black;
}
.tokenModal-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;
font-size: medium;
}
.tokenModal-info-content div{
overflow:hidden;
white-space:nowrap;
text-overflow: ellipsis;
}
.tokenModal-info-content div b{
margin-right: 5px;
}
.container-nofound {
text-align: center;
max-width: 80%;
margin: 0 auto;
color: rgba(0, 0, 0, 0.7);
font-size: large;
}
.spinner{
display: flex;
height: calc(100vh - var(--footer-height) - 50px);
width: 100%!important;
justify-content: center;
align-items: center;
}
+11 -1
View File
@@ -2,7 +2,7 @@
:root {
--main-color:#00A74F;
--footer-height: 170px;
}
.skin-blue .main-header .logo {
@@ -20,3 +20,13 @@
background-color: var(--main-color)!important;
border-color: var(--main-color)!important;
}
.modal-header{
background-color: var(--main-color);
}
.modal-header button , .modal-header h4{
color: white;
opacity: inherit;
}
.box{
border-color: var(--main-color)!important;
}
+1 -1
View File
@@ -1,7 +1,7 @@
#footer {
width: 100%;
/*max-height: 50px;*/
/*min-height: 50px;*/
min-height: var(--footer-height);
background-color: var(--main-color);
/*display: flex;
justify-content: center;
+7 -2
View File
@@ -637,9 +637,14 @@ pre tt:after {
justify-content: center;
}
.justify-item-center{
justify-items: center;
align-items: center;
}
.max-width{
width: 100%!important;
}
.max-height{
height: 100vh!important;
}
.border-none{
border: none!important ;