diff --git a/src/components/admin-lte/orders/details.js b/src/components/admin-lte/orders/details.js new file mode 100644 index 0000000..e70f009 --- /dev/null +++ b/src/components/admin-lte/orders/details.js @@ -0,0 +1,188 @@ +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 JSONPretty from "react-json-pretty" + +let _this +class Details extends React.Component { + constructor (props) { + super(props) + + _this = this + + this.state = { + order: {} + } + } + + render () { + const { order } = _this.state + return ( + <> + + + + + +

+ + Details +

+ {order._id && ( + +

+ Message Type: + {order.messageType} +

+

+ Message Class: + {order.messageClass} +

+

+ Create At: + {new Date(order.localTimestamp).toLocaleString()} +

+

+ ID: + {order._id} +

+

+ Token Id: + {order.tokenId} + +

+

+ Order Type : + {order.buyOrSell} + +

+

+ Rate in Stats : + {order.rateInSats} + +

+

+ Min. Sats to Exchange : + {order.minSatsToExchange} + +

+

+ Num Tokens: + {order.numTokens} + +

+

+ Utxo TxId: + {order.utxoTxid} + +

+

+ Utxo Vout: + {order.utxoVout} + +

+

+ Tx Hex : + {order.txHex} + +

+ +

+ Timestamp : + {order.timestamp} + +

+

+ LocalTimestamp : + {order.localTimestamp} + +

+

+ p2wdbHash : + {order.p2wdbHash} + +

+

+ Offer Hash : + {order.offerHash} + +

+

+ Address References : + {order.addrReferences} + +

+
+ )} + + +
+
+ +
+
+ +
+ + ) + } + + componentDidMount () { + _this.handleData() + } + + componentDidUpdate () { + if (_this.props.order._id !== _this.state.order._id) { + _this.handleData() + } + } + + handleData () { + try { + const { order } = _this.props + console.log('order', order) + _this.setState({ + order: order.data + }) + } catch (err) { + console.warn('Error in handleData()', err) + } + } + + handleClose () { + _this.props.onClose() + } + + // Detects if the input is a string and converts to json object + isJson (data) { + try { + JSON.parse(data) + return true + } catch (error) { + return false + } + } +} +Details.propTypes = { + order: PropTypes.object, + onClose: PropTypes.func, + onTake: PropTypes.func +} +export default Details diff --git a/src/components/admin-lte/orders/index.js b/src/components/admin-lte/orders/index.js new file mode 100644 index 0000000..cd457e8 --- /dev/null +++ b/src/components/admin-lte/orders/index.js @@ -0,0 +1,321 @@ +import React from 'react' +import { Row, Col, Content, Box, DataTable, Button, Inputs } from 'adminlte-2-react' +import Details from './details' +import './orders.css' +import Spinner from '../../../images/loader.gif' +const { Text } = Inputs + +const axios = require('axios').default +const siteConfig = require('../../site-config') + +let SERVER = siteConfig.bchDexUrl + +// const EXPLORER_URL = 'https://explorer.bitcoin.com/bch/tx/' + +let _this +class Orders extends React.Component { + constructor (props) { + super(props) + _this = this + this.state = { + showEntry: false, + data: [], + orders: [], + orderData: null, + showTakeModal: false, + takeInput: '' + } + + this.firstColumns = [ + { title: 'Ticker', data: 'ticker' }, + { + title: 'Token Id', + data: 'tokenId', + render: id => ( + {id.subString} + ) + }, + { + title: 'Type', + data: 'buyOrSell' + }, + { + title: 'Offer hash', + data: 'offerHash', + render: hash => ( + {hash.subString} + ) + }, + { title: 'Qty', data: 'qty' }, + { title: 'Sats Each', data: 'satsEach' }, + { title: 'Sats Total', data: 'satsTotal' }, + { title: 'USD Total', data: 'usdTotal' }, + { + title: '', + data: 'take', + // render: order => (order.data.orderStatus === 'posted' + // ? ( + //
+ //
+ // ) + // :

+ // ) + render: order => ( +

+
+ ) + } + ] + } + + render () { + const { data, orderData } = _this.state + return ( + <> + + + {orderData && ( + +
+ + )} + + + { + console.log(data) + if (data.key === 'hashHandler') { _this.handleHashClick(data) } + + if (data.key === 'takeHandler') { _this.handleTake(data) } + } + }} + /> + + + + + { + _this.state.showTakeModal && ( + +
+
+ Loading...
+

Taking the other side of the trade...

+
+
+
+ ) + } + + ) + } + + async componentDidMount () { + _this.handleOrders() + + // Get data and update the table + // every 20 seconds + setInterval(() => { + _this.handleOrders() + }, 30000) + } + + async handleOrders () { + const orders = await _this.getOrders() + await _this.generateDataTable(orders) + } + + // REST petition to Get data fron the pw2db + async getOrders () { + try { + // console.log('SERVER: ', SERVER) + + const options = { + method: 'GET', + url: `${SERVER}/offer/list/`, + data: {} + } + const result = await axios.request(options) + // console.log('result.data', result.data) + + _this.setState({ + orders: result.data + }) + + return result.data + } catch (err) { + console.warn('Error in getOrders() ', err) + } + } + + // Generate table content + async generateDataTable (dataArr = []) { + try { + const data = [] + // console.log(`dataArr: ${JSON.stringify(dataArr, null, 2)}`) + + if(!this.props.bchWallet) { + console.log(`BCH wallet is not initialized. Can not calculate price of BCH or tokens.`) + return + } + + // Get the spot price for BCH. + const bchSpotPrice = await this.props.bchWallet.getUsd() + + // console.log('bchSpotPrice: ', bchSpotPrice) + + for (let i = 0; i < dataArr.length; i++) { + const order = dataArr[i] + // console.log(`order: ${JSON.stringify(order, null, 2)}`) + + const satsTotal = order.numTokens * order.rateInBaseUnit + let usdTotal = bchSpotPrice * this.props.bchWallet.bchjs.BitcoinCash.toBitcoinCash(satsTotal) + usdTotal = `$${this.props.bchWallet.bchjs.Util.floor8(usdTotal)}` + + const row = { + ticker: order.ticker, + tokenId: { + tokenId: order.tokenId, + subString: _this.cutString(order.tokenId) + }, + // createdAt row data + createdAt: new Date(order.timestamp).toLocaleString(), + // Transaction id row data + buyOrSell: order.buyOrSell, + // Hash row data + offerHash: { + key: 'hashHandler', + subString: _this.cutString(order.p2wdbHash), + order: order.p2wdbHash, + data: order + }, + qty: order.numTokens, + satsEach: order.rateInBaseUnit, + satsTotal, + usdTotal, + orderStatus: order.offerStatus, + take: { + key: 'takeHandler', + data: order + } + } + data.push(row) + } + + _this.setState({ data }) + } catch (err) { + console.warn('Error in generateDataTable() ', err) + } + } + + cutString (txid) { + try { + const subTxid = txid.slice(0, 4) + const subTxid2 = txid.slice(-4) + return `${subTxid}...${subTxid2}` + } catch (err) { + console.warn('Error in cutString() ', err) + } + } + + handleHashClick (data) { + try { + // data.isValid = data.isValid.toString() + _this.setState({ + orderData: data + }) + } catch (err) { + _this.setState({ + orderData: null + }) + console.warn('Error in handleHashClick() ', err) + } + } + + handleClose () { + _this.setState({ + orderData: null, + showTakeModal: false + }) + } + + async handleTake (order) { + console.log('handleTake() order: ', order) + + const offerCid = order.data.p2wdbHash + console.log('offerCid: ', offerCid) + + // if (!order.data.status === 'posted') return + + // TODO: Future functionality. + // Throw up a modal to query how much they want to take. + // _this.setState({ + // tankenInput: '', + // showTakeModal: true + // }) + + // Show the modal + _this.setState({ + showTakeModal: true + }) + + const options = { + method: 'POST', + url: `${SERVER}/offer/take/`, + data: { + offerCid + } + } + const result = await axios.request(options) + console.log('result of taking offer: ', result) + + // Hide the modal + _this.setState({ + showTakeModal: false + }) + } + + handleModalInputs (event) { + const value = event.target.value + _this.setState({ + [event.target.name]: value + }) + } +} + +export default Orders diff --git a/src/components/admin-lte/orders/orders.css b/src/components/admin-lte/orders/orders.css new file mode 100644 index 0000000..c102b42 --- /dev/null +++ b/src/components/admin-lte/orders/orders.css @@ -0,0 +1,29 @@ +.btn-close-entry{ + width: 150px; +} +.action-handler{ + color: #3c8dbc; + cursor: pointer; +} +.details-data-content{ + text-align: left; +} +.take-form-wrapper{ + /* display: flex; + justify-content: center; + flex-direction: column; */ + text-align: center; +} +#OrdersTable> tbody >.odd{ + height: 55px!important; +} +#OrdersTable> tbody > .even{ + height: 55px!important; +} +.take-btn-lg{ + width: 150px; +} +.take-btn-table-wrapper{ + text-align: end; + padding-right: 10px; +} \ No newline at end of file diff --git a/src/components/admin-lte/tokens/index.js b/src/components/admin-lte/tokens/index.js index 8531486..00f5aef 100644 --- a/src/components/admin-lte/tokens/index.js +++ b/src/components/admin-lte/tokens/index.js @@ -3,11 +3,14 @@ import PropTypes from 'prop-types' import { Content, Row, Col, Box, Button } from 'adminlte-2-react' import TokenCard from './token-card' import TokenModal from './token-modal' +import SellModal from './sell-modal' import Spinner from '../../../images/loader.gif' // import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import SendTokens from './send-tokens' // import { SlpMutableData } from 'slp-mutable-data' + let _this + class Tokens extends React.Component { constructor (props) { super(props) @@ -16,6 +19,7 @@ class Tokens extends React.Component { tokens: [], selectedTokenToView: '', showModal: false, + showSellModal: false, inFetch: true, errMsg: '', selectedTokenToSend: '', @@ -99,6 +103,7 @@ class Tokens extends React.Component { id={`token-${i}`} token={val} showToken={_this.showToken} + showSellModal={_this.showSellModal} selectToken={_this.selectToken} /> @@ -112,6 +117,7 @@ class Tokens extends React.Component { )} )} + + + ) } @@ -196,6 +214,14 @@ class Tokens extends React.Component { _this.onHandleToggleModal() } + // This is called by the token-card when the 'Sell' button is clicked. + showSellModal (selectedTokenToView) { + _this.setState({ + selectedTokenToView + }) + _this.onHandleToggleSellModal() + } + selectToken (selectedTokenToSend) { _this.setState({ selectedTokenToSend @@ -215,6 +241,15 @@ class Tokens extends React.Component { } } + onHandleToggleSellModal (refresh = null) { + _this.setState({ + showSellModal: !_this.state.showSellModal + }) + if (refresh) { + _this.handleGetTokens(true) + } + } + handleError (error) { let errMsg = '' if (error.message) { diff --git a/src/components/admin-lte/tokens/sell-modal.js b/src/components/admin-lte/tokens/sell-modal.js new file mode 100644 index 0000000..a047e6a --- /dev/null +++ b/src/components/admin-lte/tokens/sell-modal.js @@ -0,0 +1,429 @@ +/* + This modal controlls the selling of tokens. + It was adapted from token-modal.js and contains references to 'burn' tokens. +*/ + +// Global npm libraries +import React from 'react' +import PropTypes from 'prop-types' +import { Content, Row, Col, Box, Button, Inputs } from 'adminlte-2-react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import axios from 'axios' + +// Local libraries +import './token.css' +const siteConfig = require('../../site-config') + +let _this +const { Text } = Inputs + +class SellModal extends React.Component { + constructor (props) { + super(props) + _this = this + this.state = { + copySuccess: '', + isBurnView: false, + txId: '', + errMsg: '', + inFetch: false, + sellQty: 1, + pricePerToken: 0.02 + } + + this.modalFooter = ( + <> +