diff --git a/.eslintrc.js b/.eslintrc.js index 528ff2a..fc20758 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,6 +5,7 @@ module.exports = { node: true, }, extends: ['airbnb-base', 'plugin:react/recommended', 'plugin:prettier/recommended'], + parser: 'babel-eslint', parserOptions: { ecmaFeatures: { jsx: true, diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index eb7274c..92732cf 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -35,3 +35,7 @@ jobs: - name: Format run: | npm run format:check + + - name: Linting + run: | + npm run lint diff --git a/src/components/AddressDetailLegacy.js b/src/components/AddressDetailLegacy.js index 4fdc590..c1778c5 100644 --- a/src/components/AddressDetailLegacy.js +++ b/src/components/AddressDetailLegacy.js @@ -6,15 +6,15 @@ */ import React from 'react'; +import hathorLib from '@hathor/wallet-lib'; +import ReactLoading from 'react-loading'; +import { isEqual } from 'lodash'; import AddressSummaryLegacy from './AddressSummaryLegacy'; import AddressHistoryLegacy from './AddressHistoryLegacy'; import PaginationURL from '../utils/pagination'; -import hathorLib from '@hathor/wallet-lib'; -import ReactLoading from 'react-loading'; import colors from '../index.scss'; import WebSocketHandler from '../WebSocketHandler'; import { TX_COUNT } from '../constants'; -import { isEqual } from 'lodash'; import helpers from '../utils/helpers'; import metadataApi from '../api/metadataApi'; import addressApiLegacy from '../api/addressApiLegacy'; @@ -130,8 +130,8 @@ class AddressDetailLegacy extends React.Component { // We only add new tx/blocks if it's the first page if (!this.state.hasBefore) { if (this.shouldUpdate(tx, true)) { - let transactions = this.state.transactions; - let hasAfter = this.state.hasAfter || transactions.length === TX_COUNT; + let { transactions } = this.state; + const hasAfter = this.state.hasAfter || transactions.length === TX_COUNT; transactions = helpers.updateListWs(transactions, tx, TX_COUNT); const newNumberOfTransactions = this.state.numberOfTransactions + 1; @@ -252,7 +252,7 @@ class AddressDetailLegacy extends React.Component { this.setState({ loadingSummary: false }); return; } - selectedToken = keys[0]; + [selectedToken] = keys; } } @@ -316,7 +316,7 @@ class AddressDetailLegacy extends React.Component { */ shouldUpdate = (tx, checkToken) => { const arr = [...tx.outputs, ...tx.inputs]; - const token = this.pagination.obtainQueryParams().token; + const { token } = this.pagination.obtainQueryParams(); for (const element of arr) { if (element.decoded.address === this.state.address) { @@ -374,39 +374,38 @@ class AddressDetailLegacy extends React.Component { const renderData = () => { if (this.state.errorMessage) { return

{this.state.errorMessage}

; - } else if (this.state.address === null) { + } + if (this.state.address === null) { return null; - } else { - if (this.state.loadingSummary || this.state.loadingHistory) { - return ; - } else { - return ( -
- {renderWarningAlert()} - - -
- ); - } } + if (this.state.loadingSummary || this.state.loadingHistory) { + return ; + } + return ( +
+ {renderWarningAlert()} + + +
+ ); }; return
{renderData()}
; diff --git a/src/components/AddressHistory.js b/src/components/AddressHistory.js index 265048e..4131e4a 100644 --- a/src/components/AddressHistory.js +++ b/src/components/AddressHistory.js @@ -7,9 +7,9 @@ import React from 'react'; import { connect } from 'react-redux'; -import dateFormatter from '../utils/date'; import hathorLib from '@hathor/wallet-lib'; import PropTypes from 'prop-types'; +import dateFormatter from '../utils/date'; import PaginationURL from '../utils/pagination'; import SortableTable from './SortableTable'; import EllipsiCell from './EllipsiCell'; @@ -30,7 +30,7 @@ class AddressHistory extends SortableTable { * @return {boolean} If the tx has only authority in the search address */ isAllAuthority = tx => { - for (let txin of tx.inputs) { + for (const txin of tx.inputs) { if ( !hathorLib.transactionUtils.isAuthorityOutput(txin) && txin.decoded.address === this.props.address @@ -39,7 +39,7 @@ class AddressHistory extends SortableTable { } } - for (let txout of tx.outputs) { + for (const txout of tx.outputs) { if ( !hathorLib.transactionUtils.isAuthorityOutput(txout) && txout.decoded.address === this.props.address @@ -135,12 +135,10 @@ class AddressHistory extends SortableTable { ); } trClass = 'input-tr'; - } else { - if (this.props.txCache[tx.tx_id]) { - if (this.isAllAuthority(this.props.txCache[tx.tx_id])) { - statusElement = Authority; - prettyValue = '--'; - } + } else if (this.props.txCache[tx.tx_id]) { + if (this.isAllAuthority(this.props.txCache[tx.tx_id])) { + statusElement = Authority; + prettyValue = '--'; } } @@ -149,7 +147,7 @@ class AddressHistory extends SortableTable { trClass = ''; } return ( - this.props.onRowClicked(tx.tx_id)}> + this.props.onRowClicked(tx.tx_id)}> {hathorLib.transactionUtils.getTxType(tx)} @@ -211,12 +209,10 @@ class AddressHistory extends SortableTable { ); } trClass = 'input-tr'; - } else { - if (this.props.txCache[tx.tx_id]) { - if (this.isAllAuthority(this.props.txCache[tx.tx_id])) { - statusElement = Authority; - prettyValue = '--'; - } + } else if (this.props.txCache[tx.tx_id]) { + if (this.isAllAuthority(this.props.txCache[tx.tx_id])) { + statusElement = Authority; + prettyValue = '--'; } } @@ -225,7 +221,7 @@ class AddressHistory extends SortableTable { trClass = ''; } return ( - this.props.onRowClicked(tx.tx_id)}> + this.props.onRowClicked(tx.tx_id)}> {hathorLib.transactionUtils.getTxType(tx)} diff --git a/src/components/AddressHistoryLegacy.js b/src/components/AddressHistoryLegacy.js index c63c180..3b10b6c 100644 --- a/src/components/AddressHistoryLegacy.js +++ b/src/components/AddressHistoryLegacy.js @@ -7,11 +7,11 @@ import React from 'react'; import { Link } from 'react-router-dom'; -import dateFormatter from '../utils/date'; import hathorLib, { numberUtils } from '@hathor/wallet-lib'; import PropTypes from 'prop-types'; -import PaginationURL from '../utils/pagination'; import { connect } from 'react-redux'; +import PaginationURL from '../utils/pagination'; +import dateFormatter from '../utils/date'; const mapStateToProps = state => ({ decimalPlaces: state.serverInfo.decimal_places, @@ -29,7 +29,7 @@ class AddressHistory extends React.Component { const token = this.props.selectedToken; let value = 0; - for (let txin of tx.inputs) { + for (const txin of tx.inputs) { if (txin.token === token && txin.decoded.address === this.props.address) { if (!hathorLib.transactionUtils.isAuthorityOutput(txin)) { value -= txin.value; @@ -37,7 +37,7 @@ class AddressHistory extends React.Component { } } - for (let txout of tx.outputs) { + for (const txout of tx.outputs) { if (txout.token === token && txout.decoded.address === this.props.address) { if (!hathorLib.transactionUtils.isAuthorityOutput(txout)) { value += txout.value; @@ -56,7 +56,7 @@ class AddressHistory extends React.Component { * @return {boolean} If the tx has only authority in the search address */ isAllAuthority = tx => { - for (let txin of tx.inputs) { + for (const txin of tx.inputs) { if ( !hathorLib.transactionUtils.isAuthorityOutput(txin) && txin.decoded.address === this.props.address @@ -65,7 +65,7 @@ class AddressHistory extends React.Component { } } - for (let txout of tx.outputs) { + for (const txout of tx.outputs) { if ( !hathorLib.transactionUtils.isAuthorityOutput(txout) && txout.decoded.address === this.props.address @@ -93,39 +93,35 @@ class AddressHistory extends React.Component { const loadPagination = () => { if (this.props.transactions.length === 0) { return null; - } else { - return ( - - ); - } + Next + + + + + ); }; const loadTable = () => { @@ -164,7 +160,7 @@ class AddressHistory extends React.Component { }; const loadTableBody = () => { - return this.props.transactions.map((tx, idx) => { + return this.props.transactions.map((tx, _idx) => { const value = this.calculateAddressBalance(tx); let statusElement = ''; let trClass = ''; @@ -199,11 +195,9 @@ class AddressHistory extends React.Component { ); } trClass = 'input-tr'; - } else { - if (this.isAllAuthority(tx)) { - statusElement = Authority; - prettyValue = '--'; - } + } else if (this.isAllAuthority(tx)) { + statusElement = Authority; + prettyValue = '--'; } if (!this.props.metadataLoaded) { @@ -212,7 +206,7 @@ class AddressHistory extends React.Component { } return ( - this.props.onRowClicked(tx.tx_id)}> + this.props.onRowClicked(tx.tx_id)}> {hathorLib.transactionUtils.getTxType(tx)} diff --git a/src/components/AddressSummary.js b/src/components/AddressSummary.js index 66c3bc3..6c6fc2e 100644 --- a/src/components/AddressSummary.js +++ b/src/components/AddressSummary.js @@ -74,9 +74,8 @@ class AddressSummary extends React.Component { if (this.props.isNFT) { return 'NFT'; - } else { - return 'Custom token'; } + return 'Custom token'; }; const renderValue = value => { @@ -181,13 +180,12 @@ class AddressSummary extends React.Component { {token.name} ({token.symbol}) ); - } else { - return ( - - ); } + return ( + + ); }; const newRenderTokenData = () => { @@ -198,15 +196,14 @@ class AddressSummary extends React.Component { {token.name} ({token.symbol}) ); - } else { - return ( - this.props.tokenSelectChanged(e)} - /> - ); } + return ( + this.props.tokenSelectChanged(e)} + /> + ); }; const renderTokenOptions = () => { diff --git a/src/components/AddressSummaryLegacy.js b/src/components/AddressSummaryLegacy.js index 069c369..e3b19c6 100644 --- a/src/components/AddressSummaryLegacy.js +++ b/src/components/AddressSummaryLegacy.js @@ -50,9 +50,8 @@ class AddressSummary extends React.Component { if (this.props.isNFT) { return 'NFT'; - } else { - return 'Custom token'; } + return 'Custom token'; }; const renderValue = value => { @@ -93,13 +92,12 @@ class AddressSummary extends React.Component { {balance.name} ({balance.symbol}) ); - } else { - return ( - - ); } + return ( + + ); }; const renderTokenOptions = () => { diff --git a/src/components/feature_activation/Features.js b/src/components/feature_activation/Features.js index 100fd0f..9a6c0d9 100644 --- a/src/components/feature_activation/Features.js +++ b/src/components/feature_activation/Features.js @@ -9,12 +9,12 @@ import React from 'react'; import ReactLoading from 'react-loading'; import { Link } from 'react-router-dom'; import { chunk, orderBy } from 'lodash'; +import { numberUtils } from '@hathor/wallet-lib'; import { FEATURE_COUNT } from '../../constants'; import FeatureRow from './FeatureRow'; import colors from '../../index.scss'; import PaginationURL from '../../utils/pagination'; import featureApi from '../../api/featureApi'; -import { numberUtils } from '@hathor/wallet-lib'; class Features extends React.Component { constructor(props) { @@ -39,9 +39,9 @@ class Features extends React.Component { featureApi.getFeatures().then(this.handleFeatures, e => console.error(e)); } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate(_prevProps, _prevState) { const { page = 1 } = this.pagination.obtainQueryParams(); - const newPage = parseInt(page); + const newPage = parseInt(page, 10); if (this.state.page === newPage) { return; @@ -68,6 +68,7 @@ class Features extends React.Component { }; hasBefore = () => this.state.page > 1; + hasAfter = () => this.state.page < this.state.pages.length; getPageFeatures = () => { @@ -183,7 +184,7 @@ class Features extends React.Component { const loadColumnDescriptions = () => { return this.getColumnDescriptions().map(({ name, description }) => { return ( -
+

{description}

diff --git a/src/components/timeseries/ScreenStatusMessage.js b/src/components/timeseries/ScreenStatusMessage.js index 179e21f..d63787e 100644 --- a/src/components/timeseries/ScreenStatusMessage.js +++ b/src/components/timeseries/ScreenStatusMessage.js @@ -1,10 +1,10 @@ import React from 'react'; import { get } from 'lodash'; +import { numberUtils } from '@hathor/wallet-lib'; import blockApi from '../../api/blockApi'; import { SCREEN_STATUS_LOOP_INTERVAL_IN_SECONDS } from '../../constants'; import dateFormatter from '../../utils/date'; -import { numberUtils } from '@hathor/wallet-lib'; import ErrorMessageWithIcon from '../error/ErrorMessageWithIcon'; import Loading from '../Loading'; @@ -60,11 +60,17 @@ class ScreenStatusMessage extends React.Component { render() { const height = numberUtils.prettyValue(this.state.height, 0); + if (this.state.error) { + return ( +
+ +
+ ); + } + return (
- {this.state.error ? ( - - ) : this.state.loading ? ( + {this.state.loading ? ( ) : (

diff --git a/src/components/token/TokenAutoCompleteField.js b/src/components/token/TokenAutoCompleteField.js index c42fa27..c6899af 100644 --- a/src/components/token/TokenAutoCompleteField.js +++ b/src/components/token/TokenAutoCompleteField.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -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'; +import tokensApi from '../../api/tokensApi'; +import Loading from '../Loading'; const DEBOUNCE_SEARCH_TIME = 200; // ms @@ -45,7 +45,7 @@ class TokenAutoCompleteField extends React.Component { * @param {*} event */ _handleClick(e) { - const target = e.target; + const { target } = e; if (!target) { return; } @@ -262,7 +262,11 @@ class TokenAutoCompleteField extends React.Component { _renderAutocompleteResults() { return this.state.searchResults.map(result => ( -

  • this.onItemSelected(result)} className="autocomplete-result-item"> +
  • this.onItemSelected(result)} + className="autocomplete-result-item" + > {result.name} ({result.symbol}) - {result.id}
  • )); diff --git a/src/components/token/Tokens.js b/src/components/token/Tokens.js index d2c14e6..042df49 100644 --- a/src/components/token/Tokens.js +++ b/src/components/token/Tokens.js @@ -7,12 +7,12 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { get, last, find, isEmpty } from 'lodash'; +import { withRouter } from 'react-router-dom'; import TokensTable from './TokensTable'; import TokenSearchField from './TokenSearchField'; import tokensApi from '../../api/tokensApi'; -import { get, last, find, isEmpty } from 'lodash'; import PaginationURL from '../../utils/pagination'; -import { withRouter } from 'react-router-dom'; import ErrorMessageWithIcon from '../error/ErrorMessageWithIcon'; import helpers from '../../utils/helpers'; @@ -127,7 +127,7 @@ class Tokens extends React.Component { this.setState({ isSearchLoading: true }); const tokens = await this.getTokens([]); - //When search button is clicked, results return to the first page + // When search button is clicked, results return to the first page this.setState({ isSearchLoading: false, page: 1, @@ -184,7 +184,7 @@ class Tokens extends React.Component { * * @param {*} event */ - nextPageClicked = async event => { + nextPageClicked = async _event => { this.setState({ calculatingPage: true }); const nextPage = this.state.page + 1; @@ -222,7 +222,7 @@ class Tokens extends React.Component { * * @param {*} event */ - previousPageClicked = async event => { + previousPageClicked = async _event => { this.setState({ calculatingPage: true }); const previousPage = this.state.page - 1; @@ -236,7 +236,7 @@ class Tokens extends React.Component { this.setState({ tokens: tokens.hits, hasAfter: true, - hasBefore: previousPage === 1 ? false : true, + hasBefore: previousPage !== 1, page: previousPage, calculatingPage: false, }); diff --git a/src/components/tx/TxData.js b/src/components/tx/TxData.js index 0dd0e3c..aa5aa4e 100644 --- a/src/components/tx/TxData.js +++ b/src/components/tx/TxData.js @@ -6,25 +6,25 @@ */ import $ from 'jquery'; -import HathorAlert from '../HathorAlert'; import React from 'react'; +import Viz from 'viz.js'; +import hathorLib from '@hathor/wallet-lib'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; +import { Link } from 'react-router-dom'; +import { Module, render } from 'viz.js/full.render'; +import { connect } from 'react-redux'; +import { get, upperFirst } from 'lodash'; +import HathorAlert from '../HathorAlert'; import TokenMarkers from '../token/TokenMarkers'; import TxAlerts from './TxAlerts'; import TxMarkers from './TxMarkers'; -import Viz from 'viz.js'; import dateFormatter from '../../utils/date'; -import hathorLib from '@hathor/wallet-lib'; import helpers from '../../utils/helpers'; import metadataApi from '../../api/metadataApi'; import graphvizApi from '../../api/graphvizApi'; -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'; -import { get, upperFirst } from 'lodash'; const mapStateToProps = state => ({ nativeToken: state.serverInfo.native_token, @@ -121,7 +121,7 @@ class TxData extends React.Component { * Add all tokens of this transaction (inputs and outputs) to the state */ calculateTokens = () => { - let tokens = this.props.transaction.tokens; + const { tokens } = this.props.transaction; const metaRequests = tokens.map(token => this.getTokenMetadata(token)); @@ -131,7 +131,7 @@ class TxData extends React.Component { }; getNativeToken = () => { - const nativeToken = this.props.nativeToken; + const { nativeToken } = this.props; return { ...nativeToken, uid: hathorLib.constants.NATIVE_TOKEN_UID }; }; @@ -147,7 +147,7 @@ class TxData extends React.Component { ...token, meta: data, })) - .catch(err => token); + .catch(_err => token); }; /** @@ -215,7 +215,7 @@ class TxData extends React.Component { toggleGraph = async (e, index) => { e.preventDefault(); - let graphs = [...this.state.graphs]; + const graphs = [...this.state.graphs]; graphs[index].showNeighbors = !graphs[index].showNeighbors; this.setState({ graphs }); @@ -299,13 +299,12 @@ class TxData extends React.Component { const renderBlockOrTransaction = () => { if (hathorLib.transactionUtils.isBlock(this.props.transaction)) { return 'block'; - } else { - return 'transaction'; } + return 'transaction'; }; const renderInputs = inputs => { - return inputs.map((input, idx) => { + return inputs.map((input, _idx) => { return (
    {helpers.getShortHash(input.tx_id)} ( @@ -327,30 +326,26 @@ class TxData extends React.Component { if (hathorLib.transactionUtils.isAuthorityOutput(output)) { if (hathorLib.transactionUtils.isMint(output)) { return 'Mint authority'; - } else if (hathorLib.transactionUtils.isMelt(output)) { - return 'Melt authority'; - } else { - // Should never come here - return 'Unknown authority'; } - } else { - if (!this.state.metadataLoaded) { - // We show 'Loading' until all metadatas are loaded - // to prevent switching from decimal to integer if one of the tokens is an NFT - return 'Loading...'; + if (hathorLib.transactionUtils.isMelt(output)) { + return 'Melt authority'; } - - // if it's an NFT token we should show integer value - 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 hathorLib.numberUtils.prettyValue( - output.value, - isNFT ? 0 : this.props.decimalPlaces - ); + // Should never come here + return 'Unknown authority'; + } + if (!this.state.metadataLoaded) { + // We show 'Loading' until all metadatas are loaded + // to prevent switching from decimal to integer if one of the tokens is an NFT + return 'Loading...'; } + + // if it's an NFT token we should show integer value + 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 hathorLib.numberUtils.prettyValue(output.value, isNFT ? 0 : this.props.decimalPlaces); }; const renderOutputLink = idx => { @@ -361,9 +356,8 @@ class TxData extends React.Component { (Spent) ); - } else { - return null; } + return null; }; const renderInputOrOutput = (output, idx, isOutput) => { @@ -387,12 +381,13 @@ class TxData extends React.Component { }; const renderDecodedScript = output => { + let script; switch (output.decoded.type) { case 'P2PKH': case 'MultiSig': return renderP2PKHorMultiSig(output.decoded); default: - let script = output.script; + script = output.script; // Try to parse as script data try { // The output script is decoded to base64 in the full node @@ -401,6 +396,7 @@ class TxData extends React.Component { // In the future we must receive from the full node // the decoded.type as script data but this still needs // some refactor there that won't happen soon + // eslint-disable-next-line new-cap const buff = new Buffer.from(script, 'base64'); const parsedData = hathorLib.scriptsUtils.parseScriptData(buff); return renderDataScript(parsedData.data); @@ -414,7 +410,9 @@ class TxData extends React.Component { try { script = atob(output.script); - } catch {} + } catch (e) { + console.error(e); + } return `Unable to decode script: ${script.trim()}`; } @@ -425,7 +423,7 @@ class TxData extends React.Component { }; const renderP2PKHorMultiSig = decoded => { - var ret = decoded.address; + let ret = decoded.address; if (decoded.timelock) { ret = `${ret} | Locked until ${dateFormatter.parseTimestamp(decoded.timelock)}`; } @@ -435,7 +433,7 @@ class TxData extends React.Component { const renderListWithLinks = (hashes, textDark) => { if (hashes.length === 0) { - return; + return undefined; } if (hashes.length === 1) { const h = hashes[0]; @@ -465,9 +463,7 @@ class TxData extends React.Component { }; const renderTwins = () => { - if (!this.props.meta.twins.length) { - return; - } else { + if (this.props.meta.twins.length) { return (
    This transaction has twin{' '} @@ -476,11 +472,13 @@ class TxData extends React.Component {
    ); } + + return undefined; }; const renderConflicts = () => { - let twins = this.props.meta.twins; - let conflictNotTwin = this.props.meta.conflict_with.length + const { twins } = this.props.meta; + const conflictNotTwin = this.props.meta.conflict_with.length ? this.props.meta.conflict_with.filter(hash => twins.indexOf(hash) < 0) : []; if (!this.props.meta.voided_by.length) { @@ -513,7 +511,7 @@ class TxData extends React.Component {
    ); } - return; + return undefined; } if (!this.props.meta.conflict_with.length) { @@ -590,15 +588,13 @@ class TxData extends React.Component { if (!this.props.confirmationData.success) { return 'Not available'; } - let acc = helpers.roundFloat(this.props.confirmationData.accumulated_weight); + const acc = helpers.roundFloat(this.props.confirmationData.accumulated_weight); if (this.props.confirmationData.accumulated_bigger) { return `Over ${acc}`; - } else { - return acc; } - } else { - return 'Retrieving accumulated weight data...'; + return acc; } + return 'Retrieving accumulated weight data...'; }; const renderHeight = () => { @@ -621,9 +617,8 @@ class TxData extends React.Component { const renderTokenUID = token => { if (token.uid === hathorLib.constants.NATIVE_TOKEN_UID) { return token.uid; - } else { - return {token.uid}; } + return {token.uid}; }; const tokens = this.state.tokens.map(token => { return (