diff --git a/package-lock.json b/package-lock.json index 620797ef..2aa539ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "hathor-admin", - "version": "0.18.1", + "version": "0.19.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hathor-admin", - "version": "0.18.1", + "version": "0.19.0", "dependencies": { - "@hathor/wallet-lib": "1.4.0", + "@hathor/wallet-lib": "1.8.0", "@unleash/proxy-client-react": "1.0.4", - "axios": "0.17.1", + "axios": "1.7.2", "bootstrap": "4.6.2", "d3-selection": "1.4.2", "d3-zoom": "1.8.3", @@ -2233,28 +2233,44 @@ } }, "node_modules/@hathor/wallet-lib": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@hathor/wallet-lib/-/wallet-lib-1.4.0.tgz", - "integrity": "sha512-GDPTADTMlsciIZ36S09rwvTykvQFgHeyUEBxMCtyfTFVwXmI/SN+klMm4oWpeApJyEhXkImmRNaGVGac0PgqBQ==", - "dependencies": { - "axios": "^0.21.4", - "bitcore-lib": "^8.25.10", - "bitcore-mnemonic": "^8.25.10", - "buffer": "^6.0.3", - "crypto-js": "^3.1.9-1", - "isomorphic-ws": "^4.0.1", - "level": "^8.0.0", - "lodash": "^4.17.21", - "long": "^4.0.0", - "ws": "^7.5.9" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@hathor/wallet-lib/-/wallet-lib-1.8.0.tgz", + "integrity": "sha512-G1kLlxZ4Ev3S7hPiq/y9wUl4ns4pndOvuK37q+xqdxieZKCl2/O7lXiiHVOgRN9xOntL/TR66n2UZPpe0p29zQ==", + "dependencies": { + "axios": "1.7.2", + "bitcore-lib": "8.25.10", + "bitcore-mnemonic": "8.25.10", + "buffer": "6.0.3", + "crypto-js": "4.2.0", + "isomorphic-ws": "5.0.0", + "level": "8.0.1", + "lodash": "4.17.21", + "long": "5.2.3", + "ws": "8.17.0" + }, + "engines": { + "node": ">=20.0.0", + "npm": ">=10.0.0" } }, - "node_modules/@hathor/wallet-lib/node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "dependencies": { - "follow-redirects": "^1.14.0" + "node_modules/@hathor/wallet-lib/node_modules/ws": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", + "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/@isaacs/ttlcache": { @@ -6124,13 +6140,26 @@ "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" }, "node_modules/axios": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.17.1.tgz", - "integrity": "sha512-mZzWRyJeJ0rtK7e1/6iYBUzmeXjzei+1h1IvbedyU0sB52++tU5AU6r6TLXpwNVR0ebXIpvTVW+9CpWNyc1n8w==", - "deprecated": "Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", "dependencies": { - "follow-redirects": "^1.2.5", - "is-buffer": "^1.1.5" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" } }, "node_modules/axobject-query": { @@ -6783,9 +6812,9 @@ } }, "node_modules/bech32": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", - "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.3.tgz", + "integrity": "sha512-yuVFUvrNcoJi0sv5phmqc6P+Fl1HjRDRNOOkHY2X/3LBy2bIGNSFx4fZ95HMaXHupuS7cZR15AsvtmCIF4UEyg==" }, "node_modules/big.js": { "version": "5.2.2", @@ -6795,11 +6824,6 @@ "node": "*" } }, - "node_modules/bigi": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/bigi/-/bigi-1.4.2.tgz", - "integrity": "sha512-ddkU+dFIuEIW8lE7ZwdIAf2UPoM90eaprg5m3YXAVVTmKlqV/9BX4A2M8BOK2yOq6/VgZFVhK6QAxJebhlbhzw==" - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -6817,28 +6841,12 @@ "file-uri-to-path": "1.0.0" } }, - "node_modules/bip-schnorr": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/bip-schnorr/-/bip-schnorr-0.6.4.tgz", - "integrity": "sha512-dNKw7Lea8B0wMIN4OjEmOk/Z5qUGqoPDY0P2QttLqGk1hmDPytLWW8PR5Pb6Vxy6CprcdEgfJpOjUu+ONQveyg==", - "dependencies": { - "bigi": "^1.4.2", - "ecurve": "^1.0.6", - "js-sha256": "^0.9.0", - "randombytes": "^2.1.0", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/bitcore-lib": { - "version": "8.25.47", - "resolved": "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-8.25.47.tgz", - "integrity": "sha512-qDZr42HuP4P02I8kMGZUx/vvwuDsz8X3rQxXLfM0BtKzlQBcbSM7ycDkDN99Xc5jzpd4fxNQyyFXOmc6owUsrQ==", + "version": "8.25.10", + "resolved": "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-8.25.10.tgz", + "integrity": "sha512-MyHpSg7aFRHe359RA/gdkaQAal3NswYZTLEuu0tGX1RGWXAYN9i/24fsjPqVKj+z0ua+gzAT7aQs0KiKXWCgKA==", "dependencies": { - "bech32": "=2.0.0", - "bip-schnorr": "=0.6.4", + "bech32": "=1.1.3", "bn.js": "=4.11.8", "bs58": "^4.0.1", "buffer-compare": "=1.1.1", @@ -6848,11 +6856,11 @@ } }, "node_modules/bitcore-mnemonic": { - "version": "8.25.47", - "resolved": "https://registry.npmjs.org/bitcore-mnemonic/-/bitcore-mnemonic-8.25.47.tgz", - "integrity": "sha512-wTa0imZZpFTqwlpyokvU8CNl+YdaIvQIrWKp/0AEL9gPX2vuzBnE+U8Ok6D5lHCnbG6dvmoesmtyf6R3aYI86A==", + "version": "8.25.10", + "resolved": "https://registry.npmjs.org/bitcore-mnemonic/-/bitcore-mnemonic-8.25.10.tgz", + "integrity": "sha512-FeXxO37BLV5JRvxPmVFB91zRHalavV8H4TdQGt1/hz0AkoPymIV68OkuB+TptpjeYgatcgKPoPvPhglJkTzFQQ==", "dependencies": { - "bitcore-lib": "^8.25.47", + "bitcore-lib": "^8.25.10", "unorm": "^1.4.1" }, "peerDependencies": { @@ -8369,9 +8377,9 @@ } }, "node_modules/crypto-js": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", - "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "node_modules/css": { "version": "2.2.4", @@ -9297,15 +9305,6 @@ "safer-buffer": "^2.1.0" } }, - "node_modules/ecurve": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/ecurve/-/ecurve-1.0.6.tgz", - "integrity": "sha512-/BzEjNfiSuB7jIWKcS/z8FK9jNjmEWvUV2YZ4RLSmcDtP7Lq0m6FvDuSnJpBlDpGRpfRQeTLGLBI8H+kEv0r+w==", - "dependencies": { - "bigi": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -10756,9 +10755,9 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -12548,9 +12547,9 @@ } }, "node_modules/isomorphic-ws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", "peerDependencies": { "ws": "*" } @@ -13331,11 +13330,6 @@ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" }, - "node_modules/js-sha256": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", - "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -13823,10 +13817,11 @@ "deprecated": "use String.prototype.padStart()" }, "node_modules/level": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/level/-/level-8.0.0.tgz", - "integrity": "sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/level/-/level-8.0.1.tgz", + "integrity": "sha512-oPBGkheysuw7DmzFQYyFe8NAia5jFLAgEnkgWnK3OXAuJr8qFT+xBQIwokAZPME2bhPFzS8hlYcL16m8UZrtwQ==", "dependencies": { + "abstract-level": "^1.0.4", "browser-level": "^1.0.1", "classic-level": "^1.2.0" }, @@ -14367,9 +14362,9 @@ } }, "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, "node_modules/loose-envify": { "version": "1.4.0", @@ -16114,9 +16109,9 @@ } }, "node_modules/node-gyp-build": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", - "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", + "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -18373,6 +18368,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -24071,6 +24071,7 @@ "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "peer": true, "engines": { "node": ">=8.3.0" }, diff --git a/package.json b/package.json index 1712b54f..664111b5 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "hathor-admin", - "version": "0.18.1", + "version": "0.19.0", "private": true, "dependencies": { - "@hathor/wallet-lib": "1.4.0", - "axios": "0.17.1", + "@hathor/wallet-lib": "1.8.0", + "axios": "1.7.2", "bootstrap": "4.6.2", "d3-selection": "1.4.2", "d3-zoom": "1.8.3", diff --git a/src/App.js b/src/App.js index 90568c33..56a47738 100644 --- a/src/App.js +++ b/src/App.js @@ -29,7 +29,7 @@ import VersionError from './screens/VersionError'; import WebSocketHandler from './WebSocketHandler'; import NanoContractDetail from './screens/nano/NanoContractDetail'; import BlueprintDetail from './screens/nano/BlueprintDetail'; -import { apiLoadErrorUpdate, dashboardUpdate, isVersionAllowedUpdate } from "./actions/index"; +import { apiLoadErrorUpdate, dashboardUpdate, isVersionAllowedUpdate, updateServerInfo } from "./actions/index"; import { connect } from "react-redux"; import versionApi from './api/version'; import helpers from './utils/helpers'; @@ -44,6 +44,7 @@ const mapDispatchToProps = dispatch => { dashboardUpdate: data => dispatch(dashboardUpdate(data)), isVersionAllowedUpdate: data => dispatch(isVersionAllowedUpdate(data)), apiLoadErrorUpdate: data => dispatch(apiLoadErrorUpdate(data)), + updateServerInfo: data => dispatch(updateServerInfo(data)), }; }; @@ -66,6 +67,7 @@ class Root extends React.Component { network = 'testnet'; } hathorLibConfig.setNetwork(network); + this.props.updateServerInfo(data); this.props.isVersionAllowedUpdate({allowed: helpers.isVersionAllowed(data.version)}); }, (e) => { // Error in request @@ -95,7 +97,7 @@ class Root extends React.Component { <> - { this.props.apiLoadError ? + { this.props.apiLoadError ?

Error loading the explorer. Please reload the page to try again.

diff --git a/src/actions/index.js b/src/actions/index.js index 73ab1ff7..e2c3021b 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -10,3 +10,5 @@ export const dashboardUpdate = data => ({ type: "dashboard_update", payload: dat export const isVersionAllowedUpdate = data => ({ type: "is_version_allowed_update", payload: data }); export const apiLoadErrorUpdate = data => ({ type: 'api_load_error_update', payload: data }); + +export const updateServerInfo = data => ({ type: 'update_server_info', payload: data }); diff --git a/src/components/AddressDetailExplorer.js b/src/components/AddressDetailExplorer.js index f7707bce..7d622d2c 100644 --- a/src/components/AddressDetailExplorer.js +++ b/src/components/AddressDetailExplorer.js @@ -247,7 +247,7 @@ class AddressDetailExplorer extends React.Component { // use has a selected token, we will keep the selected token selectedToken = this.state.selectedToken; } else { - const hathorUID = hathorLib.constants.HATHOR_TOKEN_CONFIG.uid + const hathorUID = hathorLib.constants.NATIVE_TOKEN_UID if (tokens[hathorUID]) { // If HTR is in the token list of this address, it's the default selection selectedToken = hathorUID; @@ -321,7 +321,7 @@ class AddressDetailExplorer extends React.Component { } getSelectedTokenMetadata = (selectedToken) => { - if (selectedToken === hathorLib.constants.HATHOR_TOKEN_CONFIG.uid) { + if (selectedToken === hathorLib.constants.NATIVE_TOKEN_UID) { this.setState({ metadataLoaded: true }); return; } diff --git a/src/components/AddressDetailLegacy.js b/src/components/AddressDetailLegacy.js index a24e49f9..9c73f676 100644 --- a/src/components/AddressDetailLegacy.js +++ b/src/components/AddressDetailLegacy.js @@ -240,7 +240,7 @@ class AddressDetailLegacy extends React.Component { // If user had selected a token already, should continue the same selectedToken = this.state.selectedToken; } else { - const hathorUID = hathorLib.constants.HATHOR_TOKEN_CONFIG.uid + const hathorUID = hathorLib.constants.NATIVE_TOKEN_UID if (hathorUID in response.tokens_data) { // If HTR is in the token list of this address, it's the default selection selectedToken = hathorUID; diff --git a/src/components/AddressHistory.js b/src/components/AddressHistory.js index 0612277a..c061f2fa 100644 --- a/src/components/AddressHistory.js +++ b/src/components/AddressHistory.js @@ -10,8 +10,13 @@ import dateFormatter from '../utils/date'; import hathorLib from '@hathor/wallet-lib'; import PropTypes from 'prop-types'; import PaginationURL from '../utils/pagination'; -import helpers from '../utils/helpers'; import SortableTable from './SortableTable'; +import { connect } from "react-redux"; + + +const mapStateToProps = (state) => ({ + decimalPlaces: state.serverInfo.decimal_places, +}); class AddressHistory extends SortableTable { @@ -65,7 +70,7 @@ class AddressHistory extends SortableTable { return 'Loading...'; } - return helpers.renderValue(value, this.props.isNFT); + return hathorLib.numberUtils.prettyValue(value, this.props.isNFT ? 0 : this.props.decimalPlaces); } renderTableBody() { @@ -135,4 +140,4 @@ AddressHistory.propTypes = { }; -export default AddressHistory; +export default connect(mapStateToProps)(AddressHistory); diff --git a/src/components/AddressHistoryLegacy.js b/src/components/AddressHistoryLegacy.js index 02311269..a12d5a3f 100644 --- a/src/components/AddressHistoryLegacy.js +++ b/src/components/AddressHistoryLegacy.js @@ -8,10 +8,14 @@ import React from 'react'; import { Link } from 'react-router-dom'; import dateFormatter from '../utils/date'; -import hathorLib from '@hathor/wallet-lib'; +import hathorLib, { numberUtils } from '@hathor/wallet-lib'; import PropTypes from 'prop-types'; import PaginationURL from '../utils/pagination'; -import helpers from '../utils/helpers'; +import { connect } from 'react-redux'; + +const mapStateToProps = (state) => ({ + decimalPlaces: state.serverInfo.decimal_places, +}); class AddressHistory extends React.Component { @@ -128,7 +132,7 @@ class AddressHistory extends React.Component { return 'Loading...'; } - return helpers.renderValue(value, this.props.isNFT); + return numberUtils.prettyValue(value, this.props.isNFT ? 0 : this.props.decimalPlaces); } const loadTableBody = () => { @@ -206,4 +210,4 @@ AddressHistory.propTypes = { }; -export default AddressHistory; +export default connect(mapStateToProps)(AddressHistory); diff --git a/src/components/AddressSummary.js b/src/components/AddressSummary.js index eecd507a..2ac09ecc 100644 --- a/src/components/AddressSummary.js +++ b/src/components/AddressSummary.js @@ -7,9 +7,14 @@ import React from 'react'; import PropTypes from 'prop-types'; -import helpers from '../utils/helpers'; +import { numberUtils } from '@hathor/wallet-lib'; +import { connect } from 'react-redux'; +const mapStateToProps = (state) => ({ + decimalPlaces: state.serverInfo.decimal_places, +}); + class AddressSummary extends React.Component { /** * Called when selected token is changed @@ -55,7 +60,7 @@ class AddressSummary extends React.Component { return 'Loading...'; } - return helpers.renderValue(value, this.props.isNFT); + return numberUtils.prettyValue(value, this.props.isNFT ? 0 : this.props.decimalPlaces); } const loadBalanceInfo = () => { @@ -126,4 +131,4 @@ AddressSummary.propTypes = { tokenSelectChanged: PropTypes.func.isRequired, }; -export default AddressSummary; +export default connect(mapStateToProps)(AddressSummary); diff --git a/src/components/AddressSummaryLegacy.js b/src/components/AddressSummaryLegacy.js index 7128ec7c..34da906e 100644 --- a/src/components/AddressSummaryLegacy.js +++ b/src/components/AddressSummaryLegacy.js @@ -7,9 +7,14 @@ import React from 'react'; import PropTypes from 'prop-types'; -import helpers from '../utils/helpers'; +import { connect } from 'react-redux'; +import { numberUtils } from '@hathor/wallet-lib'; +const mapStateToProps = (state) => ({ + decimalPlaces: state.serverInfo.decimal_places, +}); + class AddressSummary extends React.Component { /** * Called when selected token is changed @@ -55,7 +60,7 @@ class AddressSummary extends React.Component { return 'Loading...'; } - return helpers.renderValue(value, this.props.isNFT); + return numberUtils.prettyValue(value, this.props.isNFT ? 0 : this.props.decimalPlaces); } const loadBalanceInfo = () => { @@ -126,4 +131,4 @@ AddressSummary.propTypes = { tokenSelectChanged: PropTypes.func.isRequired, }; -export default AddressSummary; \ No newline at end of file +export default connect(mapStateToProps)(AddressSummary); diff --git a/src/components/Navigation.js b/src/components/Navigation.js index db209459..6fddd825 100644 --- a/src/components/Navigation.js +++ b/src/components/Navigation.js @@ -12,6 +12,7 @@ import HathorAlert from './HathorAlert'; import Version from './Version'; import hathorLib from '@hathor/wallet-lib'; import ConditionalNavigation from './ConditionalNavigation'; +import { useFlag } from '@unleash/proxy-client-react'; import { UNLEASH_TOKENS_BASE_FEATURE_FLAG, UNLEASH_TOKEN_BALANCES_FEATURE_FLAG, @@ -56,6 +57,11 @@ class Navigation extends React.Component { this.refs.alertError.show(3000); } + showTokensTab() { + return useFlag(`${UNLEASH_TOKENS_BASE_FEATURE_FLAG}.rollout`) + || useFlag(`${UNLEASH_TOKEN_BALANCES_FEATURE_FLAG}.rollout`); + } + render() { return (
@@ -73,17 +79,19 @@ class Navigation extends React.Component {
  • Transactions
  • -
  • - -
    -
      - - -
    -
    -
  • + {this.showTokensTab() && ( +
  • + +
    +
      + + +
    +
    +
  • + )}
  • Network
  • diff --git a/src/components/Network.js b/src/components/Network.js index a7086e05..11c7213e 100644 --- a/src/components/Network.js +++ b/src/components/Network.js @@ -115,7 +115,7 @@ class Network extends React.Component { const server_best_block = this.state.best_block; const {peer_best_block, synced_block} = conn; - if (server_best_block) { + if (server_best_block && synced_block && peer_best_block) { progress = synced_block.height / server_best_block.height; if (server_best_block.id === synced_block.id) { state = SyncStates.IN_SYNC; @@ -124,6 +124,8 @@ class Network extends React.Component { } else if (peer_best_block.height > server_best_block.height) { state = SyncStates.AHEAD; } + } else { + state = SyncStates.UNKNOWN; } return { progress, diff --git a/src/components/feature_activation/FeatureRow.js b/src/components/feature_activation/FeatureRow.js index 465a0ad0..ef297e63 100644 --- a/src/components/feature_activation/FeatureRow.js +++ b/src/components/feature_activation/FeatureRow.js @@ -7,7 +7,7 @@ import React from 'react'; import featureActivation from '../../utils/featureActivation'; -import helpers from '../../utils/helpers'; +import { numberUtils } from '@hathor/wallet-lib'; class FeatureRow extends React.Component { @@ -22,9 +22,9 @@ class FeatureRow extends React.Component { {prettyState} {acceptance_percentage} {(this.props.feature.threshold * 100).toFixed(0)}% - {helpers.renderValue(this.props.feature.start_height, true)} - {helpers.renderValue(this.props.feature.minimum_activation_height, true)} - {helpers.renderValue(this.props.feature.timeout_height, true)} + {numberUtils.prettyValue(this.props.feature.start_height, 0)} + {numberUtils.prettyValue(this.props.feature.minimum_activation_height, 0)} + {numberUtils.prettyValue(this.props.feature.timeout_height, 0)} {this.props.feature.lock_in_on_timeout.toString()} {this.props.feature.version} diff --git a/src/components/feature_activation/Features.js b/src/components/feature_activation/Features.js index 67f7351c..8d35c5fb 100644 --- a/src/components/feature_activation/Features.js +++ b/src/components/feature_activation/Features.js @@ -14,7 +14,7 @@ import FeatureRow from './FeatureRow'; import colors from '../../index.scss'; import PaginationURL from '../../utils/pagination'; import featureApi from '../../api/featureApi'; -import helpers from '../../utils/helpers'; +import { numberUtils } from '@hathor/wallet-lib'; class Features extends React.Component { @@ -184,9 +184,10 @@ class Features extends React.Component { } const loadFeaturesPage = () => { + const height = numberUtils.prettyValue(this.state.block_height, 0); return (
    -
    Showing feature states for current best block at height {helpers.renderValue(this.state.block_height, true)}.
    +
    Showing feature states for current best block at height {height}.
    {!this.state.loaded ? : loadTable()} {loadPagination()}
    diff --git a/src/components/timeseries/ScreenStatusMessage.js b/src/components/timeseries/ScreenStatusMessage.js index 4f45df79..005804e5 100644 --- a/src/components/timeseries/ScreenStatusMessage.js +++ b/src/components/timeseries/ScreenStatusMessage.js @@ -4,7 +4,7 @@ import { get } from 'lodash'; import blockApi from '../../api/blockApi'; import { SCREEN_STATUS_LOOP_INTERVAL_IN_SECONDS } from '../../constants'; import dateFormatter from '../../utils/date'; -import helpers from '../../utils/helpers'; +import { numberUtils } from '@hathor/wallet-lib'; import ErrorMessageWithIcon from '../error/ErrorMessageWithIcon'; import Loading from '../Loading'; @@ -12,71 +12,72 @@ import Loading from '../Loading'; class ScreenStatusMessage extends React.Component { - constructor() { - super(); - this.screenStatusLoopExecution = null; + constructor() { + super(); + this.screenStatusLoopExecution = null; - this.state = { - height: 0, - timestamp: '', - error: false, - loading: true, - }; - } + this.state = { + height: 0, + timestamp: '', + error: false, + loading: true, + }; + } - componentDidMount = async () => { - await this.getBestChainHeight(); + componentDidMount = async () => { + await this.getBestChainHeight(); - this.setState({ - loading: false - }); + this.setState({ + loading: false + }); - // Constantly execute the method to get the newest block - this.screenStatusLoopExecution = setInterval(() => { - this.getBestChainHeight(); - }, SCREEN_STATUS_LOOP_INTERVAL_IN_SECONDS * 1000); - } + // Constantly execute the method to get the newest block + this.screenStatusLoopExecution = setInterval(() => { + this.getBestChainHeight(); + }, SCREEN_STATUS_LOOP_INTERVAL_IN_SECONDS * 1000); + } - componentWillUnmount() { - // We need to clear the interval object we created when user leaves the page - if (this.screenStatusLoopExecution) { - clearInterval(this.screenStatusLoopExecution); - } + componentWillUnmount() { + // We need to clear the interval object we created when user leaves the page + if (this.screenStatusLoopExecution) { + clearInterval(this.screenStatusLoopExecution); } + } - /** + /** * Calls the Explorer Service to get the best chain height - * + * */ - getBestChainHeight = async () => { - const blockApiResponse = await blockApi.getBestChainHeight(); + getBestChainHeight = async () => { + const blockApiResponse = await blockApi.getBestChainHeight(); - const blockApiResponseData = get(blockApiResponse, 'data.hits[0]', []); + const blockApiResponseData = get(blockApiResponse, 'data.hits[0]', []); - this.setState({ - height: get(blockApiResponseData, 'height', 0), - timestamp: get(blockApiResponseData, 'timestamp', ''), - error: get(blockApiResponse, 'error', false), - }); - } + this.setState({ + height: get(blockApiResponseData, 'height', 0), + timestamp: get(blockApiResponseData, 'timestamp', ''), + error: get(blockApiResponse, 'error', false), + }); + } - render() { - return ( -
    - { - (this.state.error) ? - : - (this.state.loading) ? - : -

    - - This screen is updated until block at height {helpers.renderValue(this.state.height, true)} and the last update was on {dateFormatter.parseTimestampFromSQLTimestamp(this.state.timestamp)} - -

    - } -
    - ); - } + render() { + const height = numberUtils.prettyValue(this.state.height, 0); + return ( +
    + { + (this.state.error) ? + : + (this.state.loading) ? + : +

    + + This screen is updated until block at height {height} and the last update was on {dateFormatter.parseTimestampFromSQLTimestamp(this.state.timestamp)} + +

    + } +
    + ); + } } export default ScreenStatusMessage; diff --git a/src/components/token/TokenAutoCompleteField.js b/src/components/token/TokenAutoCompleteField.js index 17162e66..f5fd97d9 100644 --- a/src/components/token/TokenAutoCompleteField.js +++ b/src/components/token/TokenAutoCompleteField.js @@ -4,9 +4,14 @@ import Loading from '../Loading'; import tokensApi from '../../api/tokensApi'; import { debounce, get } from 'lodash'; import { constants as hathorLibConstants } from '@hathor/wallet-lib'; +import { connect } from "react-redux"; const DEBOUNCE_SEARCH_TIME = 200; // ms +const mapStateToProps = (state) => { + return { serverInfo: state.serverInfo }; +}; + class TokenAutoCompleteField extends React.Component { constructor() { super(); @@ -21,6 +26,14 @@ class TokenAutoCompleteField extends React.Component { this.handleClick = this._handleClick.bind(this); } + getNativeToken() { + return { + uid: hathorLibConstants.NATIVE_TOKEN_UID, + name: this.props.serverInfo?.native_token?.name ?? hathorLibConstants.DEFAULT_NATIVE_TOKEN_CONFIG.name, + symbol: this.props.serverInfo?.native_token?.symbol ?? hathorLibConstants.DEFAULT_NATIVE_TOKEN_CONFIG.symbol, + }; + } + /** * Handles clicks on the document to decide if we should hide the autocomplete * results @@ -58,7 +71,7 @@ class TokenAutoCompleteField extends React.Component { componentDidMount() { document.addEventListener('click', this.handleClick); - if (this.props.tokenId !== hathorLibConstants.HATHOR_TOKEN_CONFIG.uid) { + if (this.props.tokenId !== hathorLibConstants.NATIVE_TOKEN_UID) { // A token was selected in the query params // so we must search for it here to add // in the autocomplete input and perform the search @@ -174,6 +187,7 @@ class TokenAutoCompleteField extends React.Component {
    ); } + const nativeToken = this.getNativeToken(); return ( ) } @@ -234,4 +248,4 @@ TokenAutoCompleteField.propTypes = { onTokenSelected: PropTypes.func.isRequired, }; -export default TokenAutoCompleteField; +export default connect(mapStateToProps)(TokenAutoCompleteField); diff --git a/src/components/token/TokenBalanceRow.js b/src/components/token/TokenBalanceRow.js index 2d808c1b..773e3ed7 100644 --- a/src/components/token/TokenBalanceRow.js +++ b/src/components/token/TokenBalanceRow.js @@ -8,7 +8,13 @@ import React from 'react'; import { withRouter } from "react-router-dom"; import PropTypes from 'prop-types'; -import helpers from '../../utils/helpers'; +import { numberUtils } from '@hathor/wallet-lib'; +import { connect } from 'react-redux'; + + +const mapStateToProps = (state) => ({ + decimalPlaces: state.serverInfo.decimal_places, +}); class TokenBalanceRow extends React.Component { /** @@ -24,9 +30,9 @@ class TokenBalanceRow extends React.Component { return ( this.onRowClicked(this.props.address)}> {this.props.address} - {helpers.renderValue(this.props.total, false)} - {helpers.renderValue(this.props.unlocked, false)} - {helpers.renderValue(this.props.locked, false)} + {numberUtils.prettyValue(this.props.total, this.props.decimalPlaces)} + {numberUtils.prettyValue(this.props.unlocked, this.props.decimalPlaces)} + {numberUtils.prettyValue(this.props.locked, this.props.decimalPlaces)} ); } @@ -48,4 +54,4 @@ TokenBalanceRow.propTypes = { tokenId: PropTypes.string.isRequired, } -export default withRouter(TokenBalanceRow); +export default connect(mapStateToProps)(withRouter(TokenBalanceRow)); diff --git a/src/components/token/TokenBalances.js b/src/components/token/TokenBalances.js index 23accfc3..f99bd3dc 100644 --- a/src/components/token/TokenBalances.js +++ b/src/components/token/TokenBalances.js @@ -15,7 +15,7 @@ import { withRouter } from "react-router-dom"; import ErrorMessageWithIcon from '../error/ErrorMessageWithIcon' import TokenAutoCompleteField from './TokenAutoCompleteField'; import helpers from '../../utils/helpers'; -import { constants as hathorLibConstants } from '@hathor/wallet-lib'; +import { numberUtils, constants as hathorLibConstants } from '@hathor/wallet-lib'; /** @@ -39,7 +39,7 @@ class TokenBalances extends React.Component { const sortBy = get(queryParams, 'sortBy', 'total'); const order = get(queryParams, 'order', 'desc'); - const tokenId = get(queryParams, 'token', hathorLibConstants.HATHOR_TOKEN_CONFIG.uid); + const tokenId = get(queryParams, 'token', hathorLibConstants.NATIVE_TOKEN_UID); /** * tokenBalances: List of token balances currently being rendered. @@ -58,7 +58,7 @@ class TokenBalances extends React.Component { * isSearchLoading: Indicates if search results are being retrieved from explorer-service * calculatingPage: Indicates if next page is being retrieved from explorer-service * error: Indicates if an unexpected error happened when calling the explorer-service - * tokenBalanceInformationError: Indicates if an unexpected error happened when calling the token balance information service + * tokenBalanceInformationError: Indicates if an unexpected error happened when calling the token balance information service * maintenanceMode: Indicates if explorer-service or its downstream services are experiencing problems. If so, maintenance mode will be enabled on * our feature toggle service (Unleash) to remove additional load until the team fixes the problem * transactionsCount: Number of transactions for the searched token @@ -91,7 +91,7 @@ class TokenBalances extends React.Component { return; } - if (this.state.tokenId === hathorLibConstants.HATHOR_TOKEN_CONFIG.uid) { + if (this.state.tokenId === hathorLibConstants.NATIVE_TOKEN_UID) { // If we had a custom token as queryParam // then we will perform the search after the token // is found in the elastic search @@ -232,7 +232,7 @@ class TokenBalances extends React.Component { } fetchHTRTransactionCount = async () => { - const tokenApiRequest = await tokensApi.getToken(hathorLibConstants.HATHOR_TOKEN_CONFIG.uid); + const tokenApiRequest = await tokensApi.getToken(hathorLibConstants.NATIVE_TOKEN_UID); this.setState({ tokensApiError: get(tokenApiRequest, 'error', false), @@ -243,10 +243,10 @@ class TokenBalances extends React.Component { onTokenSelected = async (token) => { if (!token) { await helpers.setStateAsync(this, { - tokenId: hathorLibConstants.HATHOR_TOKEN_CONFIG.uid + tokenId: hathorLibConstants.NATIVE_TOKEN_UID }); - // HTR token is the default, so the search API is not called, we must forcefully call it + // HTR token is the default, so the search API is not called, we must forcefully call it // so we can retrieve the transactions count information await this.fetchHTRTransactionCount(); @@ -323,7 +323,7 @@ class TokenBalances extends React.Component {
    { - this.state.tokenId !== hathorLibConstants.HATHOR_TOKEN_CONFIG.uid && ( + this.state.tokenId !== hathorLibConstants.NATIVE_TOKEN_UID && (

    Click here to see the token details @@ -333,11 +333,11 @@ class TokenBalances extends React.Component { } {!this.state.tokenBalanceInformationError && ( -

    Total number of addresses: { helpers.renderValue(this.state.addressesCount, true) }

    +

    Total number of addresses: { numberUtils.prettyValue(this.state.addressesCount, 0) }

    )} {!this.state.tokensApiError && ( -

    Total number of transactions: { helpers.renderValue(this.state.transactionsCount, true) }

    +

    Total number of transactions: { numberUtils.prettyValue(this.state.transactionsCount, 0) }

    )} {(this.state.tokensApiError || this.state.tokenBalanceInformationError) &&( diff --git a/src/components/token/TokenInfo.js b/src/components/token/TokenInfo.js index a91dd675..6012f16f 100644 --- a/src/components/token/TokenInfo.js +++ b/src/components/token/TokenInfo.js @@ -1,7 +1,12 @@ import React, { useState, useEffect } from 'react'; -import helpers from '../../utils/helpers'; +import { numberUtils } from '@hathor/wallet-lib'; +import { connect } from 'react-redux'; +const mapStateToProps = (state) => ({ + decimalPlaces: state.serverInfo.decimal_places, +}) + const TokenInfo = (props) => { const [token, setToken] = useState(props.token); @@ -38,7 +43,7 @@ const TokenInfo = (props) => { return 'Loading...'; } - const amount = helpers.renderValue(token.totalSupply, isNFT()); + const amount = numberUtils.prettyValue(token.totalSupply, isNFT() ? 0 : props.decimalPlaces); return `${amount} ${token.symbol}`; } @@ -68,7 +73,6 @@ const TokenInfo = (props) => {

    Total number of transactions: {token.transactionsCount}

    ) - } -export default TokenInfo; +export default connect(mapStateToProps)(TokenInfo); diff --git a/src/components/tx/TxData.js b/src/components/tx/TxData.js index a923f28e..25f1f1d3 100644 --- a/src/components/tx/TxData.js +++ b/src/components/tx/TxData.js @@ -17,13 +17,20 @@ import hathorLib from '@hathor/wallet-lib'; import helpers from '../../utils/helpers'; import metadataApi from '../../api/metadataApi'; import graphvizApi from '../../api/graphvizApi'; -import { HATHOR_TOKEN_INDEX, HATHOR_TOKEN_CONFIG } from '../../constants'; import { CopyToClipboard } from 'react-copy-to-clipboard'; import { Link } from 'react-router-dom' import { Module, render } from 'viz.js/full.render.js'; import Loading from '../Loading'; import FeatureDataRow from '../feature_activation/FeatureDataRow'; import featureApi from '../../api/featureApi'; +import { connect } from 'react-redux'; + + +const mapStateToProps = (state) => ({ + nativeToken: state.serverInfo.native_token, + decimalPlaces: state.serverInfo.decimal_places, +}); + /** * Component that renders data of a transaction (used in TransactionDetail and DecodeTx screens) @@ -119,6 +126,11 @@ class TxData extends React.Component { }); } + getNativeToken = () => { + const nativeToken = this.props.nativeToken; + return {...nativeToken, uid: hathorLib.constants.NATIVE_TOKEN_UID}; + } + /** * Fetch token metadata * @@ -238,8 +250,8 @@ class TxData extends React.Component { * @return {string} Token symbol */ getOutputToken = (tokenData) => { - if (tokenData === HATHOR_TOKEN_INDEX) { - return HATHOR_TOKEN_CONFIG.symbol; + if (tokenData === hathorLib.constants.HATHOR_TOKEN_INDEX) { + return this.getNativeToken().symbol; } const tokenConfig = this.props.transaction.tokens[tokenData - 1]; return tokenConfig.symbol; @@ -253,8 +265,8 @@ class TxData extends React.Component { * @return {string} Token symbol */ getSymbol = (uid) => { - if (uid === HATHOR_TOKEN_CONFIG.uid) { - return HATHOR_TOKEN_CONFIG.symbol; + if (uid === hathorLib.constants.NATIVE_TOKEN_UID) { + return this.getNativeToken().symbol; } const tokenConfig = this.state.tokens.find((token) => token.uid === uid); if (tokenConfig === undefined) return ''; @@ -269,8 +281,8 @@ class TxData extends React.Component { * @return {string} Token uid */ getUIDFromTokenData = (tokenData) => { - if (tokenData === HATHOR_TOKEN_INDEX) { - return HATHOR_TOKEN_CONFIG.uid; + if (tokenData === hathorLib.constants.HATHOR_TOKEN_INDEX) { + return hathorLib.constants.NATIVE_TOKEN_UID; } const tokenConfig = this.props.transaction.tokens[tokenData - 1]; return tokenConfig.uid; @@ -323,7 +335,7 @@ class TxData extends React.Component { const uid = this.getUIDFromTokenData(hathorLib.tokensUtils.getTokenIndexFromData(output.token_data)); const tokenData = this.state.tokens.find((token) => token.uid === uid); const isNFT = tokenData && tokenData.meta && tokenData.meta.nft; - return helpers.renderValue(output.value, isNFT); + return hathorLib.numberUtils.prettyValue(output.value, isNFT ? 0 : this.props.decimalPlaces); } } @@ -547,7 +559,7 @@ class TxData extends React.Component { const renderTokenList = () => { const renderTokenUID = (token) => { - if (token.uid === hathorLib.constants.HATHOR_TOKEN_CONFIG.uid) { + if (token.uid === hathorLib.constants.NATIVE_TOKEN_UID) { return token.uid; } else { return {token.uid} @@ -722,6 +734,8 @@ class TxData extends React.Component {
    {dateFormatter.parseTimestamp(this.props.transaction.timestamp)}
    {this.props.transaction.nonce}
    {helpers.roundFloat(this.props.transaction.weight)}
    + {this.props.transaction.signer_id &&
    {this.props.transaction.signer_id.toLowerCase()}
    } + {this.props.transaction.signer &&
    {helpers.getShortHash(this.props.transaction.signer.toLowerCase())}
    } {!hathorLib.transactionUtils.isBlock(this.props.transaction) && renderFirstBlockDiv()}
    @@ -787,4 +801,4 @@ class TxData extends React.Component { } } -export default TxData; +export default connect(mapStateToProps)(TxData); diff --git a/src/constants.js b/src/constants.js index 32c7752c..8418799b 100644 --- a/src/constants.js +++ b/src/constants.js @@ -50,9 +50,7 @@ export const TESTNET_GENESIS_TX = [ '00975897028ceb037307327c953f5e7ad4d3f42402d71bd3d11ecb63ac39f01a', ]; -export const DECIMAL_PLACES = 2; - -export const VERSION = '0.18.1'; +export const VERSION = '0.19.0'; export const MIN_API_VERSION = '0.33.0'; @@ -63,11 +61,6 @@ export const MAX_GRAPH_LEVEL = 1 // First bit in the index byte indicates whether it's an authority output export const TOKEN_AUTHORITY_MASK = 0b10000000 -/** - * Hathor token config - */ -export const HATHOR_TOKEN_CONFIG = {'name': 'Hathor', 'symbol': 'HTR', 'uid': '00'}; - /** * Hathor token default index */ diff --git a/src/reducers/index.js b/src/reducers/index.js index 788c21a9..f9fc1d7f 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -5,26 +5,63 @@ * LICENSE file in the root directory of this source tree. */ -/* - 'data' object with latest data. content: - { - 'date': Date object, - 'transactions': int, - 'blocks': int, - 'hash_rate': float (hashes/s), - 'peers': int, - 'txRate': float (tx/s), - 'block_hash_rate': float (hashes/s), - 'tx_hash_rate': float (hashes/s), - 'time': float (timestamp), - 'type': 'dashboard:metrics', - } - 'isVersionAllowed': if the backend API version is allowed for this admin (boolean) -*/ +import { constants } from '@hathor/wallet-lib'; +import { cloneDeep } from 'lodash'; + +/** + * Dashboard data from websocket updates + * @typedef {Object} DashboardData + * @property {Date} date + * @property {number} transactions + * @property {number} blocks + * @property {number} hash_rate + * @property {number} peers + * @property {number} txRate + * @property {number} block_hash_rate + * @property {number} tx_hash_rate + * @property {number} time + * @property {string} type + */ + +/** + * Server info from version api + * @typedef {Object} ServerInfo + * @property {string} version + * @property {string} network + * @property {number} min_tx_weight + * @property {number} min_tx_weight_coefficient + * @property {number} min_tx_weight_k + * @property {number} token_deposit_percentage + * @property {number} reward_spend_min_blocks + * @property {number} max_number_inputs + * @property {number} max_number_outputs + * @property {Object|undefined|null} native_token + * @property {string} native_token.name + * @property {string} native_token.symbol + * @property {number} decimal_places + */ + +/** + * Explorer redux store + * @typedef {Object} ReduxStore + * @property {DashboardData} data - object with latest data. + * @property {boolean} isVersionAllowed - if the backend API version is allowed for this admin. + * @property {ServerInfo} serverInfo - server info from version api. + * @property {boolean} apiLoadError - If we had an error while loading the initial data from the server. + */ +/** + * Initial state + * @type {ReduxStore} + */ const initialState = { data: null, isVersionAllowed: undefined, + serverInfo: { + native_token: constants.DEFAULT_NATIVE_TOKEN_CONFIG, + decimal_places: constants.DECIMAL_PLACES, + }, + apiLoadError: false, }; const rootReducer = (state = initialState, action) => { @@ -35,9 +72,31 @@ const rootReducer = (state = initialState, action) => { return Object.assign({}, state, {isVersionAllowed: action.payload.allowed}); case 'api_load_error_update': return Object.assign({}, state, {apiLoadError: action.payload.apiLoadError}); + case 'update_server_info': + return setServerInfo(state, action); default: return state; } }; -export default rootReducer; \ No newline at end of file +/** + * Set the server info a.k.a '/version' data on storage. + * Will update keys based on default values. + * + * @param {ReduxStore} state - Current store state. + * @param {payload} ServerInfo - Server info to save on storage. + * @returns {ReduxStore} New state for the store. + */ +const setServerInfo = (state, { payload }) => { + const serverInfo = cloneDeep(payload); + // Default values + serverInfo.decimal_places = serverInfo.decimal_places ?? constants.DECIMAL_PLACES; + serverInfo.native_token = serverInfo.native_token ?? constants.DEFAULT_NATIVE_TOKEN_CONFIG; + + return { + ...state, + serverInfo, + } +} + +export default rootReducer; diff --git a/src/screens/Dashboard.js b/src/screens/Dashboard.js index 867b4b2d..ae5ebf74 100644 --- a/src/screens/Dashboard.js +++ b/src/screens/Dashboard.js @@ -11,11 +11,12 @@ import colors from '../index.scss'; import ReactLoading from 'react-loading'; import helpers from '../utils/helpers'; import TimeSeriesDashboard from './TimeSeriesDashboard'; +import { numberUtils } from '@hathor/wallet-lib'; -const mapStateToProps = (state) => { - return { data: state.data }; -}; +const mapStateToProps = (state) => ({ + data: state.data, +}); class Dashboard extends React.Component { @@ -37,11 +38,13 @@ class Dashboard extends React.Component { const prefix = helpers.getUnitPrefix(prettyfied.divisions); const hashRate = `${prettyValue} ${prefix}h/s`; + const bestBlockHeight = numberUtils.prettyValue(height, 0); + const ptransactions = numberUtils.prettyValue(transactions, 0); return (

    Real time

    -

    Blocks (best height): {helpers.renderValue(height, true)}

    -

    Transactions: {helpers.renderValue(transactions, true)}

    +

    Blocks (best height): {bestBlockHeight}

    +

    Transactions: {ptransactions}

    Hash rate: {hashRate}

    @@ -50,4 +53,4 @@ class Dashboard extends React.Component { } } -export default connect(mapStateToProps)(Dashboard); \ No newline at end of file +export default connect(mapStateToProps)(Dashboard); diff --git a/src/utils/helpers.js b/src/utils/helpers.js index f5f6078f..938bb627 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -6,7 +6,7 @@ */ import hathorLib from '@hathor/wallet-lib'; -import { MAINNET_GENESIS_BLOCK, TESTNET_GENESIS_BLOCK, MAINNET_GENESIS_TX, TESTNET_GENESIS_TX, DECIMAL_PLACES, MIN_API_VERSION } from '../constants'; +import { MAINNET_GENESIS_BLOCK, TESTNET_GENESIS_BLOCK, MAINNET_GENESIS_TX, TESTNET_GENESIS_TX, MIN_API_VERSION } from '../constants'; import { get } from 'lodash'; const helpers = { @@ -42,10 +42,6 @@ const helpers = { return Math.round(n*100)/100 }, - prettyValue(value) { - return (value/10**DECIMAL_PLACES).toFixed(DECIMAL_PLACES); - }, - isVersionAllowed(version) { // Verifies if the version in parameter is allowed to make requests to the API backend // We check with our min api version @@ -221,24 +217,6 @@ const helpers = { return parts[parts.length - 1]; }, - /** - * Render value to integer or decimal - * - * @param {number} amount Amount to render - * @param {boolean} isInteger If it's an integer or decimal - * - * @return {string} rendered value - * @memberof Helpers - * @inner - */ - renderValue(amount, isInteger) { - if (isInteger) { - return hathorLib.numberUtils.prettyIntegerValue(amount); - } else { - return hathorLib.numberUtils.prettyValue(amount); - } - }, - /** * Promisifies the instance's setState method * @@ -256,8 +234,8 @@ const helpers = { /** * Parses the response from Explorer Service and add an object `error` to the respoonse. * This way, clients of this method do not have to handle status codes. - * - * @param {Object} response + * + * @param {Object} response * @returns Explorer Service result enriched with the `error` object */ handleExplorerServiceResponse(response) {