{!this.isPhone && (
)}
-
+
{mobileMoreHTML}
-
diff --git a/src/components/PageHead/HeaderBlank.tsx b/src/components/PageHead/HeaderBlank.tsx
index d5838e5e3..96976905a 100644
--- a/src/components/PageHead/HeaderBlank.tsx
+++ b/src/components/PageHead/HeaderBlank.tsx
@@ -1,3 +1,11 @@
+/*
+ * @Author: AbigailDeng Abigail.deng@ienyan.com
+ * @Date: 2022-09-30 20:43:59
+ * @LastEditors: AbigailDeng Abigail.deng@ienyan.com
+ * @LastEditTime: 2022-10-28 15:13:26
+ * @FilePath: /aelf-block-explorer/src/components/PageHead/HeaderBlank.tsx
+ * @Description: occupy position except to '/' and '/search-'
+ */
import React, { Component } from 'react';
import { withRouter, NextRouter } from 'next/router';
require('./HeaderBlank.less');
diff --git a/src/components/PageHead/HeaderTop.styles.less b/src/components/PageHead/HeaderTop.styles.less
index d932d2189..7cfb9ac03 100644
--- a/src/components/PageHead/HeaderTop.styles.less
+++ b/src/components/PageHead/HeaderTop.styles.less
@@ -37,7 +37,7 @@
}
.net-change-menu {
// Add width for preventing menuItem change to ...
- width: 180px;
+ width: 244px; // 204px
color: @textContext1;
background-color: transparent;
padding-left: 12px;
@@ -47,7 +47,7 @@
}
.ant-menu-item,
.ant-menu-submenu {
- padding-left: 20px;
+ padding-left: 32px;
padding-right: 0;
line-height: @line-height1;
border-bottom: none;
diff --git a/src/components/PageHead/HeaderTop.tsx b/src/components/PageHead/HeaderTop.tsx
index 721f6600d..bb8ef16e4 100644
--- a/src/components/PageHead/HeaderTop.tsx
+++ b/src/components/PageHead/HeaderTop.tsx
@@ -1,3 +1,11 @@
+/*
+ * @Author: AbigailDeng Abigail.deng@ienyan.com
+ * @Date: 2022-10-25 13:48:07
+ * @LastEditors: AbigailDeng Abigail.deng@ienyan.com
+ * @LastEditTime: 2022-10-28 15:14:42
+ * @FilePath: /aelf-block-explorer/src/components/PageHead/HeaderTop.tsx
+ * @Description: header container at top different in pc and mobile
+ */
import { Menu } from 'antd';
import { useCallback, useEffect, useMemo, useState } from 'react';
import clsx from 'clsx';
@@ -22,11 +30,12 @@ interface PropsDto {
}
export default function HeaderTop({ headerClass, menuMode, networkList, showSearch, headers }: PropsDto) {
const { CHAIN_ID } = config;
- let isMobile = !!isPhoneCheckSSR(headers);
+ const [isMobile, setIsMobile] = useState(!!isPhoneCheckSSR(headers));
const [price, setPrice] = useState({ USD: 0 });
const [previousPrice, setPreviousPrice] = useState({ usd: 0 });
+
useEffect(() => {
- isMobile = !!isPhoneCheck();
+ setIsMobile(!!isPhoneCheck());
}, []);
const range = useMemo(() => {
if (price.USD && previousPrice.usd) {
@@ -36,7 +45,6 @@ export default function HeaderTop({ headerClass, menuMode, networkList, showSear
}, [price, previousPrice]);
useEffect(() => {
- //todo change this
if (CHAIN_ID === 'AELF' && NETWORK_TYPE === 'MAIN' && !isMobile) {
const d = new Date();
const day = d.getDate();
@@ -98,7 +106,7 @@ export default function HeaderTop({ headerClass, menuMode, networkList, showSear
diff --git a/src/components/PageHead/header.styles.less b/src/components/PageHead/header.styles.less
index c9f00148a..5ee328281 100644
--- a/src/components/PageHead/header.styles.less
+++ b/src/components/PageHead/header.styles.less
@@ -130,59 +130,6 @@
}
}
- // .ant-menu {
- // color: @textContext;
- // background: transparent;
- // border: none;
-
- // a:hover {
- // color: @hoverColor1 !important;
- // }
-
- // .ant-menu-item {
- // > a {
- // color: @textContext;
- // }
- // border-bottom: none;
- // }
-
- // .ant-menu-item-group-title {
- // color: @textContext;
- // }
-
- // .ant-menu-item-selected,
- // .ant-menu-item-active,
- // .ant-menu-submenu-active,
- // .ant-menu-submenu-selected {
- // border-bottom: none;
- // color: @hoverColor1;
- // > a {
- // border-bottom: none;
- // color: @hoverColor1 !important;
- // }
- // > a:hover {
- // color: @hoverColor1;
- // }
- // }
- // .ant-menu-item-active,
- // .ant-menu-submenu-active {
- // .submenu-arrow {
- // transform: rotateX(180deg);
- // }
- // }
-
- // .ant-menu-submenu-title:hover {
- // color: @hoverColor1;
- // }
-
- // .ant-menu-submenu-selected .ant-menu-submenu-title {
- // .submenu-title-wrapper {
- // display: inline-block;
- // border-bottom: none;
- // }
- // }
- // }
-
.header-navbar {
display: flex;
background: white;
@@ -193,47 +140,6 @@
padding-right: 24px;
}
- // .ant-menu-item,
- // .ant-menu-submenu {
- // line-height: @line-height2;
- // transform: none;
- // border-bottom: none;
- // // padding: 0 10px !important;
- // &:hover {
- // color: @hoverColor1;
- // //border-bottom: 2px solid @hoverColor1;
- // border-bottom: none;
- // }
- // }
- // .ant-menu-item,
- // .ant-menu-submenu-title,
- // .ant-menu-submenu-inline {
- // transition: none;
- // }
- // .ant-menu-submenu {
- // transition: none;
- // }
-
- // .ant-menu-horizontal {
- // > .ant-menu-item {
- // > a {
- // display: inline-block;
- // color: @textContext;
- // border-bottom: none;
- // }
- // }
- // }
-
- // .ant-menu-horizontal {
- // > .ant-menu-item-selected {
- // > a {
- // display: inline-block;
- // color: @hoverColor1;
- // border-bottom: none;
- // }
- // }
- // }
-
.header-navbar-mobile-more {
display: none;
}
@@ -253,8 +159,19 @@
display: inline-block;
}
}
+.rc-menu-submenu {
+ .rc-menu-submenu-title {
+ cursor: pointer;
+ }
+}
+
.rc-menu-item,
.rc-menu-submenu {
+ // increase click area
+ .menu-item-title {
+ cursor: pointer;
+ display: block;
+ }
> a {
display: inline-block;
}
@@ -617,9 +534,36 @@
.net-select-container .ant-select-selection {
border-color: @borderWhite;
}
- .ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected,
+
.rc-menu:not(.rc-menu-horizontal) .rc-menu-item-selected {
background: none !important;
+ &.rc-menu-item {
+ a {
+ color: @primary-blue;
+ }
+ }
+ }
+ .rc-menu-sub {
+ .rc-menu-item-selected {
+ a {
+ color: white !important;
+ }
+ }
+ }
+ .ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
+ background: none !important;
+ &.ant-menu-item {
+ a {
+ color: @primary-blue;
+ }
+ }
+ }
+ .antmenu-sub {
+ .ant-menu-item-selected {
+ a {
+ color: white !important;
+ }
+ }
}
.common-select-wrapper {
&.ant-select-open,
diff --git a/src/components/Save/index.tsx b/src/components/Save/index.tsx
new file mode 100644
index 000000000..1ec5b391f
--- /dev/null
+++ b/src/components/Save/index.tsx
@@ -0,0 +1,62 @@
+/*
+ * @Author: AbigailDeng Abigail.deng@ienyan.com
+ * @Date: 2022-10-19 18:00:07
+ * @LastEditors: AbigailDeng Abigail.deng@ienyan.com
+ * @LastEditTime: 2022-10-28 15:16:12
+ * @FilePath: /aelf-block-explorer/src/components/Save/index.tsx
+ * @Description: save as file
+ */
+
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { saveAs } from 'file-saver';
+import { DownloadOutlined } from '@ant-design/icons';
+import { Button, message } from 'antd';
+import { getZip } from 'utils/file';
+
+const SaveAsFile = (props) => {
+ const [loading, setIsLoading] = useState(false);
+ const { title, files, fileName, buttonType = 'button', fileType = '.zip', ...rest } = props;
+
+ const download = async () => {
+ setIsLoading(true);
+ try {
+ const blob = fileType === '.zip' ? await getZip(files) : new Blob([JSON.stringify(files)], { type: 'text/json' });
+
+ saveAs(blob, `${fileName}${fileType}`);
+ } catch (e) {
+ message.error('Download failed');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ return buttonType === 'button' ? (
+
} loading={loading} onClick={download} className={rest.className} />
+ ) : (
+
+ );
+};
+
+SaveAsFile.propTypes = {
+ files: PropTypes.arrayOf(
+ PropTypes.shape({
+ name: PropTypes.string.isRequired,
+ // eslint-disable-next-line react/forbid-prop-types
+ files: PropTypes.arrayOf(PropTypes.object),
+ content: PropTypes.string,
+ }),
+ ).isRequired,
+ fileName: PropTypes.string.isRequired,
+ title: PropTypes.string,
+ buttonType: PropTypes.string,
+ fileType: PropTypes.string,
+ className: PropTypes.string,
+};
+
+SaveAsFile.defaultProps = {
+ title: 'Download file',
+};
+
+export default SaveAsFile;
diff --git a/src/components/Search/Search.js b/src/components/Search/Search.js
deleted file mode 100644
index c407c8287..000000000
--- a/src/components/Search/Search.js
+++ /dev/null
@@ -1,139 +0,0 @@
-/**
- * @file Search.js
- * @author huangzongzhe
- */
-import React, { PureComponent } from 'react';
-import AElf from 'aelf-sdk';
-import { withRouter } from 'next/router';
-import { isAElfAddress, get } from 'utils/axios';
-import { Input, message } from 'antd';
-import { SearchOutlined, CloseCircleOutlined } from '@ant-design/icons';
-import IconFont from '../../components/IconFont';
-require('./search.styles.less');
-
-class Search extends PureComponent {
- state = {
- content: '',
- };
-
- emitEmpty = () => {
- this.userNameInput.focus();
- this.setState({
- content: '',
- });
- };
-
- onChangeUserName = (e) => {
- this.setState({
- content: e.target.value,
- });
- };
-
- SEARCHRULES = {
- address: (value, navigate) => {
- this.setState({
- content: '',
- });
- navigate(`/address/${value}`);
- },
- transaction: async (value, navigate) => {
- const getTxsOption = {
- limit: 1,
- page: 0,
- block_hash: value,
- };
-
- const blockInfo = await get('/block/transactions', getTxsOption);
- const isBlock = blockInfo.transactions && blockInfo.transactions.length;
- this.setState({
- content: '',
- });
- if (isBlock) {
- navigate(`/block/${value}`);
- } else {
- navigate(`/tx/${value}`);
- }
- },
- blockHeight: (value, navigate) => {
- let number = parseInt(value, 10);
- if (number == value) {
- this.setState({
- content: '',
- });
- navigate(`/block/${value}`);
- return true;
- }
- return false;
- },
- };
-
- handleSearch = (e) => {
- let value = (e.target && e.target.value) || e.searchValue || '';
- value = value.trim();
- if (!value.trim()) {
- return;
- }
-
- if (value.indexOf('_') > 0) {
- value = value.split('_')[1];
- }
-
- // AElf.utils;
- // value = ;
-
- const { length } = value;
- const isTxid = [64];
-
- // address.length === 38/66 && address.match(/^0x/)
- // search
- // 0. 0x
- // 1. transaction 66
- // 2. block 66
- // 3. address length=38
- if (`${value}`.startsWith('-')) {
- location.href = '/search-invalid/' + ((e.target && e.target.value) || e.searchValue);
- return;
- }
- if (+value === 0) {
- location.href = '/search-invalid/' + ((e.target && e.target.value) || e.searchValue);
- return;
- }
-
- if (isAElfAddress(value)) {
- const address = AElf.utils.encodeAddressRep(AElf.utils.decodeAddressRep(value));
- this.SEARCHRULES.address(address, this.props.router.push);
- } else if (isTxid.includes(length)) {
- this.SEARCHRULES.transaction(value, this.props.router.push);
- } else {
- this.SEARCHRULES.blockHeight(value, this.props.router.push) ||
- (location.href = '/search-invalid/' + ((e.target && e.target.value) || e.searchValue));
- }
- };
-
- render() {
- const { content } = this.state;
- return (
-
- (this.userNameInput = node)}
- onPressEnter={this.handleSearch}
- />
-
- this.handleSearch({
- searchValue: this.state.content,
- })
- }>
-
-
-
- );
- }
-}
-
-export default withRouter(Search);
diff --git a/src/components/Search/Search.tsx b/src/components/Search/Search.tsx
new file mode 100644
index 000000000..83f029d71
--- /dev/null
+++ b/src/components/Search/Search.tsx
@@ -0,0 +1,72 @@
+/*
+ * @Author: AbigailDeng Abigail.deng@ienyan.com
+ * @Date: 2022-10-19 18:00:07
+ * @LastEditors: AbigailDeng Abigail.deng@ienyan.com
+ * @LastEditTime: 2022-10-28 15:16:42
+ * @FilePath: /aelf-block-explorer/src/components/Search/Search.tsx
+ * @Description: search input
+ */
+
+import React, { PureComponent } from 'react';
+import { NextRouter, withRouter } from 'next/router';
+import { Input } from 'antd';
+import IconFont from '../IconFont';
+import { getHandleSearch } from 'utils/search';
+require('./search.styles.less');
+
+interface IProps {
+ router: NextRouter;
+}
+class Search extends PureComponent<
+ IProps,
+ {
+ content: string;
+ }
+> {
+ constructor(props) {
+ super(props);
+ this.state = {
+ content: '',
+ };
+ }
+
+ handleSearch = () => {
+ const navigate = this.props.router.push;
+ const { content } = this.state;
+ getHandleSearch(navigate, content)();
+ };
+ userNameInput;
+ emitEmpty = () => {
+ this.userNameInput.focus();
+ this.setState({
+ content: '',
+ });
+ };
+
+ onChangeUserName = (e) => {
+ this.setState({
+ content: e.target.value,
+ });
+ };
+
+ render() {
+ const { content } = this.state;
+ return (
+
+ (this.userNameInput = node)}
+ onPressEnter={this.handleSearch}
+ />
+
+
+
+
+ );
+ }
+}
+
+export default withRouter(Search);
diff --git a/src/components/StatisticalData/index.js b/src/components/StatisticalData/index.tsx
similarity index 78%
rename from src/components/StatisticalData/index.js
rename to src/components/StatisticalData/index.tsx
index 91fe62e99..4f98312d3 100644
--- a/src/components/StatisticalData/index.js
+++ b/src/components/StatisticalData/index.tsx
@@ -1,21 +1,22 @@
/*
- * @Author: Alfred Yang
- * @Github: https://github.com/cat-walk
- * @Date: 2019-09-09 18:52:15
- * @LastEditors: Alfred Yang
- * @LastEditTime: 2019-12-10 17:00:44
+ * @Author: AbigailDeng Abigail.deng@ienyan.com
+ * @Date: 2022-09-29 17:14:01
+ * @LastEditors: AbigailDeng Abigail.deng@ienyan.com
+ * @LastEditTime: 2022-10-28 15:17:36
+ * @FilePath: /aelf-block-explorer/src/components/StatisticalData/index.tsx
* @Description: file content
*/
+
import React, { PureComponent } from 'react';
import { Tooltip, Statistic, Spin } from 'antd';
import { ExclamationCircleOutlined } from '@ant-design/icons';
+import { IProps, IState } from './types';
require('./index.less');
const { Countdown } = Statistic;
const clsPrefix = 'statistical-data';
const arrFormate = function (arr) {
- // const arr = new Array(arrInput);
switch (arr.length) {
case 4:
// eslint-disable-next-line no-return-assign, no-param-reassign
@@ -29,11 +30,11 @@ const arrFormate = function (arr) {
return arr;
};
-export default class StatisticalData extends PureComponent {
+export default class StatisticalData extends PureComponent
{
constructor(props) {
super(props);
this.state = {
- arr: null,
+ arr: [],
};
}
@@ -44,7 +45,7 @@ export default class StatisticalData extends PureComponent {
});
}
- componentDidUpdate(prevProps, prevState) {
+ componentDidUpdate(prevProps) {
const { data } = this.props;
if (prevProps.data !== data) {
@@ -56,11 +57,11 @@ export default class StatisticalData extends PureComponent {
handleFinish(id) {
const { arr } = this.state;
- // todo: limit the data's type to object
- const countdown = arr.find((item) => item.id === id);
+ // TODO: limit the data's type to object
+ const countdown = arr.find((item) => item.id === id)!;
countdown.num = Date.now() + countdown.resetTime;
this.setState({ arr: [...arr] });
- // todo: update the current term number at the same time
+ // TODO: update the current term number at the same time
}
renderList(arr) {
@@ -86,7 +87,7 @@ export default class StatisticalData extends PureComponent {
}
render() {
- const { spinning, style, tooltip, inline } = this.props;
+ const { spinning = false, style, tooltip, inline } = this.props;
const { arr } = this.state;
if (!arr) return null;
@@ -109,7 +110,3 @@ export default class StatisticalData extends PureComponent {
);
}
}
-
-StatisticalData.defaultProps = {
- spinning: false,
-};
diff --git a/src/components/StatisticalData/types.ts b/src/components/StatisticalData/types.ts
new file mode 100644
index 000000000..5404bf9d7
--- /dev/null
+++ b/src/components/StatisticalData/types.ts
@@ -0,0 +1,69 @@
+import { CSSProperties } from 'react';
+
+export interface IProps {
+ data: Data;
+ spinning: boolean;
+ style?: CSSProperties;
+ tooltip?: string;
+ inline?: boolean;
+}
+
+interface Data {
+ termEndTime: TermEndTime;
+ currentNodesAmount: CurrentNodesAmount;
+ currentVotesAmount: CurrentNodesAmount;
+ currentMiningReward: CurrentMiningReward;
+}
+
+interface CurrentMiningReward {
+ id: number;
+ title: string;
+ span: number;
+ isRender: boolean;
+ num: Num;
+}
+
+interface Num {
+ key: string;
+ ref?: any;
+ props: Props;
+ owner?: any;
+ store: any;
+}
+
+interface Props {
+ dividends: Dividends;
+}
+
+interface Dividends {
+ eLF: number;
+}
+
+interface CurrentNodesAmount {
+ id: number;
+ title: string;
+ span: number;
+ num: number;
+}
+
+interface TermEndTime {
+ id: number;
+ title: string;
+ isCountdown: boolean;
+ resetTime: number;
+ span: number;
+ num: string;
+}
+export interface IState {
+ arr: Arr[];
+}
+
+interface Arr {
+ id: number;
+ title: string;
+ isCountdown?: boolean;
+ resetTime: number;
+ span: number;
+ num: Num | number | string;
+ isRender?: boolean;
+}
diff --git a/src/components/StatusTag/StatusTag.jsx b/src/components/StatusTag/StatusTag.tsx
similarity index 62%
rename from src/components/StatusTag/StatusTag.jsx
rename to src/components/StatusTag/StatusTag.tsx
index 223b9635d..79d5cf8f9 100644
--- a/src/components/StatusTag/StatusTag.jsx
+++ b/src/components/StatusTag/StatusTag.tsx
@@ -1,8 +1,18 @@
+/*
+ * @Author: AbigailDeng Abigail.deng@ienyan.com
+ * @Date: 2022-09-29 17:14:01
+ * @LastEditors: AbigailDeng Abigail.deng@ienyan.com
+ * @LastEditTime: 2022-10-28 15:18:40
+ * @FilePath: /aelf-block-explorer/src/components/StatusTag/StatusTag.tsx
+ * @Description: status tag
+ */
+
import React, { useMemo } from 'react';
import IconFont from '../IconFont/index';
require('./StatusTag.style.less');
-export default function StatusTag({ status }) {
+
+export default function StatusTag({ status }: { status: 'MINED' | 'FAILED' | 'PENDING' }) {
const statusObj = useMemo(
() => ({
MINED: 'Success',
diff --git a/src/components/Svg/Svg.js b/src/components/Svg/Svg.js
deleted file mode 100644
index 2ca4e15ed..000000000
--- a/src/components/Svg/Svg.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * @file Svg
- * @author zhouminghui
- */
-
-import React, { PureComponent } from 'react';
-// import svgList from '../../assets/svgList';
-import svgList from 'assets/svgs';
-
-export default class Svg extends PureComponent {
- constructor(props) {
- super(props);
- this.state = {
- style: this.props.style,
- };
- }
-
- render() {
- const { icon } = this.props;
- const svg = svgList[icon];
-
- return (
-
- );
- }
-}
diff --git a/src/components/Svg/Svg.tsx b/src/components/Svg/Svg.tsx
new file mode 100644
index 000000000..8a3f86890
--- /dev/null
+++ b/src/components/Svg/Svg.tsx
@@ -0,0 +1,30 @@
+/*
+ * @Author: AbigailDeng Abigail.deng@ienyan.com
+ * @Date: 2022-09-29 10:12:13
+ * @LastEditors: AbigailDeng Abigail.deng@ienyan.com
+ * @LastEditTime: 2022-10-28 15:22:48
+ * @FilePath: /aelf-block-explorer/src/components/Svg/Svg.tsx
+ * @Description: show svg
+ */
+
+import React, { CSSProperties, MouseEventHandler, PureComponent } from 'react';
+import svgList from 'assets/svgs';
+
+interface IProps {
+ style?: CSSProperties;
+ className: string;
+ icon: string;
+ click?: MouseEventHandler;
+}
+export default class Svg extends PureComponent {
+ constructor(props) {
+ super(props);
+ }
+
+ render() {
+ const { icon } = this.props;
+ const svg = svgList[icon];
+
+ return ;
+ }
+}
diff --git a/src/components/TPSChart/TPSChart.js b/src/components/TPSChart/TPSChart.tsx
similarity index 87%
rename from src/components/TPSChart/TPSChart.js
rename to src/components/TPSChart/TPSChart.tsx
index 03fc65d01..a6ab06c99 100644
--- a/src/components/TPSChart/TPSChart.js
+++ b/src/components/TPSChart/TPSChart.tsx
@@ -1,24 +1,56 @@
-import React from 'react';
+/*
+ * @Author: AbigailDeng Abigail.deng@ienyan.com
+ * @Date: 2022-10-25 17:10:46
+ * @LastEditors: AbigailDeng Abigail.deng@ienyan.com
+ * @LastEditTime: 2022-10-28 15:24:08
+ * @FilePath: /aelf-block-explorer/src/components/TPSChart/TPSChart.tsx
+ * @Description: tps chart used in home page and only for csr
+ */
+
+import React, { RefObject } from 'react';
// import the core library.
import ReactEchartsCore from 'echarts-for-react/lib/core';
-
// then import echarts modules those you have used manually.
import echarts from 'echarts/lib/echarts';
import 'echarts/lib/chart/line';
import 'echarts/lib/component/legend';
import 'echarts/lib/component/tooltip';
import { CHAIN_ID } from '../../constants';
-
-require('./TPSChart.styles.less');
-
import { get } from 'utils/axios';
import { TPS_LIST_API_URL } from '../../constants';
+require('./TPSChart.styles.less');
const symbol =
'image://data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABGdBTUEAALGPC/xhBQAAC/9JREFUaAXVmsuLZEkVxs99ZVZmPaab6dZZFFogyGxmMYK4EKQZEHrlSsGNO5f+Of4HggjOWgYGpDeuRmaE2ciA0EovemzbmqnK1337+yJuZN6bmV1VXT1gd8CtuHnzxonvixNx4sSXFdk3UNq2jV7FTBRF7au0V9tbAdgCvrHxx87e/WvsPjMP/Bdd7VmsydyG2AbEDYakR8C3E/AA+gvu73I9tejxsSeSnvs6mK7ueuBnl9TvcJ1zfd8/M5HbEHOkXobQjYi8kIDA5xYL+LiwOL6wKJ5aFM2oJ9SLIZF2am2ztLY9ol5wnVibj6xxxMbWOFK3JHQtkQGJ4IFnFmvkn1QWZ7XFSWPxbGRJ2vr7qIAI9/LEIvNkpqUf+Sayph1ZW8fWVNwfFVbrvkysOU0hI0/dp94QupF3XkhkQECIHgGs84AIjMeWJJCYzS3JMu5zS/KUurQEo3EBkSjFK5Un0qZ4orJ2JCJmTZ1ZPa4gMba6LK0+OuQeMnlutSMUPPQAUr5cSeg6IpH1vPB4bvHkPwCHhAiMWktzroyr5ErM0pqLGBTHCYRqixhp1weea9uE6VRbTYxreLfi3SqLrCq5xlwFlwiVkFnes/rsEBI39M5eIp03BiSePLFEXpgXlo5rS4vcslFiWVVaxkhmaeXrBEIiEzfeM0wf1wfTrpUnmth4HRKAxqNllVqpOs2sLKhHYyvzxKrDkVXOO6dWb5PZFwR2iOyQ+NwSTaWD0tJFA4kU8I2Nai7myCirbIzvcQ4Xn+PYsqaxjDqhTpiQvo/GWp7VPKupS+oyigwnWMFiKsrU8pjPSWxFypVXVk5jq1aZVW6qvQeh3rrZJjMgMlgXWhMsanlCJDCYjZZu1Md1aeM2tjHL8gACB65u+MwzR6aFjKZYhFfabo1EeKSFBN5gwTsSTJw8iol7ka0AsnI1z5KMq7a8mFhJ36UjM/SMWzd9Mmm3kPpV5Bb2FomqshHTZtyWdgC4g6q1CfUUMBPATAAywchBOrF347G9b6l9B3+c8M6JjLMuLhjTC6vsX01un9Ur+zuPV1zLtuaKnVe13hL6iJl6EX1GkDDIGANqpzKkNeM3crf49Uhl7ZHBlDpnbyCcHi4sXV5YdqCppNHnYlFPacSOYFPm+2HU2CFT4SR6yx7GI/sAwEfe9NV/IThrCvtz+7V9xFS9gMicdTUH0QKEC4LAAo+uCIGrFVNtcmLlfGrVGeGajbfpNs82eGXXI9qpnxKdLljYqaUT1kRde084EoBnbRwxn9nW7Cg7sR/ji5/T6Z3BEF3NQx46isb2M0b4J9nSPiwu7S9MzZThTrAdldzgDKsJ22OiHUGmnbCJugl9nxon97twHhl4A0NaF1MI5AULusYThZ9GRKMjRvIICycAOU7v2i/xwk/7Bm97j3c+rs7tD9i/BNQF9mdEtxn1IhrZknC+Go+sWBAETrVe4Nj3itt91517b0QKs9onFGIVnbSwS9YBYzDl/lgksrftN98UCfUvW7Ip2+pDfalP9S0MwiJMwqasAk+ul4Xab6aWNj6SPIXaqeyyH7CBZTlGWORa4G5N0MFRhifYtd+XgX2lYQm7q8D/Lr6wGBkywBLH/bWvnWzKy+W5/Z5pVtGnRr5iKldMr4r1qL2nFsbTL1gnz8DcJZqBiGfI2lDuNCPdYGGnMZsdUWRMzDxw0cnsUGsCV++dTkQiqy8BX+3CJDKxXfsLwJYcc0Fqu8gzo2P7Z3lB7DQrGcyCvapkYy0JLmUxh1QKQZJVvKJ1Is+0aW/vMGWxR0vIkDu1K3ZvPEK4JMXDI7iZOXuihU3DnVJBoJ75x6NvsZh+QDz+Hi5/yz+rvibO/oNJ/ym7378Z5nN6J76lENop9JHO7FO45/TNpLBc2cOIDTlSXsdgC+tZ11AcgkdMCeEYryuLnS4sWeIRpR0YGTE9SIVY8HftoaJT135drUkwfd5+aHb8w/VX65vsHlGI6+RHZpefmD3/aEN8m4zrg3DOVP+QIV8y5iNhISdLJ8yW2ZS1ojCsJPaB78Ivdq0PDkU6TygVd1ksg6VR0E6tHZvrALd/sEbW3bjpJE9g6Z1f7Sex3UZE9a7ayIuysV1cX5rSXbYgLMyIVNiEUVjdQU7YKZ6I7ogEOhTpbKFUvCJ7VQJIXuTIMJ/fJaLsbHZaEyryxMGZu73RH72rNirBhv/k/6ov12eXvwmLwwQ2YRRWF726Rp4IoUxzTic7HYqgGMeVMRXZoJQAkju5tKPfE/eKTFrYWhP7ptPW6zsf1UZtZUO2tov6VN/CICzCJGwOI1iFOYThjUewouOpTnY6FOk8oVQcjzAAhGnlTlsldK6FfdsS2gZbAzv0qb6FwWEBk8MmfGDtv7smIqEgnLEJAZEORWLPSMTcYMInf/3G7MauKDrdtoS2wdbAjpJO+nYYhEOYwKZ3hLUvbqyJBAM6Y+t4qpOdDkXOlTLSZbHhPdVhswshtv/dTe9D22Cr3059hv6FRZiELegA/Xd3iPS//H/fs+BvXHaISO2QUKAzdjieEssbjF5sW1XaoaLN7rYltA22tuxcuL7pX1jcuR9sQZHpv7smIvFMupO+lNohoUBGiN0NN2Q4u0SUO6lox75tCW2DrYEd+lTfDoNwCBPY9I6wBsFPn9dE9EHimXQnSTZSOyQUEPrc8VQnO73TL0oAVZR23LaEtsHWwA59ArASBocFTA6b8IG1/64nwqFeap8UQIln8kRDYsZIVIS+UmdsHU/7DXWvzpXkKHdS2vGyRW3UVjb2EVGf6lsYhEWYhM1hBKtTKCVIUDYeQeGTjCnVT+IZmk4tyYbRcEKBzthEESUjg6IsVkW50+qxu73RH72rNirBhv/k/6ov1yfKijAIi8MENmEUVqdKdo08EeX0CMrSYglzjVMAGQEC+FrtII1f6Yzd70z3SsUTJS7sXE9/dzPPyBN6V23Udl867/qiT97J2QMKYWE/qYRNGIXVieBb5xGTgJw/Rov9CqUPGTNbofwluLQy5GnSaakdCAU6Y2N4kAGH7FUJ4PM/Qeav16fx2HMkQlt9DoW5/ZXrK7YlETjHO4WEPNL4ShKr9OLZHSieQaYraE8R6byP2JpzX0pQRovlifMILitw60qSDSnRhYQCNJRfBwOhFiDOCi4B1Lz/bzdtwvf9WmviRQcr9x59SFnhfhlxVidtUtJecvStIrDFiN1ufXRGxQGTrrROxUM4Liu8AutiZlWLjMmpJifZyjg7S03MpHZkqX2XcLlzSnTTjKmmvMldL3nUFRKm1MelV1TmeEMeWUmFHLPgddQdoQ0jQEgO9Io9kVjtAhFzZ99H1p5+bs0zVHEJynhBEYN8GI8oeavJuJBspHYgFNxjZPee2xWB9kUhdXhVYY/4TLaRg2bAnLOJ6ReWlaRU6cJsmlWinx/gq6VgDzZTaxO11INCGdFL4rFUcQnK0mIZiZyjok5qC+4vmbOX5XP7rUbvKmAv853zBDZlW32oL/WpvoVBWIRJ2Fy06sJu6APP+dKtk8jJpQjXX+aojGhbE87sTtsqbTIQ6FjPEuoQCtYCXbD1MrUWNkPkBDp5gkA2U00SOXdqY+Y1LbAUYCm/PWZrlqD9AK8wrV6sNIop8295bDWSabREWkYyjaTFspbZGd3mQ+aDMVwttUNCgb2iZIqtuaYTJBaQ20im6CXM6/IQuXR+hz4L3tryhgZu7RF9GHhlj4jNgV9qu9eAJVyjdWHWidiqAXOg4+mNRWztE4RYRh/thjpMJ9YFIX7F74t5iid2FPktbwj7ZrHrky8+gqF6s6icCi41XKo4Z/lWWmxTkkxGjA4blHQnvMMPV4AxFMmlfRIv7G847vqfFbRH6CLEKjppYUMmj7ufFSCx/2cFxtxD3fwdeESP116ROiFZ8k38oSfw2yHTTbM36qe3nld0O/gd8Y37MXSHjB48YjlK2UNzlYgs75DIvf4/Twu7SjfNdDvwjsQxEZLoLcHstf2HASEPZUBGD0Mg6Dwkoey1/xeOQEb1CwnpS5F63f+pRjj7pUdIjzchvBOUg4zZbzO4DztzdyjqvlvvDSHtGLS55sMGxDUvXvX1FrGrXt373W2Abxv6H3X/RALNQT4kAAAAAElFTkSuQmCC';
-
+interface IItem {
+ count: number;
+ end: number;
+ start: number;
+}
+interface IProps {
+ own: number[];
+ all: number[];
+}
+interface IState {
+ ownList: IItem[];
+ allList: IItem[];
+}
+interface ILabel {
+ show: boolean;
+}
+interface IDataItem {
+ label: ILabel;
+ symbol: string;
+ symbolSize: number[];
+ value: number;
+}
// The usage of ReactEchartsCore are same with above.
-export default class TPSChart extends React.Component {
+export default class TPSChart extends React.Component {
+ interval: number;
+ delay: number;
+ hadRenderedOnce: boolean;
+ myRef: RefObject;
+ getTpsDataTimer?: ReturnType;
constructor(props) {
super(props);
this.interval = 60 * 1000; // 1 minute
@@ -28,7 +60,7 @@ export default class TPSChart extends React.Component {
allList: props.all || [],
};
this.hadRenderedOnce = false;
- this.getTpsDataTimer = null;
+ this.getTpsDataTimer;
this.myRef = React.createRef();
}
@@ -64,17 +96,15 @@ export default class TPSChart extends React.Component {
getOption() {
const { ownList, allList } = this.state;
-
- const xAxisData = [];
- const allData = [];
- const ownData = [];
+ const xAxisData: string[] = [];
+ const allData: (number | IDataItem)[] = [];
+ const ownData: (number | IDataItem)[] = [];
const { length } = ownList;
ownList.forEach((item, index) => {
- // start or end ?
const startTime = new Date(item.start);
const hours = startTime.getHours();
- let minutes = startTime.getMinutes();
+ let minutes = '' + startTime.getMinutes();
if (`${minutes}`.length === 1) {
minutes = `0${minutes}`;
@@ -84,8 +114,9 @@ export default class TPSChart extends React.Component {
allData.push(allList[index].count);
ownData.push(item.count);
});
+
allData[length - 1] = {
- value: allData[length - 1],
+ value: allData[length - 1] as number,
symbol,
symbolSize: [30, 30],
label: {
@@ -93,7 +124,7 @@ export default class TPSChart extends React.Component {
},
};
ownData[length - 1] = {
- value: ownData[length - 1],
+ value: ownData[length - 1] as number,
symbol,
symbolSize: [30, 30],
label: {
@@ -193,7 +224,7 @@ export default class TPSChart extends React.Component {
if (this.hadRenderedOnce) {
ecahrtsHTML = (
-
- {children}
-
-
- );
-}
diff --git a/src/components/TableLayer/TableLayer.styles.less b/src/components/TableLayer/TableLayer.styles.less
index 21b51ab82..267e87cf4 100644
--- a/src/components/TableLayer/TableLayer.styles.less
+++ b/src/components/TableLayer/TableLayer.styles.less
@@ -4,10 +4,12 @@
background-color: @bgBlankWhite !important;
overflow-x: auto;
padding-bottom: 16px;
+ padding-top: 8px;
width: 100%;
+ border-radius: 8px;
> .table-layer-block {
min-width: 16px;
- height: 45px;
+ height: 41px;
border-bottom: @border-color2 1px solid !important;
}
.ant-table-wrapper {
@@ -15,7 +17,7 @@
.ant-table-thead {
tr {
th {
- font-family: 'Roboto-Medium';
+ font-family: 'Roboto-Medium', sans-serif;
background: @bgBlankWhite !important;
border-bottom: @border-color2 1px solid !important;
font-weight: 400;
@@ -42,4 +44,9 @@
background: white;
border-radius: 4px;
padding-bottom: 10px;
+ > .table-layer-block {
+ min-width: 8px;
+ height: 41px;
+ border-bottom: @border-color2 1px solid !important;
+ }
}
diff --git a/src/components/TableLayer/TableLayer.tsx b/src/components/TableLayer/TableLayer.tsx
new file mode 100644
index 000000000..d86946b62
--- /dev/null
+++ b/src/components/TableLayer/TableLayer.tsx
@@ -0,0 +1,26 @@
+/*
+ * @Author: AbigailDeng Abigail.deng@ienyan.com
+ * @Date: 2022-10-25 13:48:07
+ * @LastEditors: AbigailDeng Abigail.deng@ienyan.com
+ * @LastEditTime: 2022-10-28 15:23:05
+ * @FilePath: /aelf-block-explorer/src/components/TableLayer/TableLayer.tsx
+ * @Description: table layer different in pc and mobile
+ */
+import React, { useEffect, useState } from 'react';
+import { isPhoneCheck, isPhoneCheckSSR } from 'utils/deviceCheck';
+
+require('./TableLayer.styles.less');
+export default function TableLayer({ children, className = '', ...props }) {
+ const [isMobile, setIsMobile] = useState(!!isPhoneCheckSSR(props.headers));
+
+ useEffect(() => {
+ setIsMobile(!!isPhoneCheck());
+ }, []);
+ return (
+
+ );
+}
diff --git a/src/components/Total/index.jsx b/src/components/Total/index.jsx
deleted file mode 100644
index 7a9903990..000000000
--- a/src/components/Total/index.jsx
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * @file total
- * @author atom-yang
- */
-import React from 'react';
-
-const Total = (total) => (
-
- Total
- {total} Items
-
-);
-
-export default Total;
diff --git a/src/components/Total/index.tsx b/src/components/Total/index.tsx
new file mode 100644
index 000000000..7c32880b0
--- /dev/null
+++ b/src/components/Total/index.tsx
@@ -0,0 +1,19 @@
+/*
+ * @Author: AbigailDeng Abigail.deng@ienyan.com
+ * @Date: 2022-09-29 17:14:01
+ * @LastEditors: AbigailDeng Abigail.deng@ienyan.com
+ * @LastEditTime: 2022-10-28 15:23:43
+ * @FilePath: /aelf-block-explorer/src/components/Total/index.tsx
+ * @Description: total num used in pagination
+ */
+
+import React from 'react';
+
+const Total = (total) => (
+
+ Total
+ {total} Items
+
+);
+
+export default Total;
diff --git a/src/components/TransactionTable/TransactionTable.styles.less b/src/components/TransactionTable/TransactionTable.styles.less
index b5775134d..9b7ee693d 100644
--- a/src/components/TransactionTable/TransactionTable.styles.less
+++ b/src/components/TransactionTable/TransactionTable.styles.less
@@ -7,7 +7,7 @@
width: calc(100% - 32px);
thead {
th {
- font-family: 'Roboto-Medium';
+ font-family: 'Roboto-Medium', sans-serif;
background: @bgBlankWhite !important;
border-bottom: @border-color2 1px solid !important;
font-weight: 400;
@@ -111,26 +111,23 @@
}
}
}
-.mobile {
- .transaction-table {
- padding: 0 0 10px;
- background: transparent !important;
- overflow-x: scroll;
- .ant-table-wrapper {
- min-width: 968px;
- }
+.mobile.transaction-table {
+ padding: 0 0 10px;
+ overflow-x: scroll;
+ .ant-table-wrapper {
+ min-width: 968px;
}
}
.transaction-tooltip {
.ant-tooltip-content {
width: 337.5px;
- box-shadow: 0px 2px 16px 0px rgba(0, 0, 0, 0.08), 0px 4px 8px 0px rgba(0, 0, 0, 0.08);
+ box-shadow: 0 2px 16px 0 rgb(0 0 0 / 8%), 0 4px 8px 0 rgb(0 0 0 / 8%);
.ant-tooltip-inner {
padding: 0 !important;
background-color: white;
}
- .ant-tooltip-arrow:before {
+ .ant-tooltip-arrow::before {
background-color: white;
}
}
@@ -144,7 +141,7 @@
padding: 11px 20px 11px 22px;
border-bottom: 1px solid @border-color2;
font-size: 14px;
- font-family: 'Roboto-Medium';
+ font-family: 'Roboto-Medium', sans-serif;
line-height: 22px;
color: @mine-shaft;
a {
@@ -213,6 +210,6 @@
}
.ant-tooltip-content {
width: 100%;
- box-shadow: 0px 0px 100000px 100000px rgba(0, 0, 0, 0.6);
+ box-shadow: 0 0 100000px 100000px rgb(0 0 0 / 60%);
}
}
diff --git a/src/components/TransactionTable/TransactionTable.jsx b/src/components/TransactionTable/TransactionTable.tsx
similarity index 57%
rename from src/components/TransactionTable/TransactionTable.jsx
rename to src/components/TransactionTable/TransactionTable.tsx
index 94f3b2b50..fe96591b8 100644
--- a/src/components/TransactionTable/TransactionTable.jsx
+++ b/src/components/TransactionTable/TransactionTable.tsx
@@ -1,3 +1,11 @@
+/*
+ * @Author: AbigailDeng Abigail.deng@ienyan.com
+ * @Date: 2022-10-17 16:40:55
+ * @LastEditors: AbigailDeng Abigail.deng@ienyan.com
+ * @LastEditTime: 2022-10-28 15:25:04
+ * @FilePath: /aelf-block-explorer/src/components/TransactionTable/TransactionTable.tsx
+ * @Description: transaction table
+ */
import { Table } from 'antd';
import React, { useMemo, useState } from 'react';
import { useEffectOnce } from 'react-use';
@@ -6,20 +14,25 @@ import { get } from 'utils/axios';
import TableLayer from '../TableLayer/TableLayer';
import ColumnConfig from './columnConfig';
require('./TransactionTable.styles.less');
-export default function TransactionTable({ dataLoading, dataSource }) {
- const [price, setPrice] = useState(0);
+export default function TransactionTable({ dataLoading, dataSource, price: priceSSR = {}, headers = undefined }) {
+ const [price, setPrice] = useState(priceSSR);
const [timeFormat, setTimeFormat] = useState('Age');
const columns = useMemo(
() =>
- ColumnConfig(timeFormat, price, () => {
- setTimeFormat(timeFormat === 'Age' ? 'Date Time' : 'Age');
- }),
+ ColumnConfig(
+ timeFormat,
+ price,
+ () => {
+ setTimeFormat(timeFormat === 'Age' ? 'Date Time' : 'Age');
+ },
+ headers,
+ ),
[timeFormat, price],
);
useEffectOnce(() => {
- get(ELF_REALTIME_PRICE_URL, { fsym: 'ELF', tsyms: 'USD,BTC,CNY' }).then((price) => setPrice(price));
+ get(ELF_REALTIME_PRICE_URL, { fsym: 'ELF', tsyms: 'USD,BTC,CNY' }).then((res) => setPrice(res));
});
return (
diff --git a/src/components/TransactionTable/columnConfig.jsx b/src/components/TransactionTable/columnConfig.tsx
similarity index 82%
rename from src/components/TransactionTable/columnConfig.jsx
rename to src/components/TransactionTable/columnConfig.tsx
index 96c6867a2..1fb47dc8f 100644
--- a/src/components/TransactionTable/columnConfig.jsx
+++ b/src/components/TransactionTable/columnConfig.tsx
@@ -7,10 +7,12 @@ import addressFormat from 'utils/addressFormat';
import { getFormattedDate } from 'utils/timeUtils';
import StatusTag from 'components/StatusTag/StatusTag';
import IconFont from 'components/IconFont';
-import { isPhoneCheck } from 'utils/deviceCheck';
+import { isPhoneCheck, isPhoneCheckSSR } from 'utils/deviceCheck';
+import { useRouter } from 'next/router';
const PreviewCard = ({ info, text, price = { USD: 0 } }) => {
- const { quantity = 0, decimals, block_height } = info;
+ const { quantity = 0, block_height } = info;
+ const nav = useRouter().push;
const [confirmations, setConfirmations] = useState(0);
const [lastBlockLoading, setLastBlockLoading] = useState(true);
aelf.chain.getChainStatus().then(({ LastIrreversibleBlockHeight }) => {
@@ -25,7 +27,7 @@ const PreviewCard = ({ info, text, price = { USD: 0 } }) => {
} else if (quantity <= 9) {
amount = `0.0000000${quantity}`;
} else {
- amount = quantity / Math.pow(10, 8);
+ amount = '' + quantity / 10 ** 8;
}
}
@@ -33,21 +35,21 @@ const PreviewCard = ({ info, text, price = { USD: 0 } }) => {
Block:
-
(location.href = `/block/${info.block_height}`)}>{info.block_height}
+
nav(`/block/${info.block_height}`)}>{info.block_height}
{!lastBlockLoading ? (
-
+
{confirmations >= 0 ? `${confirmations} Block Confirmations` : 'Unconfirmed'}
) : (
@@ -71,8 +73,11 @@ const PreviewCard = ({ info, text, price = { USD: 0 } }) => {
);
};
-export default (timeFormat, price, handleFormatChange) => {
- const isMobile = isPhoneCheck();
+export default (timeFormat, price, handleFormatChange, headers) => {
+ let isMobile = !!isPhoneCheckSSR(headers);
+ if (typeof window !== 'undefined') {
+ isMobile = !!isPhoneCheck();
+ }
return [
{
dataIndex: 'tx_id',
@@ -128,7 +133,7 @@ export default (timeFormat, price, handleFormatChange) => {
),
width: isMobile ? 140 : 162,
render: (text) => {
- return {getFormattedDate(text, timeFormat)}
;
+ return {getFormattedDate(text, timeFormat)}
;
},
},
{
@@ -153,7 +158,7 @@ export default (timeFormat, price, handleFormatChange) => {
render: (text) => {
return (
-
+
{text}
@@ -167,7 +172,7 @@ export default (timeFormat, price, handleFormatChange) => {
render: (text) => {
return (
);
},
diff --git a/src/components/plugin/index.jsx b/src/components/plugin/index.tsx
similarity index 79%
rename from src/components/plugin/index.jsx
rename to src/components/plugin/index.tsx
index 803ee8ca3..1a87526c3 100644
--- a/src/components/plugin/index.jsx
+++ b/src/components/plugin/index.tsx
@@ -1,3 +1,12 @@
+/*
+ * @Author: AbigailDeng Abigail.deng@ienyan.com
+ * @Date: 2022-09-29 17:14:01
+ * @LastEditors: AbigailDeng Abigail.deng@ienyan.com
+ * @LastEditTime: 2022-10-28 15:14:56
+ * @FilePath: /aelf-block-explorer/src/components/plugin/index.tsx
+ * @Description: download plugin for proposal
+ */
+
import React from 'react';
import { Row, Col } from 'antd';
require('./index.less');
diff --git a/src/constants/api.ts b/src/constants/api.ts
index 73fc87a5e..6ba5616c8 100644
--- a/src/constants/api.ts
+++ b/src/constants/api.ts
@@ -13,7 +13,7 @@ const ADDRESS_BALANCE_API_URL = '/api/address/balance';
const VIEWER_GET_ALL_TOKENS = '/viewer/getAllTokens';
const TPS_LIST_API_URL = '/tps/all';
const ADDRESS_TOKENS_API_URL = '/address/tokens';
-const ELF_REALTIME_PRICE_URL = 'https://min-api.cryptocompare.com/data/price?fsym=ELF&tsyms=USD,BTC,CNY';
+const ELF_REALTIME_PRICE_URL = '/token/price';
const HISTORY_PRICE = '/token/price-history';
const ELF_REST_TRADE_API = 'https://www.bcex.top/Api_Market/getCoinTrade';
const RESOURCE_REALTIME_RECORDS = '/resource/realtime-records';
diff --git a/src/constants/config/config.js b/src/constants/config/config.js
new file mode 100644
index 000000000..61180d5c3
--- /dev/null
+++ b/src/constants/config/config.js
@@ -0,0 +1,42 @@
+/* eslint-disable no-restricted-globals */
+/**
+ * @file config.js
+ * @author huangzongzhe
+ */
+const config = require('./config.json');
+
+// the block chain URL this explorer is serving
+
+const BUILD_ENDPOINT = process.argv[process.argv.indexOf('--CHAIN_ENDPOINT') + 1];
+const MAINCHAINID = 'AELF';
+// MAIN TESTNET
+const NETWORK_TYPE = 'TESTNET';
+// ChainId: AELF
+// ChainId: tDVV(AElf public chain)
+const CHAINS_LINK = {
+ AELF: 'https://explorer-test.aelf.io',
+};
+const CHAINS_LINK_NAMES = {
+ AELF: 'Main chain AELF',
+};
+const WALLET_DOMAIN = 'https://wallet-test.aelf.io/';
+const APPNAME = 'explorer.aelf.io';
+const commonPrivateKey = '0000000000000000000000000000000000000000000000000000000000000001';
+const DEFAUTRPCSERVER =
+ typeof window !== 'undefined'
+ ? `${window?.location?.protocol}//${window?.location?.host}/chain`
+ : `${process.env.BUILD_ENDPOINT_CHAIN}/chain`;
+
+module.exports = {
+ DEFAUTRPCSERVER,
+ commonPrivateKey,
+ MAINCHAINID,
+ NETWORK_TYPE,
+ APPNAME,
+ CHAINS_LINK,
+ CHAINS_LINK_NAMES,
+ // The following variable are with suitable name
+ WALLET_DOMAIN,
+ BUILD_ENDPOINT,
+ ...config,
+};
diff --git a/src/constants/config/config.ts b/src/constants/config/config.ts
deleted file mode 100644
index 062f6f5ff..000000000
--- a/src/constants/config/config.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * @file config.js
- * @author huangzongzhe
- */
-// only used in server
-export let config: any =
- process.env.CHAIN_ID === 'AELF' ? require('../platform/AELF').default : require('../platform/tDVW').default;
-// only used in client
-if (typeof window !== 'undefined') {
- const host = location.host;
- if (host.includes('tdvw') || host.includes('side02')) {
- config = require('../platform/tDVW').default;
- } else {
- config = require('../platform/AELF').default;
- }
-}
-
-export const MAINCHAINID = 'AELF';
-// change handly: MAIN TESTNET
-export const NETWORK_TYPE = 'TESTNET' as string;
-// ChainId: AELF
-// ChainId: tDVV(AElf public chain)
-export const CHAINS_LINK = {
- AELF: 'https://explorer-test.aelf.io',
-};
-export const CHAINS_LINK_NAMES = {
- AELF: 'Main chain AELF',
-};
-export const WALLET_DOMAIN = 'https://wallet-test.aelf.io/';
-export const APPNAME = 'explorer.aelf.io';
-export const commonPrivateKey = '0000000000000000000000000000000000000000000000000000000000000001';
-export const DEFAUTRPCSERVER =
- typeof window !== 'undefined' ? `${window?.location?.protocol}//${window?.location?.host}/chain` : '/chain';
-// const DEFAUTRPCSERVER = `https://explorer-test.aelf.io/chain`;
-module.exports = {
- DEFAUTRPCSERVER,
- commonPrivateKey,
- MAINCHAINID,
- NETWORK_TYPE,
- APPNAME,
- CHAINS_LINK,
- CHAINS_LINK_NAMES,
- // The following variable are with suitable name
- WALLET_DOMAIN,
- ...config,
-};
-
-export default {
- DEFAUTRPCSERVER,
- commonPrivateKey,
- MAINCHAINID,
- NETWORK_TYPE,
- APPNAME,
- CHAINS_LINK,
- CHAINS_LINK_NAMES,
- // The following variable are with suitable name
- WALLET_DOMAIN,
- ...config,
-} as any;
diff --git a/src/constants/config/viewer/config.js b/src/constants/config/viewer/config.js
index 3509f3ee6..bed517d0a 100644
--- a/src/constants/config/viewer/config.js
+++ b/src/constants/config/viewer/config.js
@@ -1,6 +1,6 @@
/* eslint-disable global-require */
-let queriedConfig =
- process.env.CHAIN_ID === 'AELF' ? require('../../platform/AELF').default : require('../../platform/tDVW').default;
+const queriedConfig = require('./config.json');
+
let config = {};
if (process.env.NODE_ENV === 'production') {
diff --git a/src/constants/config/viewer/config.json b/src/constants/config/viewer/config.json
new file mode 100644
index 000000000..fb97ac619
--- /dev/null
+++ b/src/constants/config/viewer/config.json
@@ -0,0 +1,110 @@
+{
+ "wallet": {
+ "privateKey": "f6e512a3c259e5f9af981d7f99d245aa5bc52fe448495e0b0dd56e8406be6f71"
+ },
+ "contracts": {
+ "parliament": "AElf.ContractNames.Parliament",
+ "referendum": "AElf.ContractNames.Referendum",
+ "association": "AElf.ContractNames.Association",
+ "crossChain": "AElf.ContractNames.CrossChain",
+ "token": "AElf.ContractNames.Token"
+ },
+ "contractNames": [
+ "AElf.ContractNames.Election",
+ "AElf.ContractNames.Profit",
+ "AElf.ContractNames.Vote",
+ "AElf.ContractNames.Treasury",
+ "AElf.ContractNames.Token",
+ "AElf.ContractNames.TokenHolder",
+ "AElf.ContractNames.TokenConverter",
+ "AElf.ContractNames.Consensus",
+ "AElf.ContractNames.Parliament",
+ "AElf.ContractNames.CrossChain",
+ "AElf.ContractNames.Association",
+ "AElf.ContractNames.Configuration",
+ "AElf.ContractNames.Referendum",
+ "AElf.ContractNames.Economic"
+ ],
+ "viewer": {
+ "addressUrl": "/address",
+ "txUrl": "/tx",
+ "blockUrl": "/block",
+ "chainId": "AELF",
+ "contractAddress": [
+ {
+ "contractName": "Genesis",
+ "description": "contract Genesis",
+ "contractAddress": "pykr77ft9UUKJZLVq15wCH8PinBSjVRQ12sD1Ayq92mKFsJ1i"
+ },
+ {
+ "description": "contract Token",
+ "contractAddress": "JRmBduh4nXWi1aXgdUsj5gJrzeZb2LxmrAbf7W99faZSvoAaE",
+ "contractName": "Token"
+ },
+ {
+ "description": "contract Dividend",
+ "contractAddress": "KNdM6U6PyPsgyena8rPHTbCoMrkrALhxAy1b8Qx2cgi4169xr",
+ "contractName": "Dividend"
+ },
+ {
+ "description": "contract Consensus",
+ "contractAddress": "pGa4e5hNGsgkfjEGm72TEvbF7aRDqKBd4LuXtab4ucMbXLcgJ",
+ "contractName": "Consensus.Dpos"
+ },
+ {
+ "description": "contract Token Converter",
+ "contractAddress": "SietKh9cArYub9ox6E4rU94LrzPad6TB72rCwe3X1jQ5m1C34",
+ "contractName": "Token Converter"
+ },
+ {
+ "description": "contract Election",
+ "contractAddress": "NrVf8B7XUduXn1oGHZeF1YANFXEXAhvCymz2WPyKZt4DE2zSg",
+ "contractName": "Election"
+ },
+ {
+ "description": "contract Profit",
+ "contractAddress": "2ZUgaDqWSh4aJ5s5Ker2tRczhJSNep4bVVfrRBRJTRQdMTbA5W",
+ "contractName": "Profit"
+ },
+ {
+ "description": "contract Parliament",
+ "contractAddress": "2JT8xzjR5zJ8xnBvdgBZdSjfbokFSbF5hDdpUCbXeWaJfPDmsK",
+ "contractName": "Parliament"
+ },
+ {
+ "description": "contract Association",
+ "contractAddress": "XyRN9VNabpBiVUFeX2t7ZUR2b3tWV7U31exufJ2AUepVb5t56",
+ "contractName": "Association"
+ },
+ {
+ "description": "contract Referendum",
+ "contractAddress": "NxSBGHE3zs85tpnX1Ns4awQUtFL8Dnr6Hux4C4E18WZsW4zzJ",
+ "contractName": "Referendum"
+ },
+ {
+ "description": "contract CrossChain",
+ "contractAddress": "2SQ9LeGZYSWmfJcYuQkDQxgd3HzwjamAaaL4Tge2eFSXw2cseq",
+ "contractName": "CrossChain"
+ }
+ ]
+ },
+ "constants": {
+ "proposalTypes": {
+ "PARLIAMENT": "Parliament",
+ "REFERENDUM": "Referendum",
+ "ASSOCIATION": "Association"
+ },
+ "proposalStatus": {
+ "ALL": "all",
+ "PENDING": "pending",
+ "APPROVED": "approved",
+ "RELEASED": "released",
+ "EXPIRED": "expired"
+ },
+ "proposalActions": {
+ "APPROVE": "Approve",
+ "REJECT": "Reject",
+ "ABSTAIN": "Abstain"
+ }
+ }
+}
diff --git a/src/constants/info.ts b/src/constants/info.ts
index 2a728caec..c8bd965bc 100644
--- a/src/constants/info.ts
+++ b/src/constants/info.ts
@@ -51,7 +51,6 @@ const NO_AUTHORIZATION_ERROR_TIP = "Sorry, you temporarily don't has the authori
const INPUT_SOMETHING_TIP = 'Sorry, you should input something';
const INTEGER_TIP = 'It can only be integer';
const UNLOCK_PLUGIN_TIP = 'Your plugin has beed locked, please unlock and refresh the page';
-const GET_TIP = 'It can only be integer';
const ALREADY_BEEN_CURRENT_CANDIDATE_TIP = 'You already been candidate';
const NOT_CURRENT_CANDIDATE_TIP =
'Sorry, the node is not current candidate \n Please refresh the page then choose another node to vote.';
@@ -69,7 +68,6 @@ const SHORTEST_LOCK_TIME = 90; // day
export {
LOG_STATUS,
LOADING_STATUS,
- GET_TIP,
TXSSTATUS,
txStatusInUpperCase,
IE_ADVICE,
diff --git a/src/constants/misc.tsx b/src/constants/misc.tsx
index 4a6be210a..e92ea08e5 100644
--- a/src/constants/misc.tsx
+++ b/src/constants/misc.tsx
@@ -1,14 +1,16 @@
import config, { DEFAUTRPCSERVER } from './config/config';
import Link from 'next/link';
-import React from 'react';
+import React, { ReactNode } from 'react';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import addressFormat from '../utils/addressFormat';
import Dividends from '../components/Dividends';
import { ArrowRightOutlined } from '@ant-design/icons';
-import { removeAElfPrefix } from '../utils/utils';
+import { removeAElfPrefix } from 'utils/utils';
dayjs.extend(relativeTime);
import BigNumber from 'bignumber.js';
+import { AlignType, RenderedCell } from 'rc-table/lib/interface';
+import { ColumnsType } from 'antd/lib/table';
const { SYMBOL, CHAIN_ID } = config;
// for address
const globalConfig = {
@@ -97,7 +99,7 @@ const BLOCKS_LIST_COLUMNS = [
key: 'tx_count ',
width: 60,
render: (text: string, row: any) =>
- !isNaN(+row.tx_count) && +row.tx_count !== 0 ? (
+ !Number.isNaN(+row.tx_count) && +row.tx_count !== 0 ? (
{' '}
<>{row.tx_count}>{' '}
@@ -202,7 +204,8 @@ const ALL_TXS_LIST_COLUMNS = [
} else if (row.quantity <= 9) {
amount = `0.0000000${row.quantity}`;
} else {
- amount = '' + row.quantity / Math.pow(10, row.decimals);
+ // ** is same as Math.pow
+ amount = '' + row.quantity / 10 ** row.decimals;
}
}
if (row.symbol) {
@@ -236,7 +239,14 @@ const ADDRESS_INFO_COLUMN = [
},
];
-const RESOURCE_DETAILS_COLUMN = [
+const RESOURCE_DETAILS_COLUMN: ColumnsType<{
+ title: string;
+ dataIndex: string;
+ key: string;
+ align: AlignType;
+ ellipsis: boolean;
+ render: (text: string) => ReactNode | RenderedCell;
+}> = [
{
title: 'Tx Id',
dataIndex: 'tx_id',
@@ -279,7 +289,7 @@ const RESOURCE_DETAILS_COLUMN = [
elf /= ELF_DECIMAL;
fee /= ELF_DECIMAL;
price = ((method === 'Buy' ? elf + fee : elf - fee) / resource).toFixed(ELF_PRECISION);
- price = isNaN(+price) ? '-' : price;
+ price = Number.isNaN(+price) ? '-' : price;
return price;
},
},
diff --git a/src/constants/platform/AELF.ts b/src/constants/platform/AELF.ts
deleted file mode 100644
index a1cf31ff6..000000000
--- a/src/constants/platform/AELF.ts
+++ /dev/null
@@ -1,195 +0,0 @@
-export default {
- SYMBOL: 'ELF',
- CHAIN_ID: 'AELF',
- CONTRACTS: {
- voteContractAddr: 'AElf.ContractNames.Vote',
- electionContractAddr: 'AElf.ContractNames.Election',
- profitContractAddr: 'AElf.ContractNames.Profit',
- multiToken: 'AElf.ContractNames.Token',
- dividends: 'AElf.ContractNames.Treasury',
- consensusDPoS: 'AElf.ContractNames.Consensus',
- tokenConverter: 'AElf.ContractNames.TokenConverter',
- feeReceiverContract: 'AElf.ContractNames.Token',
- parliamentContract: 'AElf.ContractNames.Parliament',
- associationContract: 'AElf.ContractNames.Association',
- referendumContract: 'AElf.ContractNames.Referendum',
- crossChainContract: 'AElf.ContractNames.CrossChain',
- },
- schemeIds: [
- {
- type: 'Citizen Welfare',
- schemeId: '32effe6aad9d6e529e884fc7cbca47bfb90c6236dab6e6c2e942e7483090d41b',
- },
- {
- type: 'Backup Subsidy',
- schemeId: '97c31794f133f3ca7e9c3b08e797398a245c220f7d00878c3ab1e3ef91af965c',
- },
- {
- type: 'Welcome Reward',
- schemeId: '976eafd3bbd8dddbaefb3513ace018a1adb5dc3a84fa82022787823c26887383',
- },
- {
- type: 'Miner Basic Reward',
- schemeId: '6da0ba4043fdf0dd966e2fa35402f58c1eb04f7cd718152e93f4559f230f1554',
- },
- {
- type: 'Flexible Reward',
- schemeId: 'f0d6ae7bd14592ef88c2448f8e80441f9dba1382614a27a3a186d165531ef510',
- },
- ],
- voteContractAddr: '2aoPatvMevjmhwsU1S9pkH2vnkNAuaiUaiU6JDroKNKe3fBQns',
- electionContractAddr: 'NrVf8B7XUduXn1oGHZeF1YANFXEXAhvCymz2WPyKZt4DE2zSg',
- profitContractAddr: '2ZUgaDqWSh4aJ5s5Ker2tRczhJSNep4bVVfrRBRJTRQdMTbA5W',
- multiToken: 'JRmBduh4nXWi1aXgdUsj5gJrzeZb2LxmrAbf7W99faZSvoAaE',
- dividends: 'KNdM6U6PyPsgyena8rPHTbCoMrkrALhxAy1b8Qx2cgi4169xr',
- consensusDPoS: 'pGa4e5hNGsgkfjEGm72TEvbF7aRDqKBd4LuXtab4ucMbXLcgJ',
- tokenConverter: 'SietKh9cArYub9ox6E4rU94LrzPad6TB72rCwe3X1jQ5m1C34',
- feeReceiverContract: 'JRmBduh4nXWi1aXgdUsj5gJrzeZb2LxmrAbf7W99faZSvoAaE',
- parliamentContract: '2JT8xzjR5zJ8xnBvdgBZdSjfbokFSbF5hDdpUCbXeWaJfPDmsK',
- associationContract: 'XyRN9VNabpBiVUFeX2t7ZUR2b3tWV7U31exufJ2AUepVb5t56',
- referendumContract: 'NxSBGHE3zs85tpnX1Ns4awQUtFL8Dnr6Hux4C4E18WZsW4zzJ',
- crossChainContract: '2SQ9LeGZYSWmfJcYuQkDQxgd3HzwjamAaaL4Tge2eFSXw2cseq',
- genesisContract: 'pykr77ft9UUKJZLVq15wCH8PinBSjVRQ12sD1Ayq92mKFsJ1i',
- resourceTokens: [
- {
- symbol: 'WRITE',
- decimals: 8,
- },
- {
- symbol: 'READ',
- decimals: 8,
- },
- {
- symbol: 'STORAGE',
- decimals: 8,
- },
- {
- symbol: 'TRAFFIC',
- decimals: 8,
- },
- {
- symbol: 'CPU',
- decimals: 8,
- },
- {
- symbol: 'RAM',
- decimals: 8,
- },
- {
- symbol: 'DISK',
- decimals: 8,
- },
- {
- symbol: 'NET',
- decimals: 8,
- },
- ],
- wallet: {
- privateKey: 'f6e512a3c259e5f9af981d7f99d245aa5bc52fe448495e0b0dd56e8406be6f71',
- },
- contracts: {
- parliament: 'AElf.ContractNames.Parliament',
- referendum: 'AElf.ContractNames.Referendum',
- association: 'AElf.ContractNames.Association',
- crossChain: 'AElf.ContractNames.CrossChain',
- token: 'AElf.ContractNames.Token',
- },
- contractNames: [
- 'AElf.ContractNames.Election',
- 'AElf.ContractNames.Profit',
- 'AElf.ContractNames.Vote',
- 'AElf.ContractNames.Treasury',
- 'AElf.ContractNames.Token',
- 'AElf.ContractNames.TokenHolder',
- 'AElf.ContractNames.TokenConverter',
- 'AElf.ContractNames.Consensus',
- 'AElf.ContractNames.Parliament',
- 'AElf.ContractNames.CrossChain',
- 'AElf.ContractNames.Association',
- 'AElf.ContractNames.Configuration',
- 'AElf.ContractNames.Referendum',
- 'AElf.ContractNames.Economic',
- ],
- viewer: {
- addressUrl: '/address',
- txUrl: '/tx',
- blockUrl: '/block',
- chainId: 'AELF',
- contractAddress: [
- {
- contractName: 'Genesis',
- description: 'contract Genesis',
- contractAddress: 'pykr77ft9UUKJZLVq15wCH8PinBSjVRQ12sD1Ayq92mKFsJ1i',
- },
- {
- description: 'contract Token',
- contractAddress: 'JRmBduh4nXWi1aXgdUsj5gJrzeZb2LxmrAbf7W99faZSvoAaE',
- contractName: 'Token',
- },
- {
- description: 'contract Dividend',
- contractAddress: 'KNdM6U6PyPsgyena8rPHTbCoMrkrALhxAy1b8Qx2cgi4169xr',
- contractName: 'Dividend',
- },
- {
- description: 'contract Consensus',
- contractAddress: 'pGa4e5hNGsgkfjEGm72TEvbF7aRDqKBd4LuXtab4ucMbXLcgJ',
- contractName: 'Consensus.Dpos',
- },
- {
- description: 'contract Token Converter',
- contractAddress: 'SietKh9cArYub9ox6E4rU94LrzPad6TB72rCwe3X1jQ5m1C34',
- contractName: 'Token Converter',
- },
- {
- description: 'contract Election',
- contractAddress: 'NrVf8B7XUduXn1oGHZeF1YANFXEXAhvCymz2WPyKZt4DE2zSg',
- contractName: 'Election',
- },
- {
- description: 'contract Profit',
- contractAddress: '2ZUgaDqWSh4aJ5s5Ker2tRczhJSNep4bVVfrRBRJTRQdMTbA5W',
- contractName: 'Profit',
- },
- {
- description: 'contract Parliament',
- contractAddress: '2JT8xzjR5zJ8xnBvdgBZdSjfbokFSbF5hDdpUCbXeWaJfPDmsK',
- contractName: 'Parliament',
- },
- {
- description: 'contract Association',
- contractAddress: 'XyRN9VNabpBiVUFeX2t7ZUR2b3tWV7U31exufJ2AUepVb5t56',
- contractName: 'Association',
- },
- {
- description: 'contract Referendum',
- contractAddress: 'NxSBGHE3zs85tpnX1Ns4awQUtFL8Dnr6Hux4C4E18WZsW4zzJ',
- contractName: 'Referendum',
- },
- {
- description: 'contract CrossChain',
- contractAddress: '2SQ9LeGZYSWmfJcYuQkDQxgd3HzwjamAaaL4Tge2eFSXw2cseq',
- contractName: 'CrossChain',
- },
- ],
- },
- constants: {
- proposalTypes: {
- PARLIAMENT: 'Parliament',
- REFERENDUM: 'Referendum',
- ASSOCIATION: 'Association',
- },
- proposalStatus: {
- ALL: 'all',
- PENDING: 'pending',
- APPROVED: 'approved',
- RELEASED: 'released',
- EXPIRED: 'expired',
- },
- proposalActions: {
- APPROVE: 'Approve',
- REJECT: 'Reject',
- ABSTAIN: 'Abstain',
- },
- },
-};
diff --git a/src/constants/platform/tDVW.ts b/src/constants/platform/tDVW.ts
deleted file mode 100644
index 5c73f328a..000000000
--- a/src/constants/platform/tDVW.ts
+++ /dev/null
@@ -1,180 +0,0 @@
-export default {
- SYMBOL: 'ELF',
- CHAIN_ID: 'tDVW',
- CONTRACTS: {
- voteContractAddr: 'AElf.ContractNames.Vote',
- electionContractAddr: 'AElf.ContractNames.Election',
- profitContractAddr: 'AElf.ContractNames.Profit',
- multiToken: 'AElf.ContractNames.Token',
- dividends: 'AElf.ContractNames.Treasury',
- consensusDPoS: 'AElf.ContractNames.Consensus',
- tokenConverter: 'AElf.ContractNames.TokenConverter',
- feeReceiverContract: 'AElf.ContractNames.Token',
- parliamentContract: 'AElf.ContractNames.Parliament',
- associationContract: 'AElf.ContractNames.Association',
- referendumContract: 'AElf.ContractNames.Referendum',
- crossChainContract: 'AElf.ContractNames.CrossChain',
- },
- schemeIds: [
- {
- type: 'Citizen Welfare',
- schemeId: '32effe6aad9d6e529e884fc7cbca47bfb90c6236dab6e6c2e942e7483090d41b',
- },
- {
- type: 'Backup Subsidy',
- schemeId: '97c31794f133f3ca7e9c3b08e797398a245c220f7d00878c3ab1e3ef91af965c',
- },
- {
- type: 'Welcome Reward',
- schemeId: '976eafd3bbd8dddbaefb3513ace018a1adb5dc3a84fa82022787823c26887383',
- },
- {
- type: 'Miner Basic Reward',
- schemeId: '6da0ba4043fdf0dd966e2fa35402f58c1eb04f7cd718152e93f4559f230f1554',
- },
- {
- type: 'Flexible Reward',
- schemeId: 'f0d6ae7bd14592ef88c2448f8e80441f9dba1382614a27a3a186d165531ef510',
- },
- ],
- voteContractAddr: null,
- electionContractAddr: null,
- profitContractAddr: 'tf238ARScVSfBRPKKgkwE36a35nMALLxu6oVCBMJ9CCNvs7F2',
- multiToken: 'ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx',
- dividends: null,
- consensusDPoS: '2KPUA5wG78nnNmK9JsRWbFiEFUEfei9WKniZuKaVziDKZRwchM',
- tokenConverter: null,
- feeReceiverContract: 'ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx',
- parliamentContract: 'vcv1qewcsFN2tVWqLuu7DJ5wVFA8YEx5FFgCQBb1jMCbAQHxV',
- associationContract: 'MbWXHaAY5sGpngiep6RS2euSzMZ2vHoXgmrfjEn3D1kCc1wbJ',
- referendumContract: '2cVQrFiXNaedBYmUrovmUV2jcF9Hf6AXbh12gWsD4P49NaX99y',
- crossChainContract: '2PC7Jhb5V6iZXxz8uQUWvWubYkAoCVhtRGSL7VhTWX85R8DBuN',
- genesisContract: '2UKQnHcQvhBT6X6ULtfnuh3b9PVRvVMEroHHkcK4YfcoH1Z1x2',
- resourceTokens: [
- {
- symbol: 'WRITE',
- decimals: 8,
- },
- {
- symbol: 'READ',
- decimals: 8,
- },
- {
- symbol: 'STORAGE',
- decimals: 8,
- },
- {
- symbol: 'TRAFFIC',
- decimals: 8,
- },
- {
- symbol: 'CPU',
- decimals: 8,
- },
- {
- symbol: 'RAM',
- decimals: 8,
- },
- {
- symbol: 'DISK',
- decimals: 8,
- },
- {
- symbol: 'NET',
- decimals: 8,
- },
- ],
- wallet: {
- privateKey: 'f6e512a3c259e5f9af981d7f99d245aa5bc52fe448495e0b0dd56e8406be6f71',
- },
- contracts: {
- parliament: 'AElf.ContractNames.Parliament',
- referendum: 'AElf.ContractNames.Referendum',
- association: 'AElf.ContractNames.Association',
- crossChain: 'AElf.ContractNames.CrossChain',
- token: 'AElf.ContractNames.Token',
- },
- contractNames: [
- 'AElf.ContractNames.Election',
- 'AElf.ContractNames.Profit',
- 'AElf.ContractNames.Vote',
- 'AElf.ContractNames.Treasury',
- 'AElf.ContractNames.Token',
- 'AElf.ContractNames.TokenHolder',
- 'AElf.ContractNames.TokenConverter',
- 'AElf.ContractNames.Consensus',
- 'AElf.ContractNames.Parliament',
- 'AElf.ContractNames.CrossChain',
- 'AElf.ContractNames.Association',
- 'AElf.ContractNames.Configuration',
- 'AElf.ContractNames.Referendum',
- 'AElf.ContractNames.Economic',
- ],
- viewer: {
- addressUrl: '/address',
- txUrl: '/tx',
- blockUrl: '/block',
- chainId: 'tDVW',
- contractAddress: [
- {
- contractName: 'Genesis',
- description: 'contract Genesis',
- contractAddress: '2UKQnHcQvhBT6X6ULtfnuh3b9PVRvVMEroHHkcK4YfcoH1Z1x2',
- },
- {
- description: 'contract Token',
- contractAddress: 'ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx',
- contractName: 'Token',
- },
- {
- description: 'contract Consensus',
- contractAddress: '2KPUA5wG78nnNmK9JsRWbFiEFUEfei9WKniZuKaVziDKZRwchM',
- contractName: 'Consensus.Dpos',
- },
- {
- description: 'contract Profit',
- contractAddress: 'tf238ARScVSfBRPKKgkwE36a35nMALLxu6oVCBMJ9CCNvs7F2',
- contractName: 'Profit',
- },
- {
- description: 'contract Parliament',
- contractAddress: 'vcv1qewcsFN2tVWqLuu7DJ5wVFA8YEx5FFgCQBb1jMCbAQHxV',
- contractName: 'Parliament',
- },
- {
- description: 'contract Association',
- contractAddress: 'MbWXHaAY5sGpngiep6RS2euSzMZ2vHoXgmrfjEn3D1kCc1wbJ',
- contractName: 'Association',
- },
- {
- description: 'contract Referendum',
- contractAddress: '2cVQrFiXNaedBYmUrovmUV2jcF9Hf6AXbh12gWsD4P49NaX99y',
- contractName: 'Referendum',
- },
- {
- description: 'contract CrossChain',
- contractAddress: '2PC7Jhb5V6iZXxz8uQUWvWubYkAoCVhtRGSL7VhTWX85R8DBuN',
- contractName: 'CrossChain',
- },
- ],
- },
- constants: {
- proposalTypes: {
- PARLIAMENT: 'Parliament',
- REFERENDUM: 'Referendum',
- ASSOCIATION: 'Association',
- },
- proposalStatus: {
- ALL: 'all',
- PENDING: 'pending',
- APPROVED: 'approved',
- RELEASED: 'released',
- EXPIRED: 'expired',
- },
- proposalActions: {
- APPROVE: 'Approve',
- REJECT: 'Reject',
- ABSTAIN: 'Abstain',
- },
- },
-};
diff --git a/src/constants/viewerApi.js b/src/constants/viewerApi.ts
similarity index 76%
rename from src/constants/viewerApi.js
rename to src/constants/viewerApi.ts
index 053d2d51a..d1e002161 100644
--- a/src/constants/viewerApi.js
+++ b/src/constants/viewerApi.ts
@@ -33,6 +33,15 @@ export const API_PATH = {
GET_FILES: '/api/viewer/getFile',
GET_HISTORY: '/api/viewer/history',
};
+export const VIEWER_ACCOUNT_LIST = '/viewer/accountList';
+export const VIEWER_CONTRACTS_LIST = '/viewer/list';
+export const VIEWER_BALANCES = '/viewer/balances';
+export const VIEWER_TRANSFER_LIST = '/viewer/transferList';
+export const VIEWER_HISTORY = '/viewer/history';
+export const VIEWER_GET_FILE = '/viewer/getFile';
+export const VIEWER_EVENT_LIST = '/viewer/eventList';
+export const VIEWER_TOKEN_TX_LIST = '/viewer/tokenTxList';
+export const TOKEN_PRICE = '/token/price';
export default {
...config,
API_PATH,
diff --git a/src/hooks/Providers/ProviderBasic/index.tsx b/src/hooks/Providers/ProviderBasic/index.tsx
index cdf05b764..5587fa396 100644
--- a/src/hooks/Providers/ProviderBasic/index.tsx
+++ b/src/hooks/Providers/ProviderBasic/index.tsx
@@ -1,7 +1,12 @@
import { ConfigProvider } from 'antd';
import { prefixCls } from 'constants/misc';
import type { ReactNode } from 'react';
-ConfigProvider.config({ prefixCls });
+ConfigProvider.config({
+ prefixCls,
+ theme: {
+ primaryColor: '#266cd3',
+ },
+});
export default function ProviderBasic({ children }: { children: ReactNode }) {
return (
diff --git a/src/hooks/callback/useLockCallback.ts b/src/hooks/callback/useLockCallback.ts
deleted file mode 100644
index 4b7c1c6d6..000000000
--- a/src/hooks/callback/useLockCallback.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { DependencyList, useCallback, useRef } from 'react';
-
-// useLockCallback prevent concurrent execution
-export default function useLockCallback any>(callback: T, deps: DependencyList) {
- const lock = useRef(false);
- return useCallback(async (...args: any) => {
- if (lock.current) return;
- lock.current = true;
- try {
- const req = await callback(...args);
- lock.current = false;
- return req;
- } catch (e) {
- lock.current = false;
- throw e;
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, deps);
-}
diff --git a/src/hooks/memo/hooks.ts b/src/hooks/memo/hooks.ts
deleted file mode 100644
index 79eef2938..000000000
--- a/src/hooks/memo/hooks.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { useMemo } from 'react';
-import { isMobileDevices } from 'utils/isMobile';
-
-export function useMobile() {
- return useMemo(() => isMobileDevices(), []);
-}
diff --git a/src/hooks/useMobile.js b/src/hooks/useMobile.js
deleted file mode 100644
index 56c354304..000000000
--- a/src/hooks/useMobile.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { useEffect, useState } from 'react';
-import { isPhoneCheck } from '../utils/deviceCheck';
-
-export default function useMobile() {
- const [isMobile, setIsMobile] = useState(false);
- // cannot use location as dependency
- useEffect(() => {
- setIsMobile(isPhoneCheck());
- }, [location]);
-
- return !!isMobile;
-}
diff --git a/src/hooks/useMobile.ts b/src/hooks/useMobile.ts
new file mode 100644
index 000000000..83105f378
--- /dev/null
+++ b/src/hooks/useMobile.ts
@@ -0,0 +1,19 @@
+/*
+ * @Author: AbigailDeng Abigail.deng@ienyan.com
+ * @Date: 2022-10-06 20:32:23
+ * @LastEditors: AbigailDeng Abigail.deng@ienyan.com
+ * @LastEditTime: 2022-10-28 15:27:48
+ * @FilePath: /aelf-block-explorer/src/hooks/useMobile.ts
+ * @Description: only used in csr because cannot use location as dependency
+ */
+import { useEffect, useState } from 'react';
+import { isPhoneCheck } from '../utils/deviceCheck';
+
+export default function useMobile() {
+ const [isMobile, setIsMobile] = useState(false);
+ useEffect(() => {
+ setIsMobile(!!isPhoneCheck());
+ }, [location]);
+
+ return !!isMobile;
+}
diff --git a/src/page-components/Accounts/columnConfig.tsx b/src/page-components/Accounts/columnConfig.tsx
new file mode 100644
index 000000000..43fb7da3d
--- /dev/null
+++ b/src/page-components/Accounts/columnConfig.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import AddressLink from 'components/AddressLink';
+import { numberFormatter } from 'utils/formater';
+import { ColumnsType } from 'antd/lib/table';
+interface IRecord {
+ balance: string;
+ count: boolean;
+ owner: string;
+ percentage: string;
+ symbol: string;
+}
+export default ({ isMobile, preTotal }) => {
+ const columns: ColumnsType = [
+ {
+ title: 'Rank',
+ dataIndex: 'id',
+ width: isMobile ? 86 : 186,
+ render(id, record, index) {
+ return preTotal + index + 1;
+ },
+ },
+ {
+ title: 'Address',
+ dataIndex: 'owner',
+ width: isMobile ? 216 : 320,
+ ellipsis: true,
+ className: 'color-blue',
+ render: (address) => ,
+ },
+ {
+ title: 'Balance',
+ dataIndex: 'balance',
+ width: isMobile ? 156 : 300,
+ render(balance, record) {
+ return `${numberFormatter(balance)} ${record.symbol}`;
+ },
+ },
+ {
+ title: 'Percentage',
+ dataIndex: 'percentage',
+ width: isMobile ? 126 : 182,
+ },
+ {
+ title: 'Transfers',
+ dataIndex: 'count',
+ align: 'right',
+ width: isMobile ? 76 : 100,
+ },
+ ];
+ return columns;
+};
diff --git a/src/page-components/Accounts/index.less b/src/page-components/Accounts/index.less
new file mode 100644
index 000000000..ba29bab6e
--- /dev/null
+++ b/src/page-components/Accounts/index.less
@@ -0,0 +1,20 @@
+@import 'assets/theme/color.less';
+@import 'styles/common.less';
+.accounts-page-container {
+ h2 {
+ font-size: 18px;
+ font-family: 'Roboto Bold', sans-serif;
+ font-weight: 400;
+ margin-bottom: 16px;
+ color: @cloud-burst;
+ }
+
+ .ant-table-tbody > tr > td {
+ padding-top: 24px;
+ padding-bottom: 24px;
+ vertical-align: top;
+ &.color-blue {
+ color: @mariner;
+ }
+ }
+}
diff --git a/src/page-components/Accounts/index.tsx b/src/page-components/Accounts/index.tsx
new file mode 100644
index 000000000..d09237f7f
--- /dev/null
+++ b/src/page-components/Accounts/index.tsx
@@ -0,0 +1,124 @@
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import clsx from 'clsx';
+import { Pagination, Table } from 'antd';
+import { useDebounce, useEffectOnce, useUpdateEffect } from 'react-use';
+import { isPhoneCheck, isPhoneCheckSSR } from 'utils/deviceCheck';
+import TableLayer from 'components/TableLayer/TableLayer';
+import { get } from 'utils/axios/index';
+import { defaultAElfInstance, getContract } from 'utils/utils';
+import { getContractAddress } from 'page-components/Proposal/common/utils';
+import getColumn from './columnConfig';
+import { VIEWER_ACCOUNT_LIST } from 'constants/viewerApi';
+require('./index.less');
+export default function Accounts({ totalelfssr, datasourcessr, actualtotalssr, headers }) {
+ const [isMobile, setIsMobile] = useState(!!isPhoneCheckSSR(headers));
+ const [dataLoading, setDataLoading] = useState(false);
+ const [pageIndex, setPageIndex] = useState(1);
+ const [pageSize, setPageSize] = useState(50);
+ const [dataSource, setDataSource] = useState(datasourcessr);
+ const [actualTotal, setActualTotal] = useState(actualtotalssr || 0);
+ const [totalELF, setTotalELF] = useState(totalelfssr || 0);
+
+ const total = useMemo(() => {
+ if (actualTotal > 1000) return 1000;
+ return actualTotal;
+ }, [actualTotal]);
+
+ const columns = useMemo(() => {
+ return getColumn({
+ isMobile,
+ preTotal: Number(pageSize) * (pageIndex - 1),
+ });
+ }, [isMobile, pageIndex, pageSize]);
+
+ const fetchAccountList = useCallback(async () => {
+ const result = await get(VIEWER_ACCOUNT_LIST, {
+ pageSize,
+ pageNum: pageIndex,
+ symbol: 'ELF',
+ });
+ if (result.code === 0) {
+ const { data } = result;
+ setActualTotal(data.total);
+ setDataSource(data.list);
+ setDataLoading(false);
+ } else {
+ // when error
+ setDataSource(undefined);
+ setDataLoading(false);
+ }
+ }, [pageSize, pageIndex]);
+
+ const handlePageChange = useCallback(
+ (page, size) => {
+ setDataSource(undefined);
+ setDataLoading(true);
+ setPageIndex(size === pageSize ? page : 1);
+ setPageSize(size);
+ },
+ [pageSize],
+ );
+ useEffect(() => {
+ setIsMobile(!!isPhoneCheck());
+ }, []);
+
+ // useEffectOnce(() => {
+ // const fetchData = async () => {
+ // const token = await getContract(defaultAElfInstance, getContractAddress('Token'));
+ // const result = await token.GetTokenInfo.call({
+ // symbol: 'ELF',
+ // });
+
+ // if (result) {
+ // setTotalELF(result.supply);
+ // }
+ // };
+ // fetchData();
+ // });
+
+ useUpdateEffect(() => {
+ fetchAccountList();
+ }, [pageIndex, pageSize]);
+
+ return (
+
+
Top Accounts
+
+
+
+
+ A total of {'>'} {Number(actualTotal).toLocaleString()} accounts found{' '}
+ {`(${Number(totalELF / 100000000).toLocaleString()} ELF)`}
+
+
(Showing the top 1,000 accounts only)
+
+
+
+
+
+
+
+
handlePageChange(1, size)}
+ />
+
+
+
+ );
+}
diff --git a/src/page-components/Address/AddressLink/index.jsx b/src/page-components/Address/AddressLink/index.jsx
deleted file mode 100644
index e0a1418e2..000000000
--- a/src/page-components/Address/AddressLink/index.jsx
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * @author atom-yang
- */
-import React, { useEffect, useState } from 'react';
-import PropTypes from 'prop-types';
-import Link from 'next/link';
-import config from 'constants/viewerApi';
-import { getContractNames } from 'utils/utils';
-const AddressLink = (props) => {
- const { address, suffix } = props;
- const [contracts, setContracts] = useState({});
- useEffect(() => {
- getContractNames()
- .then((res) => setContracts(res))
- .catch((err) => console.error(err));
- }, []);
- return (
-
-
- {`ELF_${address}_${config.viewer.chainId}`}
- {suffix}
-
-
- );
-};
-
-AddressLink.propTypes = {
- address: PropTypes.string.isRequired,
- // eslint-disable-next-line react/forbid-prop-types
- suffix: PropTypes.any,
-};
-
-AddressLink.defaultProps = {
- suffix: null,
-};
-
-export default AddressLink;
diff --git a/src/page-components/Address/Bread/index.jsx b/src/page-components/Address/Bread/index.jsx
deleted file mode 100644
index 11a054d62..000000000
--- a/src/page-components/Address/Bread/index.jsx
+++ /dev/null
@@ -1,58 +0,0 @@
-/**
- * @file bread
- * @author atom-yang
- */
-import React from 'react';
-import PropTypes from 'prop-types';
-import Link from 'next/link';
-import { Divider, Breadcrumb } from 'antd';
-import { If, Then, Else } from 'react-if';
-require('./index.less');
-
-const Bread = (props) => {
- const { title, subTitle, breads } = props;
- return (
- <>
-
-
-
{title}
- {subTitle ? #{subTitle} : null}
-
-
0}>
-
-
- {breads.map((b) => (
-
-
- {b.title}
-
- {b.title}
-
-
-
- ))}
-
-
-
-
-
- >
- );
-};
-
-Bread.propTypes = {
- title: PropTypes.node.isRequired,
- subTitle: PropTypes.node,
- breads: PropTypes.arrayOf(
- PropTypes.shape({
- title: PropTypes.string.isRequired,
- path: PropTypes.string,
- }),
- ),
-};
-Bread.defaultProps = {
- subTitle: '',
- breads: [],
-};
-
-export default React.memo(Bread);
diff --git a/src/page-components/Address/Bread/index.less b/src/page-components/Address/Bread/index.less
deleted file mode 100644
index b14fafe86..000000000
--- a/src/page-components/Address/Bread/index.less
+++ /dev/null
@@ -1,20 +0,0 @@
-.address-bread {
- display: flex;
- flex-flow: row nowrap;
- justify-content: space-between;
- align-items: center;
- background: #ffffff;
- .breadcrumb-title > h2 {
- display: inline-block;
- }
- .ant-breadcrumb {
- span {
- font-size: 16px;
- }
- }
- .sub-title {
- color: #666;
- font-size: 16px;
- font-weight: 200;
- }
-}
diff --git a/src/page-components/Address/EventItem/index.jsx b/src/page-components/Address/EventItem/index.jsx
deleted file mode 100644
index fe20b705c..000000000
--- a/src/page-components/Address/EventItem/index.jsx
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * @file event item
- * @author atom-yang
- */
-import React, { useState } from 'react';
-import { Button, message, Input } from 'antd';
-import PropTypes from 'prop-types';
-import { deserializeLog } from 'utils/utils';
-
-const { TextArea } = Input;
-
-const EventItem = (props) => {
- const { data, name, address } = props;
- const [result, setResult] = useState({ ...(data || {}) });
- const [hasDecoded, setHasDecoded] = useState(false);
- const [loading, setLoading] = useState(false);
- function decode() {
- setLoading(true);
- if (hasDecoded) {
- setResult({
- ...(data || {}),
- });
- setHasDecoded(false);
- setLoading(false);
- } else {
- deserializeLog(data, name, address)
- .then((res) => {
- setResult(res);
- setLoading(false);
- setHasDecoded(true);
- })
- .catch(() => {
- message.error('Decode failed');
- setLoading(false);
- });
- }
- }
- return (
-
-
-
-
- );
-};
-
-EventItem.propTypes = {
- // eslint-disable-next-line react/forbid-prop-types
- data: PropTypes.object.isRequired,
- name: PropTypes.string.isRequired,
- address: PropTypes.string.isRequired,
-};
-
-export default EventItem;
diff --git a/src/page-components/Address/EventItem/index.less b/src/page-components/Address/EventItem/index.less
deleted file mode 100644
index dc18443eb..000000000
--- a/src/page-components/Address/EventItem/index.less
+++ /dev/null
@@ -1,6 +0,0 @@
-.event-item {
- &-text-area {
- display: block;
- width: 100%;
- }
-}
diff --git a/src/page-components/Address/EventList/index.jsx b/src/page-components/Address/EventList/index.jsx
deleted file mode 100644
index 2678ed15a..000000000
--- a/src/page-components/Address/EventList/index.jsx
+++ /dev/null
@@ -1,139 +0,0 @@
-/**
- * @file event list
- * @author atom-yang
- */
-import React, { useEffect, useState } from 'react';
-import PropTypes from 'prop-types';
-import { Table, Pagination, message } from 'antd';
-import EventItem from '../EventItem';
-import { request } from 'utils/request';
-import config from 'constants/viewerApi';
-import { sendHeight } from 'utils/utils';
-import Total from 'components/Total';
-import TableLayer from 'components/TableLayer/TableLayer';
-
-const columns = [
- {
- title: 'TxId',
- dataIndex: 'txId',
- key: 'txId',
- width: 120,
- ellipsis: true,
- render(txId) {
- return (
-
- {txId}
-
- );
- },
- },
- {
- title: 'Method',
- dataIndex: 'name',
- key: 'name',
- width: 160,
- ellipsis: true,
- },
- {
- title: 'Logs',
- dataIndex: 'data',
- key: 'data',
- render(data, record) {
- return ;
- },
- },
-];
-
-const fetchingStatusMap = {
- FETCHING: 'fetching',
- ERROR: 'error',
- SUCCESS: 'success',
-};
-
-const EventList = (props) => {
- const { contractAddress } = props;
- const [list, setList] = useState([]);
- const [fetchingStatus, setFetchingStatus] = useState(fetchingStatusMap.FETCHING);
- const [pagination, setPagination] = useState({
- total: 0,
- pageSize: 10,
- pageNum: 1,
- showSizeChanger: false,
- });
-
- const getList = (pager) => {
- setFetchingStatus(fetchingStatusMap.FETCHING);
- request(
- config.API_PATH.GET_EVENT_LIST,
- {
- ...pager,
- address: contractAddress,
- },
- {
- method: 'GET',
- },
- )
- .then((result) => {
- setFetchingStatus(fetchingStatusMap.SUCCESS);
- const { list: resultList, total } = result;
- setList(resultList);
- setPagination({
- ...pager,
- total,
- });
- sendHeight(500);
- })
- .catch((e) => {
- sendHeight(500);
- setFetchingStatus(fetchingStatusMap.ERROR);
- console.error(e);
- message.error('Network error');
- });
- };
-
- useEffect(() => {
- getList(pagination);
- }, []);
-
- const onPageNumChange = (page, pageSize) => {
- const newPagination = {
- ...pagination,
- pageNum: page,
- pageSize,
- };
- getList(newPagination);
- };
-
- return (
- <>
-
-
-
-
- >
- );
-};
-
-EventList.propTypes = {
- contractAddress: PropTypes.string.isRequired,
-};
-
-export default React.memo(EventList);
diff --git a/src/page-components/Address/Header/index.jsx b/src/page-components/Address/Header/index.jsx
deleted file mode 100644
index 72674ec6e..000000000
--- a/src/page-components/Address/Header/index.jsx
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * @file header
- * @author atom-yang
- */
-import React from 'react';
-import PropTypes from 'prop-types';
-import { If, Then } from 'react-if';
-import { Tag, Divider, Tooltip } from 'antd';
-import { LinkIcon } from 'assets/icons/addressIcon';
-require('./index.less');
-import AddressLink from '../AddressLink';
-
-const Header = (props) => {
- const { author, isSystemContract, contractName } = props;
- return (
-
-
-
- <>
-
-
- {contractName}
-
-
-
- >
-
-
-
-
-
-
-
-
-
-
Contract Type:
-
{isSystemContract ? 'System' : 'User'}
-
-
-
- );
-};
-
-Header.propTypes = {
- author: PropTypes.string,
- isSystemContract: PropTypes.bool.isRequired,
- contractName: PropTypes.string,
-};
-Header.defaultProps = {
- author: false,
- contractName: '',
-};
-
-export default Header;
diff --git a/src/page-components/Address/Header/index.less b/src/page-components/Address/Header/index.less
deleted file mode 100644
index 1a13d9e3b..000000000
--- a/src/page-components/Address/Header/index.less
+++ /dev/null
@@ -1,17 +0,0 @@
-.contract-viewer-header {
- margin-bottom: 24px;
- &-desc {
- display: flex;
- flex-flow: row nowrap;
- &-item {
- color: #000000;
- display: inline-block;
- >div {
- display: inline-block;
- }
- }
- &-item:first-child {
- margin-right: 60px;
- }
- }
-}
diff --git a/src/page-components/Address/HolderList/index.jsx b/src/page-components/Address/HolderList/index.jsx
deleted file mode 100644
index d8a7d9766..000000000
--- a/src/page-components/Address/HolderList/index.jsx
+++ /dev/null
@@ -1,147 +0,0 @@
-/**
- * @file account list
- * @author atom-yang
- */
-import React, { useMemo, useState, useEffect } from 'react';
-import { message, Pagination, Table } from 'antd';
-import PropTypes from 'prop-types';
-import { request } from 'utils/request';
-import config from 'constants/viewerApi';
-import Total from 'components/Total';
-import { sendHeight } from 'utils/utils';
-import AddressLink from '../AddressLink';
-import TableLayer from 'components/TableLayer/TableLayer';
-
-function getListColumn(preTotal) {
- return [
- {
- title: 'Rank',
- dataIndex: 'id',
- key: 'id',
- width: 80,
- render(id, record, index) {
- return preTotal + index + 1;
- },
- },
- {
- title: 'Address',
- dataIndex: 'owner',
- key: 'owner',
- ellipsis: true,
- render: (address) => ,
- },
- {
- title: 'Balance',
- dataIndex: 'balance',
- key: 'balance',
- render(balance, record) {
- return `${Number(balance).toLocaleString()} ${record.symbol}`;
- },
- },
- {
- title: 'Percentage',
- dataIndex: 'percentage',
- key: 'percentage',
- },
- {
- title: 'Transfers',
- dataIndex: 'count',
- key: 'count',
- },
- ];
-}
-
-const fetchingStatusMap = {
- FETCHING: 'fetching',
- ERROR: 'error',
- SUCCESS: 'success',
-};
-
-const HolderList = (props) => {
- const { symbol } = props;
- const [list, setList] = useState([]);
- const [fetchingStatus, setFetchingStatus] = useState(fetchingStatusMap.FETCHING);
- const [pagination, setPagination] = useState({
- total: 0,
- pageSize: 10,
- pageNum: 1,
- showSizeChanger: false,
- });
- const columns = useMemo(() => getListColumn(pagination.pageSize * (pagination.pageNum - 1)), [pagination]);
-
- const getList = (pager) => {
- setFetchingStatus(fetchingStatusMap.FETCHING);
- request(
- config.API_PATH.GET_ACCOUNT_LIST,
- {
- ...pager,
- symbol,
- },
- {
- method: 'GET',
- },
- )
- .then((result) => {
- setFetchingStatus(fetchingStatusMap.SUCCESS);
- const { list: resultList, total } = result;
- setList(resultList);
- setPagination({
- ...pager,
- total,
- });
- sendHeight(500);
- })
- .catch((e) => {
- sendHeight(500);
- setFetchingStatus(fetchingStatusMap.ERROR);
- console.error(e);
- message.error('Network error');
- });
- };
-
- useEffect(() => {
- getList(pagination);
- }, []);
-
- const onPageNumChange = (page, pageSize) => {
- const newPagination = {
- ...pagination,
- pageNum: page,
- pageSize,
- };
- getList(newPagination);
- };
-
- return (
- <>
-
-
-
-
- >
- );
-};
-
-HolderList.propTypes = {
- symbol: PropTypes.string.isRequired,
-};
-
-export default React.memo(HolderList);
diff --git a/src/page-components/Address/OldTransactionList/index.jsx b/src/page-components/Address/OldTransactionList/index.jsx
deleted file mode 100644
index fefce17aa..000000000
--- a/src/page-components/Address/OldTransactionList/index.jsx
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * @file old transaction list from old api
- * @author atom-yang
- */
-import React, { useMemo } from 'react';
-import PropTypes from 'prop-types';
-import TransactionList from '../TransactionList';
-
-function formatParams(params) {
- return {
- limit: params.pageSize,
- page: params.pageNum - 1,
- address: params.address,
- order: 'DESC',
- };
-}
-
-function formatResponse(data) {
- const { total = 0, transactions = [] } = data;
- return {
- total,
- list: transactions.map((item) => ({
- txId: item.tx_id,
- blockHeight: item.block_height,
- method: item.method,
- addressFrom: item.address_from,
- addressTo: item.address_to,
- txFee: JSON.parse(item.tx_fee),
- time: item.time,
- })),
- };
-}
-
-const OldTransactionList = (props) => {
- const { owner, api } = props;
- const freezeParams = useMemo(
- () => ({
- address: owner,
- }),
- [owner],
- );
- return (
-
- );
-};
-
-OldTransactionList.propTypes = {
- owner: PropTypes.string.isRequired,
- api: PropTypes.string.isRequired,
-};
-
-export default OldTransactionList;
diff --git a/src/page-components/Address/Save/index.jsx b/src/page-components/Address/Save/index.jsx
deleted file mode 100644
index 0bf1afbb8..000000000
--- a/src/page-components/Address/Save/index.jsx
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * @file Save
- * @author atom-yang
- */
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import { saveAs } from 'file-saver';
-import { DownloadOutlined } from '@ant-design/icons';
-import { Button, message } from 'antd';
-import { getZip } from 'utils/utils';
-
-const SaveAsZip = (props) => {
- const [loading, setIsLoading] = useState(false);
- const { title, files, fileName, ...rest } = props;
-
- const download = async () => {
- setIsLoading(true);
- try {
- const blob = await getZip(files);
- saveAs(blob, `${fileName}.zip`);
- } catch (e) {
- message.error('Download failed');
- } finally {
- setIsLoading(false);
- }
- };
- return (
- } loading={loading} onClick={download} {...rest} />
- );
-};
-
-SaveAsZip.propTypes = {
- files: PropTypes.arrayOf(
- PropTypes.shape({
- name: PropTypes.string.isRequired,
- files: PropTypes.arrayOf(PropTypes.object),
- content: PropTypes.string,
- }),
- ).isRequired,
- fileName: PropTypes.string.isRequired,
- title: PropTypes.string,
-};
-
-SaveAsZip.defaultProps = {
- title: 'Download zip file',
-};
-
-export default SaveAsZip;
diff --git a/src/page-components/Address/TokenList/index.jsx b/src/page-components/Address/TokenList/index.jsx
deleted file mode 100644
index 60c4a36fd..000000000
--- a/src/page-components/Address/TokenList/index.jsx
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * @file item list
- * @author atom-yang
- */
-import React from 'react';
-import PropTypes from 'prop-types';
-import { Select } from 'antd';
-require('./index.less');
-
-const { Option } = Select;
-
-const TokenList = (props) => {
- const { balances } = props;
- function onFilter(input, option) {
- return option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0;
- }
- return (
-
- );
-};
-
-TokenList.propTypes = {
- // eslint-disable-next-line react/forbid-prop-types
- balances: PropTypes.array.isRequired,
-};
-
-export default React.memo(TokenList);
diff --git a/src/page-components/Address/TokenList/index.less b/src/page-components/Address/TokenList/index.less
deleted file mode 100644
index 50e720ef4..000000000
--- a/src/page-components/Address/TokenList/index.less
+++ /dev/null
@@ -1,3 +0,0 @@
-.token-list-select {
- min-width: 300px;
-}
diff --git a/src/page-components/Address/TransactionList/index.jsx b/src/page-components/Address/TransactionList/index.jsx
deleted file mode 100644
index 1dfdded88..000000000
--- a/src/page-components/Address/TransactionList/index.jsx
+++ /dev/null
@@ -1,224 +0,0 @@
-/**
- * @file transaction list
- * @author atom-yang
- */
-import React, { useMemo, useState, useEffect } from 'react';
-import axios from 'axios';
-import moment from 'moment';
-import PropTypes from 'prop-types';
-import Link from 'next/link';
-import { Table, Pagination, message } from 'antd';
-import config from 'constants/viewerApi';
-import { getContractNames, removeAElfPrefix, sendHeight } from 'utils/utils';
-import Total from 'components/Total';
-import Dividends from 'components/Dividends';
-import AddressLink from '../AddressLink';
-import TableLayer from 'components/TableLayer/TableLayer';
-
-function getTableColumns(contractNames, ownerAddress) {
- return [
- {
- title: 'Tx Id',
- dataIndex: 'txId',
- key: 'txId',
- ellipsis: true,
- width: 250,
- render(txId) {
- return (
-
- {txId}
-
- );
- },
- },
- {
- title: 'Height',
- dataIndex: 'blockHeight',
- key: 'blockHeight',
- render(height) {
- return (
-
- {height}
-
- );
- },
- },
- {
- title: 'Method',
- width: 200,
- dataIndex: 'method',
- key: 'method',
- ellipsis: true,
- },
- {
- title: 'From',
- dataIndex: 'addressFrom',
- key: 'addressFrom',
- ellipsis: true,
- render(from) {
- return from === ownerAddress ? `ELF_${from}_${config.viewer.chainId}` : ;
- },
- },
- {
- title: 'To',
- dataIndex: 'addressTo',
- key: 'addressTo',
- ellipsis: true,
- render(to) {
- let text = `ELF_${to}_${config.viewer.chainId}`;
- if (contractNames[to] && contractNames[to].contractName) {
- text = removeAElfPrefix(contractNames[to].contractName);
- }
- return to === ownerAddress ? (
- text
- ) : (
-
- {text}
-
- );
- },
- },
- {
- title: 'Tx Fee',
- dataIndex: 'txFee',
- key: 'txFee',
- render(fee) {
- return ;
- },
- },
- {
- title: 'Time',
- dataIndex: 'time',
- key: 'time',
- width: 160,
- render(time) {
- return moment(time).format('YYYY/MM/DD HH:mm:ss');
- },
- },
- ];
-}
-
-function getList(api, params, formatter) {
- return axios
- .get(api, {
- params,
- })
- .then((res) => {
- const { data } = res;
- const { total = 0, list = [] } = formatter(data);
- return {
- total,
- list,
- };
- })
- .catch((e) => {
- console.error(e);
- return {
- total: 0,
- list: [],
- };
- });
-}
-
-const TransactionList = (props) => {
- const { api, requestParamsFormatter, responseFormatter, freezeParams, owner, getColumns, ...rest } = props;
- const [contractNames, setContractNames] = useState({});
- const columns = useMemo(() => getColumns(contractNames, owner), [contractNames, owner]);
-
- const [result, setResult] = useState({
- list: [],
- total: 0,
- });
- const [params, setParams] = useState({
- pageSize: 10,
- pageNum: 1,
- loading: false,
- ...freezeParams,
- });
-
- function fetch(apiParams) {
- setParams({
- ...params,
- loading: true,
- });
- getList(api, requestParamsFormatter(apiParams), responseFormatter)
- .then((res) => {
- const { list, total } = res;
- setResult({
- list,
- total,
- });
- setParams({
- ...params,
- ...apiParams,
- loading: false,
- });
- sendHeight(500);
- })
- .catch((e) => {
- sendHeight(500);
- console.error(e);
- message.error('Network error');
- });
- }
-
- useEffect(() => {
- fetch({
- ...params,
- ...freezeParams,
- });
- getContractNames().then((res) => setContractNames(res));
- }, [freezeParams, api, owner]);
-
- async function onPageChange(pageNum, pageSize) {
- await fetch({
- ...params,
- pageNum,
- pageSize,
- });
- }
- return (
-
- );
-};
-
-TransactionList.propTypes = {
- api: PropTypes.string.isRequired,
- responseFormatter: PropTypes.func,
- requestParamsFormatter: PropTypes.func,
- owner: PropTypes.string,
- // eslint-disable-next-line react/forbid-prop-types
- freezeParams: PropTypes.object.isRequired,
- getColumns: PropTypes.func,
-};
-
-TransactionList.defaultProps = {
- requestParamsFormatter: (params) => params,
- responseFormatter: (response) => response.data,
- owner: '',
- getColumns: getTableColumns,
-};
-
-export default TransactionList;
diff --git a/src/page-components/Address/TransferList/index.jsx b/src/page-components/Address/TransferList/index.jsx
deleted file mode 100644
index c22bad14d..000000000
--- a/src/page-components/Address/TransferList/index.jsx
+++ /dev/null
@@ -1,126 +0,0 @@
-/**
- * @file old transaction list from old api
- * @author atom-yang
- */
-import React, { useMemo } from 'react';
-import PropTypes from 'prop-types';
-import moment from 'moment';
-import TransactionList from '../TransactionList';
-import config from 'constants/viewerApi';
-import Dividends from 'components/Dividends';
-import AddressLink from '../AddressLink';
-
-function getColumns(contractNames, ownerAddress) {
- return [
- {
- title: 'Tx Id',
- dataIndex: 'txId',
- key: 'txId',
- ellipsis: true,
- width: 150,
- render(txId) {
- return (
-
- {txId}
-
- );
- },
- },
- {
- title: 'Height',
- dataIndex: 'blockHeight',
- key: 'blockHeight',
- width: 80,
- ellipsis: true,
- render(height) {
- return (
-
- {height}
-
- );
- },
- },
- {
- title: 'Method',
- width: 120,
- dataIndex: 'method',
- key: 'method',
- ellipsis: true,
- },
- {
- title: 'Event',
- width: 130,
- dataIndex: 'action',
- key: 'action',
- ellipsis: true,
- },
- {
- title: 'Symbol',
- width: 100,
- dataIndex: 'symbol',
- key: 'symbol',
- ellipsis: true,
- },
- {
- title: 'From',
- dataIndex: 'from',
- key: 'to',
- ellipsis: true,
- render(from) {
- return from === ownerAddress ? `ELF_${from}_${config.viewer.chainId}` : ;
- },
- },
- {
- title: 'To',
- dataIndex: 'to',
- key: 'to',
- ellipsis: true,
- render(to) {
- return to === ownerAddress ? `ELF_${to}_${config.viewer.chainId}` : ;
- },
- },
- {
- title: 'Amount',
- dataIndex: 'amount',
- key: 'amount',
- render(amount, record) {
- return `${Number(amount).toFixed(4)} ${record.symbol}`;
- },
- },
- {
- title: 'Tx Fee',
- dataIndex: 'txFee',
- key: 'txFee',
- render(fee) {
- return ;
- },
- },
- {
- title: 'Time',
- dataIndex: 'time',
- key: 'time',
- width: 160,
- render(time) {
- return moment(time).format('YYYY/MM/DD HH:mm:ss');
- },
- },
- ];
-}
-
-const TransferList = (props) => {
- const { owner, api } = props;
- const freezeParams = useMemo(
- () => ({
- address: owner,
- }),
- [owner],
- );
- return ;
-};
-
-TransferList.propTypes = {
- owner: PropTypes.string.isRequired,
- api: PropTypes.string.isRequired,
-};
-
-export default React.memo(TransferList);
diff --git a/src/page-components/AddressDetail/components/CommonTabPane.tsx b/src/page-components/AddressDetail/components/CommonTabPane.tsx
new file mode 100644
index 000000000..2ab0f0cd7
--- /dev/null
+++ b/src/page-components/AddressDetail/components/CommonTabPane.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import Tokens from './Tokens/Tokens';
+import Transactions from './Transactions/Transactions';
+import Transfers from './Transfers/Transfers';
+
+export default function CommonTabPane({ balances, prices, tokensLoading, address, headers }) {
+ return [
+ {
+ key: 'tokens',
+ tab: 'Tokens',
+ children: ,
+ },
+ {
+ key: 'transactions',
+ tab: 'Transactions',
+ children: ,
+ },
+ {
+ key: 'transfers',
+ tab: 'Transfers',
+ children: ,
+ },
+ ];
+}
diff --git a/src/page-components/AddressDetail/components/Contract/Contract.styles.less b/src/page-components/AddressDetail/components/Contract/Contract.styles.less
new file mode 100644
index 000000000..a65dd37e8
--- /dev/null
+++ b/src/page-components/AddressDetail/components/Contract/Contract.styles.less
@@ -0,0 +1,51 @@
+.contract-pane {
+ padding: 16px 24px 24px;
+ .contract-info {
+ display: flex;
+ gap: 16px;
+ flex-direction: column;
+ > p {
+ display: flex;
+ gap: 16px;
+ .label {
+ display: inline-block;
+ width: 300px;
+ font-family: 'Roboto Medium', sans-serif;
+ color: @mine-shaft;
+ button {
+ width: 24px;
+ height: 24px;
+ margin-left: 8px;
+ background: transparent;
+ color: @scorpion;
+ border: 1px solid @alto;
+ border-radius: 4px;
+ }
+ }
+ }
+ }
+ .contract-code {
+ margin-top: 16px;
+ .copy-btn {
+ width: 24px;
+ height: 24px;
+ border: 1px solid @alto;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ }
+}
+.contract-pane.mobile {
+ .contract-info {
+ gap: 24px;
+ > p {
+ flex-direction: column;
+ gap: 6px;
+ }
+ }
+ .contract-code {
+ overflow: scroll;
+ }
+}
diff --git a/src/page-components/AddressDetail/components/Contract/Contract.tsx b/src/page-components/AddressDetail/components/Contract/Contract.tsx
new file mode 100644
index 000000000..a72de67a9
--- /dev/null
+++ b/src/page-components/AddressDetail/components/Contract/Contract.tsx
@@ -0,0 +1,62 @@
+import React, { useEffect, useState } from 'react';
+import clsx from 'clsx';
+import Reader from '../Reader/Reader';
+import SaveAsFile from 'components/Save';
+import { isPhoneCheck, isPhoneCheckSSR } from 'utils/deviceCheck';
+require('./Contract.styles.less');
+
+interface IContractInfo {
+ contractName: string;
+ files: string;
+ address: string;
+ version: string;
+}
+interface IHistory {
+ codeHash: string;
+ version: string;
+}
+interface IProps {
+ contractInfo?: IContractInfo;
+ codeHash?: string;
+ history?: IHistory[];
+ isShow: boolean;
+ headers: any;
+}
+export default function Contract({
+ contractInfo = { contractName: '-', files: '', address: '-', version: '-' },
+ codeHash,
+ history,
+ isShow,
+ headers,
+}: IProps) {
+ const [isMobile, setIsMobile] = useState(!!isPhoneCheckSSR(headers));
+
+ useEffect(() => {
+ setIsMobile(!!isPhoneCheck());
+ }, []);
+ return (
+
+
+
+ Contract Name
+ {contractInfo.contractName === '-1' ? '-' : contractInfo.contractName}
+
+
+ Compiler Version
+
+ {codeHash ? history?.find((i) => i.codeHash === codeHash)?.version : contractInfo.version}
+
+
+
+
+ Contract Info
+
+
+
+
+
+
+ );
+}
diff --git a/src/page-components/AddressDetail/components/ContractTabPane.tsx b/src/page-components/AddressDetail/components/ContractTabPane.tsx
new file mode 100644
index 000000000..92077f539
--- /dev/null
+++ b/src/page-components/AddressDetail/components/ContractTabPane.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import Contract from './Contract/Contract';
+import Events from './Events/Events';
+import History from './History/History';
+
+export default function ContractTabPane({ contractInfo, contractHistory, address, codeHash, activeKey, headers }) {
+ return [
+ {
+ key: 'contract',
+ tab: 'Contract',
+ children: (
+
+ ),
+ },
+ {
+ key: 'events',
+ tab: 'Events',
+ children: ,
+ },
+ {
+ key: 'history',
+ tab: 'History',
+ children: ,
+ },
+ ];
+}
diff --git a/src/page-components/AddressDetail/components/Events/Events.styles.less b/src/page-components/AddressDetail/components/Events/Events.styles.less
new file mode 100644
index 000000000..930abd784
--- /dev/null
+++ b/src/page-components/AddressDetail/components/Events/Events.styles.less
@@ -0,0 +1,52 @@
+.events-pane {
+ padding: 0 0 40px;
+ background: @bgBlankGrey;
+ .table-layer {
+ padding-top: 0;
+ .ant-table-tbody > tr > td {
+ padding-top: 24px;
+ padding-bottom: 24px;
+ vertical-align: top;
+ &.color-blue {
+ color: @mariner;
+ }
+ }
+ }
+ .list {
+ background: white;
+ padding: 8px 16px 32px;
+ .row {
+ display: flex;
+ flex-direction: column;
+ padding: 32px 0;
+ border-bottom: 1px solid @border-color2;
+ gap: 24px;
+ > p {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ .label {
+ font-family: 'Roboto Medium', sans-serif;
+ }
+ a {
+ width: 100%;
+ display: block;
+ flex-wrap: nowrap;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow-wrap: nowrap;
+ overflow: hidden;
+ }
+ textarea {
+ height: 206px;
+ }
+ }
+ &:first-child {
+ padding-top: 0;
+ }
+ &:last-child {
+ border: none;
+ }
+ }
+ }
+}
diff --git a/src/page-components/AddressDetail/components/Events/Events.tsx b/src/page-components/AddressDetail/components/Events/Events.tsx
new file mode 100644
index 000000000..e0dabf47b
--- /dev/null
+++ b/src/page-components/AddressDetail/components/Events/Events.tsx
@@ -0,0 +1,138 @@
+import { Pagination, Spin, Table } from 'antd';
+import React, { useCallback, useMemo, useState } from 'react';
+import Link from 'next/link';
+import { useDebounce } from 'react-use';
+import clsx from 'clsx';
+import { VIEWER_EVENT_LIST } from 'constants/viewerApi';
+import EventItem from 'components/EventItem';
+import TableLayer from 'components/TableLayer/TableLayer';
+import useMobile from 'hooks/useMobile';
+import { get } from 'utils/axios';
+
+require('./Events.styles.less');
+
+interface IData {
+ Indexed: string[];
+ NonIndexed: string;
+}
+interface IDataSource {
+ address: string;
+ data: IData;
+ id: number;
+ name: string;
+ txId: string;
+}
+export default function Events(props) {
+ const { address } = props;
+ const isMobile = useMobile();
+ const [total, setTotal] = useState(0);
+ const [dataLoading, setDataLoading] = useState(true);
+ const [pageIndex, setPageIndex] = useState(1);
+ const [pageSize, setPageSize] = useState(10);
+ const [dataSource, setDataSource] = useState([]);
+ const columns = useMemo(
+ () => [
+ {
+ title: 'Txn Hash',
+ width: 192,
+ ellipsis: true,
+ dataIndex: 'txId',
+ className: 'color-blue',
+ render(txId) {
+ return {txId};
+ },
+ },
+ { title: 'Method', width: 196, ellipsis: true, dataIndex: 'name' },
+ {
+ title: 'Logs',
+ dataIndex: 'data',
+ render(data, record) {
+ return ;
+ },
+ },
+ ],
+ [],
+ );
+ const fetchEvents = useCallback(async () => {
+ const result = await get(VIEWER_EVENT_LIST, {
+ pageSize,
+ pageNum: pageIndex,
+ address,
+ });
+ setDataLoading(false);
+ if (result.code === 0) {
+ setDataSource(result.data.list);
+ setTotal(result.data.total);
+ }
+ }, [address, pageIndex, pageSize]);
+
+ const handlePageChange = useCallback(
+ (page, size) => {
+ setDataSource([]);
+ setDataLoading(true);
+ setPageIndex(size === pageSize ? page : 1);
+ setPageSize(size);
+ },
+ [pageSize],
+ );
+
+ useDebounce(
+ () => {
+ fetchEvents();
+ },
+ 1000,
+ [pageIndex, pageSize],
+ );
+
+ return (
+
+ {isMobile ? (
+
+ {dataLoading ? (
+
+ ) : (
+ dataSource?.map((item) => (
+
+
+ Txn Hash
+
+ {item.txId}
+
+
+
+ Method
+ {item.name}
+
+
+ Logs
+
+
+
+ ))
+ )}
+
+ ) : (
+
+
+
+ )}
+
+
handlePageChange(1, size)}
+ />
+
+
+ );
+}
diff --git a/src/page-components/Address/FileTree/index.jsx b/src/page-components/AddressDetail/components/FileTree/index.tsx
similarity index 71%
rename from src/page-components/Address/FileTree/index.jsx
rename to src/page-components/AddressDetail/components/FileTree/index.tsx
index eafc57151..81e7d72ba 100644
--- a/src/page-components/Address/FileTree/index.jsx
+++ b/src/page-components/AddressDetail/components/FileTree/index.tsx
@@ -1,14 +1,15 @@
-/**
- * @file file tree
- * @author atom-yang
- */
-import React, { useMemo } from 'react';
+import React, { ChangeEvent, useMemo } from 'react';
import PropTypes from 'prop-types';
import Tree from 'rc-tree';
import { MinusSquareOutlined, PlusSquareOutlined, FileOutlined } from '@ant-design/icons';
+import { IFile } from 'page-components/AddressDetail/types';
require('rc-tree/assets/index.css');
-function addKeyForTree(files = [], parentKey = '', splitChar = '#') {
+interface IProps {
+ files: IFile[];
+ onChange: (e: ChangeEvent) => void;
+}
+function addKeyForTree(files: IFile[] = [], parentKey = '', splitChar = '#') {
return files.map((file) => {
const { name } = file;
const newKey = `${parentKey}${name}`;
@@ -26,7 +27,7 @@ function addKeyForTree(files = [], parentKey = '', splitChar = '#') {
});
}
-function getFirstFile(files = []) {
+function getFirstFile(files: IFile[] = []) {
if (files.length === 0) {
return '';
}
@@ -44,7 +45,19 @@ function renderTreeNode(item) {
};
}
-const FileTree = (props) => {
+const renderSwitcherIcon = (props) => {
+ const { expanded, isLeaf } = props;
+ if (isLeaf) {
+ return ;
+ }
+ return expanded ? (
+
+ ) : (
+
+ );
+};
+
+const FileTree = (props: IProps) => {
const { files = [], onChange } = props;
const { filesWithKey, firstFileKey, firstDirectory } = useMemo(() => {
const filesHandled = addKeyForTree(files);
@@ -52,7 +65,7 @@ const FileTree = (props) => {
return {
filesWithKey: filesHandled,
firstFileKey: [firstKey],
- firstDirectory: [filesHandled[0].key],
+ firstDirectory: [filesHandled[0]?.key],
};
}, [files]);
const onSelect = (selectedKey) => {
@@ -62,23 +75,10 @@ const FileTree = (props) => {
onChange(selectedKey[0].split('#'));
};
const treeData = useMemo(() => filesWithKey.map((v) => renderTreeNode(v)), [filesWithKey]);
- const renderSwitcherIcon = (props) => {
- console.log(props);
- const { expanded, isLeaf } = props;
- if (isLeaf) {
- return ;
- }
- return expanded ? (
-
- ) : (
-
- );
- };
return (
.ant-steps-icon .ant-steps-icon-dot {
+ border: 3px solid @headerbgColor4;
+ }
+ .ant-steps-item-wait .ant-steps-item-icon > .ant-steps-icon .ant-steps-icon-dot {
+ border: 3px solid @border-color2;
+ }
+ .ant-steps-dot .ant-steps-item-tail::after,
+ .ant-steps-dot.ant-steps-small .ant-steps-item-tail::after {
+ margin-left: 15px;
+ }
+ .ant-steps-item-title {
+ display: flex;
+ flex-direction: column;
+ line-height: 22px !important;
+ font-weight: 400;
+ font-family: 'Roboto Medium', sans-serif;
+ margin-bottom: 16px;
+ color: @mine-shaft !important;
+ font-size: 14px;
+ .ant-steps-item-subtitle {
+ font-family: 'Roboto', sans-serif;
+ margin: 0;
+ line-height: 20px;
+ font-size: 12px;
+ color: @gray;
+ }
+ }
+ .ant-steps-item-description {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ padding-bottom: 25px !important;
+ .description-item {
+ color: @textContext1;
+ span {
+ display: inline-flex;
+ width: 140px;
+ margin-right: 16px;
+ }
+ }
+ }
+}
+.mobile {
+ .history-pane {
+ .ant-steps-item-description {
+ gap: 24px;
+ .description-item {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ }
+ }
+ }
+}
diff --git a/src/page-components/AddressDetail/components/History/History.tsx b/src/page-components/AddressDetail/components/History/History.tsx
new file mode 100644
index 000000000..069f9fb98
--- /dev/null
+++ b/src/page-components/AddressDetail/components/History/History.tsx
@@ -0,0 +1,63 @@
+import { Skeleton, Steps } from 'antd';
+import moment from 'moment';
+import React from 'react';
+import Link from 'next/link';
+import config from 'constants/config/config';
+
+require('./History.styles.less');
+
+const EventMap = {
+ CodeUpdated: 'Code Updated',
+ AuthorChanged: 'Author Changed',
+ ContractDeployed: 'Contract Deployed',
+};
+
+export default function History({ history }) {
+ const CHAIN_ID = config.CHAIN_ID;
+ const StepDescription = (props) => {
+ const { address, author, codeHash, txId, version, blockHeight, isLast } = props;
+ return (
+ <>
+
+ Author:
+ {`ELF_${author}_${CHAIN_ID}`}
+
+
+ Code Hash:
+ {codeHash}
+
+
+ Version:
+ {version}
+
+
+ Transaction Hash:
+ {txId}
+
+
+ Block:
+ {blockHeight}
+
+ >
+ );
+ };
+
+ return (
+
+ {history ? (
+
+ {history.map((v, index) => (
+
+ ))}
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/src/page-components/AddressDetail/components/Overview.tsx b/src/page-components/AddressDetail/components/Overview.tsx
new file mode 100644
index 000000000..fb4f80949
--- /dev/null
+++ b/src/page-components/AddressDetail/components/Overview.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import { numberFormatter } from 'utils/formater';
+
+export default function Overview({ elfBalance, prices }) {
+ return (
+
+ Overview
+
+
+ Balance
+ {elfBalance ? `${numberFormatter(elfBalance)} ELF` : '-'}
+
+
+ Value in USD
+
+ {elfBalance && prices.ELF
+ ? `$${numberFormatter(prices.ELF * elfBalance)}(@ $${numberFormatter(prices.ELF)}/ELF)`
+ : '-'}
+
+
+
+
+ );
+}
diff --git a/src/page-components/AddressDetail/components/QrCode/QrCode.styles.less b/src/page-components/AddressDetail/components/QrCode/QrCode.styles.less
new file mode 100644
index 000000000..57de22c22
--- /dev/null
+++ b/src/page-components/AddressDetail/components/QrCode/QrCode.styles.less
@@ -0,0 +1,10 @@
+.qr-code {
+ width: 268px;
+ font-size: 12px;
+ padding: 28px 24px 24px;
+ text-align: center;
+ p {
+ margin-top: 16px;
+ color: @scorpion;
+ }
+}
diff --git a/src/page-components/AddressDetail/components/QrCode/QrCode.tsx b/src/page-components/AddressDetail/components/QrCode/QrCode.tsx
new file mode 100644
index 000000000..215358670
--- /dev/null
+++ b/src/page-components/AddressDetail/components/QrCode/QrCode.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import QRCode from 'qrcode.react';
+
+require('./QrCode.styles.less');
+
+export default function QrCode({ value }) {
+ return (
+
+ );
+}
diff --git a/src/page-components/AddressDetail/components/Reader/Reader.tsx b/src/page-components/AddressDetail/components/Reader/Reader.tsx
new file mode 100644
index 000000000..b80169512
--- /dev/null
+++ b/src/page-components/AddressDetail/components/Reader/Reader.tsx
@@ -0,0 +1,125 @@
+import React, { useEffect, useState } from 'react';
+import { If, Then, Switch, Case } from 'react-if';
+import { Skeleton, Alert } from 'antd';
+import { sendHeight } from 'utils/utils';
+import FileTree from '../FileTree';
+import Viewer from '../Viewer/Viewer';
+import { isPhoneCheck, isPhoneCheckSSR } from 'utils/deviceCheck';
+import { IFile, IViewerConfig } from 'page-components/AddressDetail/types';
+require('./index.less');
+
+function getDefaultFile(files: IFile[] = [], names: string[] = [], index = 0, path = '') {
+ const filtered = files.filter((v) => v.name === names[index]);
+ if (filtered.length === 0) {
+ return {};
+ }
+ const newPath = `${path}${filtered[0].name}/`;
+ if (index === names.length - 1) {
+ if (Array.isArray(filtered[0].files) && filtered[0].files.length > 0) {
+ return {
+ ...filtered[0].files[0],
+ path: `${newPath}${filtered[0].files[0].name}`,
+ };
+ }
+ return {
+ ...filtered[0],
+ path: `${path}${filtered[0].name}`,
+ };
+ }
+ if (Array.isArray(filtered[0].files)) {
+ return getDefaultFile(filtered[0].files, names, index + 1, newPath);
+ }
+ return {
+ ...filtered[0],
+ path: newPath,
+ };
+}
+
+function handleFiles(data) {
+ let defaultFile;
+ let result;
+ try {
+ result = JSON.parse(data.files || '[]');
+ } catch (e) {
+ result = data.files;
+ } finally {
+ defaultFile = getDefaultFile(result, [result[0]?.name]);
+ }
+ return {
+ result,
+ defaultFile,
+ };
+}
+
+const sketchParagraph = {
+ rows: 10,
+ width: '100%',
+};
+
+const fetchingStatusMap = {
+ FETCHING: 'fetching',
+ ERROR: 'error',
+ SUCCESS: 'success',
+};
+
+const Reader = ({ contractInfo, isShow, headers }) => {
+ const [isMobile, setIsMobile] = useState(!!isPhoneCheckSSR(headers));
+
+ useEffect(() => {
+ setIsMobile(!!isPhoneCheck());
+ }, []);
+ const contractToFiles = handleFiles(contractInfo);
+ const [files, setFiles] = useState(contractToFiles.result);
+ const [fetchingStatus, setFetchingStatus] = useState(fetchingStatusMap.SUCCESS);
+ const [viewerConfig, setViewerConfig] = useState(contractToFiles.defaultFile);
+ useEffect(() => {
+ if (contractInfo.files) {
+ const { result, defaultFile } = handleFiles(contractInfo);
+ setFiles(result);
+ setViewerConfig(defaultFile);
+ setFetchingStatus(fetchingStatusMap.SUCCESS);
+ } else {
+ setFetchingStatus(fetchingStatusMap.FETCHING);
+ }
+ sendHeight(500);
+ }, [contractInfo]);
+
+ const onFileChange = (names) => {
+ const selectedFile = getDefaultFile(files, names);
+ if (Object.keys(selectedFile).length > 0) {
+ setViewerConfig({
+ ...selectedFile,
+ });
+ }
+ };
+
+ return (
+
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+
+ );
+};
+
+export default Reader;
diff --git a/src/pages/contract/[address]/Reader/index.less b/src/page-components/AddressDetail/components/Reader/index.less
similarity index 86%
rename from src/pages/contract/[address]/Reader/index.less
rename to src/page-components/AddressDetail/components/Reader/index.less
index c4f5d780c..316b9c9fc 100644
--- a/src/pages/contract/[address]/Reader/index.less
+++ b/src/page-components/AddressDetail/components/Reader/index.less
@@ -1,5 +1,5 @@
.reader {
- background-color: #ffffff;
+ background-color: #fff;
min-width: 1160px;
.contract-reader {
display: flex;
@@ -15,12 +15,12 @@
}
&:hover {
&::-webkit-scrollbar-track {
- box-shadow: inset 0 0 6px hsla(0,0%,39.2%,.4);
+ box-shadow: inset 0 0 6px hsl(0deg 0% 39.2% / 40%);
border-radius: 10px;
}
&::-webkit-scrollbar-thumb {
border-radius: 10px;
- box-shadow: inset 0 0 12px rgba(0,0,0,0.5);
+ box-shadow: inset 0 0 12px rgb(0 0 0 / 50%);
}
}
}
diff --git a/src/page-components/AddressDetail/components/Tokens/Tokens.styles.less b/src/page-components/AddressDetail/components/Tokens/Tokens.styles.less
new file mode 100644
index 000000000..455d4233e
--- /dev/null
+++ b/src/page-components/AddressDetail/components/Tokens/Tokens.styles.less
@@ -0,0 +1,11 @@
+.tokens-container.tokens-pane {
+ padding-top: 17px;
+ .ant-table-wrapper {
+ min-width: 404px;
+ }
+ .before-table {
+ padding-left: 32px;
+ color: @gray;
+ margin-bottom: 8px;
+ }
+}
diff --git a/src/page-components/AddressDetail/components/Tokens/Tokens.tsx b/src/page-components/AddressDetail/components/Tokens/Tokens.tsx
new file mode 100644
index 000000000..f4a63dcfa
--- /dev/null
+++ b/src/page-components/AddressDetail/components/Tokens/Tokens.tsx
@@ -0,0 +1,39 @@
+import { Table } from 'antd';
+import React, { useMemo, useEffect, useState } from 'react';
+import TableLayer from 'components/TableLayer/TableLayer';
+import getColumn from './columnConfig';
+import { isPhoneCheck, isPhoneCheckSSR } from 'utils/deviceCheck';
+require('./Tokens.styles.less');
+
+export default function Tokens({ balances, prices, dataLoading, headers }) {
+ const [isMobile, setIsMobile] = useState(!!isPhoneCheckSSR(headers));
+
+ const columns = useMemo(() => {
+ return getColumn({ prices, isMobile });
+ }, [prices]);
+
+ const sortedArr = useMemo(() => {
+ const elf = balances.find((i) => i.symbol === 'ELF');
+ const other = balances.filter((i) => i.symbol !== 'ELF');
+ return [elf, ...other];
+ }, [balances]);
+
+ useEffect(() => {
+ setIsMobile(!!isPhoneCheck());
+ }, []);
+
+ return (
+
+
A total of {balances.length} tokens found
+
+ record.symbol!}
+ pagination={false}
+ />
+
+
+ );
+}
diff --git a/src/page-components/AddressDetail/components/Tokens/columnConfig.tsx b/src/page-components/AddressDetail/components/Tokens/columnConfig.tsx
new file mode 100644
index 000000000..9f597763c
--- /dev/null
+++ b/src/page-components/AddressDetail/components/Tokens/columnConfig.tsx
@@ -0,0 +1,53 @@
+import Link from 'next/link';
+import React from 'react';
+import { numberFormatter } from 'utils/formater';
+import { ColumnsType } from 'antd/es/table';
+interface IRecord {
+ title?: string;
+ dataIndex?: string;
+ width?: number;
+ balance?: string;
+ render?: (prop: any) => void;
+ align?: 'left' | 'right' | 'center';
+ symbol?: string;
+}
+export default ({ prices, isMobile }) => {
+ const column: ColumnsType = [
+ {
+ title: 'Token Name',
+ dataIndex: 'symbol',
+ width: isMobile ? 94 : 376,
+ render(symbol) {
+ return symbol ? {symbol} : '-';
+ },
+ },
+ {
+ title: 'Balance',
+ dataIndex: 'balance',
+ width: isMobile ? 90 : 356,
+ render(balance) {
+ return numberFormatter(balance);
+ },
+ },
+ {
+ title: 'Token Price',
+ dataIndex: 'symbol',
+ width: isMobile ? 70 : 300,
+ render(symbol) {
+ if (symbol && prices) return prices[symbol] ? `$${numberFormatter(prices[symbol])}` : '-';
+ return '-';
+ },
+ },
+ {
+ title: 'Value in USD',
+ dataIndex: 'symbol',
+ align: 'right',
+ width: isMobile ? 70 : 136,
+ render(symbol, record) {
+ if (symbol && prices) return prices[symbol] ? `$${numberFormatter(prices[symbol] * +record.balance!)}` : '-';
+ return '-';
+ },
+ },
+ ];
+ return column;
+};
diff --git a/src/page-components/AddressDetail/components/Transactions/Transactions.styles.less b/src/page-components/AddressDetail/components/Transactions/Transactions.styles.less
new file mode 100644
index 000000000..b13f32640
--- /dev/null
+++ b/src/page-components/AddressDetail/components/Transactions/Transactions.styles.less
@@ -0,0 +1,36 @@
+.transactions-pane {
+ background: @bgBlankGrey;
+ .table-layer {
+ padding-top: 0;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ }
+ .after-table {
+ padding-top: 16px;
+ background: @bgBlankGrey;
+ .ant-pagination-options {
+ position: absolute;
+ border: none;
+ left: 0;
+ margin-left: 0;
+ &::before,
+ &::after {
+ font-size: 14px;
+ margin-right: 8px;
+ line-height: 32px;
+ }
+ &::before {
+ content: "Show";
+ }
+ &::after {
+ content: "records";
+ }
+ .ant-select-selector {
+ padding: 0 23px 0 9px;
+ .ant-select-selection-item {
+ padding-right: 5px;
+ }
+ }
+ }
+ }
+}
diff --git a/src/page-components/AddressDetail/components/Transactions/Transactions.tsx b/src/page-components/AddressDetail/components/Transactions/Transactions.tsx
new file mode 100644
index 000000000..9bae1d490
--- /dev/null
+++ b/src/page-components/AddressDetail/components/Transactions/Transactions.tsx
@@ -0,0 +1,62 @@
+import { Pagination } from 'antd';
+import React, { useCallback, useEffect, useState } from 'react';
+import TransactionTable from 'components/TransactionTable/TransactionTable';
+import { ADDRESS_TXS_API_URL } from 'constants/api';
+import useMobile from 'hooks/useMobile';
+import { get } from 'utils/axios';
+
+require('./Transactions.styles.less');
+
+export default function Transactions({ address }) {
+ const isMobile = useMobile();
+ const [dataLoading, setDataLoading] = useState(true);
+ const [pageIndex, setPageIndex] = useState(1);
+ const [pageSize, setPageSize] = useState(10);
+ const [total, setTotal] = useState(0);
+ const [dataSource, setDataSource] = useState(undefined);
+
+ const handlePageChange = useCallback(
+ (page, size) => {
+ setDataSource(undefined);
+ setDataLoading(true);
+ setPageIndex(size === pageSize ? page : 1);
+ setPageSize(size);
+ },
+ [pageSize],
+ );
+
+ const fetchTransactions = useCallback(async () => {
+ const result = await get(ADDRESS_TXS_API_URL, {
+ limit: pageSize,
+ page: pageIndex - 1,
+ address,
+ order: 'DESC',
+ });
+ const { transactions = [], total: resTotal = 0 } = result;
+ setTotal(resTotal);
+ setDataSource(transactions);
+ setDataLoading(false);
+ }, [address, pageSize, pageIndex]);
+
+ useEffect(() => {
+ fetchTransactions();
+ }, [fetchTransactions]);
+
+ return (
+
+
+
+
handlePageChange(1, size)}
+ />
+
+
+ );
+}
diff --git a/src/page-components/AddressDetail/components/Transfers/Transfers.styles.less b/src/page-components/AddressDetail/components/Transfers/Transfers.styles.less
new file mode 100644
index 000000000..e0b6242dd
--- /dev/null
+++ b/src/page-components/AddressDetail/components/Transfers/Transfers.styles.less
@@ -0,0 +1,52 @@
+.transfers-pane {
+ background: @bgBlankGrey;
+ .table-layer {
+ padding-top: 0;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ tbody {
+ .color-blue {
+ color: @mariner;
+ }
+ .from {
+ position: relative;
+ a {
+ display: block;
+ width: 116px;
+ overflow-x: hidden;
+ text-overflow: ellipsis;
+ word-wrap: none;
+ white-space: nowrap;
+ }
+ .ant-tag {
+ position: absolute;
+ top: -1px;
+ right: 0;
+ width: 40px;
+ line-height: 20px;
+ border-radius: 2px;
+ text-align: center;
+ padding: 2px 0;
+ border: none;
+ &.in {
+ background: #e6f8f0;
+ color: @jade;
+ }
+ &.out {
+ background: @fair-pink;
+ color: @sunset-orange;
+ }
+ }
+ }
+ .to {
+ display: block;
+ width: 126px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+ }
+ .after-table {
+ background: @bgBlankGrey;
+ }
+}
diff --git a/src/page-components/AddressDetail/components/Transfers/Transfers.tsx b/src/page-components/AddressDetail/components/Transfers/Transfers.tsx
new file mode 100644
index 000000000..10a908046
--- /dev/null
+++ b/src/page-components/AddressDetail/components/Transfers/Transfers.tsx
@@ -0,0 +1,78 @@
+import { Pagination, Table } from 'antd';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import { VIEWER_TRANSFER_LIST } from 'constants/viewerApi';
+import TableLayer from 'components/TableLayer/TableLayer';
+import useMobile from 'hooks/useMobile';
+import { get } from 'utils/axios';
+import getColumnConfig from './columnConfig';
+
+require('./Transfers.styles.less');
+
+export default function Transfers({ address }) {
+ const isMobile = useMobile();
+ const [timeFormat, setTimeFormat] = useState('Age');
+ const [dataLoading, setDataLoading] = useState(true);
+ const [pageIndex, setPageIndex] = useState(1);
+ const [pageSize, setPageSize] = useState(10);
+ const [total, setTotal] = useState(0);
+ const [dataSource, setDataSource] = useState(undefined);
+
+ const columns = useMemo(() => {
+ return getColumnConfig({
+ address,
+ isMobile,
+ timeFormat,
+ handleFormatChange: () => {
+ setTimeFormat(timeFormat === 'Age' ? 'Date Time' : 'Age');
+ },
+ });
+ }, [address, isMobile, timeFormat]);
+
+ const handlePageChange = useCallback(
+ (page, size) => {
+ setDataSource(undefined);
+ setDataLoading(true);
+ setPageIndex(size === pageSize ? page : 1);
+ setPageSize(size);
+ },
+ [pageSize],
+ );
+
+ const fetchTransfers = useCallback(async () => {
+ const result = await get(VIEWER_TRANSFER_LIST, {
+ pageSize,
+ pageNum: pageIndex,
+ address,
+ });
+ if (result.code === 0) {
+ const { data } = result;
+ setTotal(data.total);
+ setDataSource(data.list);
+ }
+ setDataLoading(false);
+ }, [address, pageSize, pageIndex]);
+
+ useEffect(() => {
+ fetchTransfers();
+ }, [fetchTransfers]);
+
+ return (
+
+
+
+
+
+
handlePageChange(1, size)}
+ />
+
+
+ );
+}
diff --git a/src/page-components/AddressDetail/components/Transfers/columnConfig.tsx b/src/page-components/AddressDetail/components/Transfers/columnConfig.tsx
new file mode 100644
index 000000000..fc704f9c3
--- /dev/null
+++ b/src/page-components/AddressDetail/components/Transfers/columnConfig.tsx
@@ -0,0 +1,93 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+import React from 'react';
+import Link from 'next/link';
+import clsx from 'clsx';
+import { Tag } from 'antd';
+import config from 'constants/config/config';
+import Dividends from 'components/Dividends';
+import IconFont from 'components/IconFont';
+import { getFormattedDate } from 'utils/timeUtils';
+import { numberFormatter } from 'utils/formater';
+import { ColumnsType } from 'antd/lib/table';
+const { CHAIN_ID } = config;
+interface IRecord {
+ title: string;
+ dataIndex: string;
+ ellipsis: boolean;
+ width: number;
+ render(prop: any): any;
+ align?: 'left' | 'right' | 'center';
+ symbol: string;
+}
+const getColumnConfig = ({ address, isMobile, timeFormat, handleFormatChange, ellipsis = true }) => {
+ const columnConfig: ColumnsType = [
+ {
+ title: 'Txn Hash',
+ width: isMobile ? 149 : 206,
+ ellipsis,
+ dataIndex: 'txId',
+ className: 'color-blue',
+ render(hash) {
+ return {hash};
+ },
+ },
+ {
+ dataIndex: 'time',
+ width: isMobile ? 156 : 190,
+ title: (
+
+ {timeFormat}
+
+ ),
+ render: (text) => {
+ return {getFormattedDate(text, timeFormat)}
;
+ },
+ },
+ {
+ title: 'From',
+ dataIndex: 'from',
+ width: isMobile ? 188 : 172,
+ className: 'color-blue',
+ render(from) {
+ const isOut = from === address;
+ return (
+
+ {`ELF_${from}_${CHAIN_ID}`}
+ {isOut ? 'OUT' : 'IN'}
+
+ );
+ },
+ },
+ {
+ title: 'Interacted With (To )',
+ dataIndex: 'to',
+ width: isMobile ? 140 : 224,
+ ellipsis,
+ className: 'color-blue',
+ render(to) {
+ return {`ELF_${to}_${CHAIN_ID}`};
+ },
+ },
+ {
+ title: 'Amount',
+ dataIndex: 'amount',
+ width: isMobile ? 96 : 150,
+ render(amount, record) {
+ return `${numberFormatter(amount)} ${record.symbol}`;
+ },
+ },
+ {
+ title: 'Txn Fee',
+ dataIndex: 'txFee',
+ align: 'right',
+ width: isMobile ? 96 : 120,
+ render(fee) {
+ return ;
+ },
+ },
+ ];
+ return columnConfig;
+};
+
+export default getColumnConfig;
diff --git a/src/page-components/Address/Viewer/index.jsx b/src/page-components/AddressDetail/components/Viewer/Viewer.tsx
similarity index 62%
rename from src/page-components/Address/Viewer/index.jsx
rename to src/page-components/AddressDetail/components/Viewer/Viewer.tsx
index 1882086cc..6f10c4296 100644
--- a/src/page-components/Address/Viewer/index.jsx
+++ b/src/page-components/AddressDetail/components/Viewer/Viewer.tsx
@@ -1,15 +1,23 @@
-/**
- * @file viewer
- * @author atom-yang
- */
+/*
+it wants to access the document object, and it requires browser environment.
+Basically you just need to avoid running that part out of browser environment
+*/
import React, { useEffect, useState, useRef } from 'react';
import copy from 'copy-to-clipboard';
import PropTypes from 'prop-types';
-import { CopyOutlined } from '@ant-design/icons';
-import { Button, message } from 'antd';
+import { message } from 'antd';
import { useMonaco } from '@monaco-editor/react';
+import { useEffectOnce } from 'react-use';
+import CopyButton from 'components/CopyButton/CopyButton';
+import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
require('./index.less');
+interface IProps {
+ content: string;
+ isShow: boolean;
+ name: string;
+ path: string;
+}
const languageDetector = [
{
language: 'csharp',
@@ -29,64 +37,54 @@ function getLanguage(name) {
return languageDetector.filter((v) => v.test.test(name))[0].language;
}
-let positions = {};
+const positions = {};
-const Viewer = (props) => {
+const Viewer = (props: IProps) => {
const monaco = useMonaco();
- const [editor, setEditor] = useState(null);
+ const [editor, setEditor] = useState();
const [isReadOnly] = useState(true);
const editorEl = useRef(null);
- const { content, name, path } = props;
- useEffect(() => () => editor && editor.dispose(), []);
+ const { content, name, path, isShow } = props;
+ useEffectOnce(() => () => editor?.dispose());
useEffect(() => {
const language = getLanguage(name);
+ const { [path]: position = { lineNumber: 1, column: 1, top: 1 } } = positions as any;
if (editor) {
- const position = positions[path] || {
- lineNumber: 1,
- column: 1,
- top: 1,
- };
- editor.updateOptions({
- language,
- });
- editor.setValue(atob(content));
+ editor.setValue(window.atob(content));
editor.setPosition(position);
editor.revealLine(position.lineNumber);
editor.setScrollTop(position.top);
} else {
- const monacoEditor = monaco?.editor.create(editorEl.current, {
+ const monacoEditor = monaco?.editor.create(editorEl.current!, {
lineNumbers: 'on',
readOnly: isReadOnly,
language,
- value: atob(content),
+ value: window.atob(content),
});
setEditor(monacoEditor);
window.editor = monacoEditor;
}
return () => {
if (editor) {
- positions = {
- ...positions,
- [path]: {
- ...editor.getPosition(),
- top: editor.getScrollTop(),
- },
+ positions[path] = {
+ ...editor.getPosition(),
+ top: editor.getScrollTop(),
};
}
};
- }, [monaco, path || null]);
+ }, [path, isShow, editorEl, monaco]);
useEffect(() => {
if (editor) {
editor.updateOptions({
readOnly: isReadOnly,
});
- editor.setValue(atob(content));
+ editor.setValue(window.atob(content));
}
- }, [isReadOnly]);
+ }, [isReadOnly, content]);
const handleCopy = () => {
try {
- copy(editor.getValue());
+ copy(editor?.getValue() as string);
message.success('Copied!');
} catch (e) {
message.error('Copy failed, please copy by yourself.');
@@ -98,7 +96,7 @@ const Viewer = (props) => {
{path}
- } title="Copy code" />
+
diff --git a/src/page-components/Address/Viewer/index.less b/src/page-components/AddressDetail/components/Viewer/index.less
similarity index 100%
rename from src/page-components/Address/Viewer/index.less
rename to src/page-components/AddressDetail/components/Viewer/index.less
diff --git a/src/page-components/AddressDetail/index.less b/src/page-components/AddressDetail/index.less
new file mode 100644
index 000000000..eb8e4069b
--- /dev/null
+++ b/src/page-components/AddressDetail/index.less
@@ -0,0 +1,91 @@
+.address-detail-page-container {
+ .basic-info {
+ margin-bottom: 16px;
+ h2 {
+ font-size: 18px;
+ font-weight: 400;
+ font-family: 'Roboto Bold', sans-serif;
+ line-height: 28px;
+ margin-bottom: 5px;
+ }
+ > p {
+ display: flex;
+ line-height: 24px;
+ svg {
+ border: 1px solid @alto;
+ border-radius: 4px;
+ width: 24px;
+ height: 24px;
+ padding: 4px;
+ }
+ > span {
+ margin-left: 16px;
+ &:last-child {
+ margin-left: 12px;
+ }
+ }
+ }
+ .ant-tooltip {
+ max-width: 268px;
+ }
+ .ant-tooltip-inner {
+ padding: 0;
+ }
+ }
+ .overview {
+ background: white;
+ border-radius: 8px;
+
+ > p {
+ width: 100%;
+ height: 48px;
+ padding: 13px 24px;
+ font-family: 'Roboto Medium', sans-serif;
+ color: @cloud-burst;
+ border-bottom: 1px solid @border-color2;
+ }
+ > div {
+ padding: 24px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ p {
+ .label {
+ color: @mine-shaft;
+ font-family: 'Roboto Medium', sans-serif;
+ display: inline-block;
+ min-width: 300px;
+ }
+ }
+ }
+ }
+ .more-info {
+ margin-top: 24px;
+ background: white;
+ border-radius: 8px;
+ .ant-tabs-nav-wrap {
+ padding-left: 24px;
+ }
+ }
+}
+
+.address-detail-page-container.mobile {
+ .basic-info {
+ h2 {
+ margin-bottom: 16px;
+ }
+ p {
+ align-items: center;
+ }
+ }
+ .overview {
+ > div {
+ gap: 24px;
+ > p {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ }
+ }
+ }
+}
diff --git a/src/page-components/AddressDetail/index.tsx b/src/page-components/AddressDetail/index.tsx
new file mode 100644
index 000000000..ebefcd2f0
--- /dev/null
+++ b/src/page-components/AddressDetail/index.tsx
@@ -0,0 +1,178 @@
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import clsx from 'clsx';
+import { Tabs, Tooltip } from 'antd';
+import { useEffectOnce, useUpdateEffect } from 'react-use';
+import CopyButton from 'components/CopyButton/CopyButton';
+import IconFont from 'components/IconFont';
+import config from 'constants/config/config';
+import QrCode from './components/QrCode/QrCode';
+import { get, getContractNames } from 'utils/axios';
+import { TOKEN_PRICE, VIEWER_BALANCES, VIEWER_GET_FILE, VIEWER_HISTORY } from 'constants/viewerApi';
+import { isPhoneCheck, isPhoneCheckSSR } from 'utils/deviceCheck';
+import CommonTabPane from './components/CommonTabPane';
+import Overview from './components/Overview';
+import ContractTabPane from './components/ContractTabPane';
+import { isAddress } from 'utils/utils';
+import { useRouter } from 'next/router';
+import { IBalance } from './types';
+require('./index.less');
+export default function AddressDetail({
+ balancesssr: balancesSSR,
+ pricesssr: pricesSSR,
+ contractinfossr: contractInfoSSR,
+ contracthistoryssr: contractHistorySSR,
+ activekeyssr: activeKeySSR,
+ contractsssr: contractsSSR,
+ headers,
+}) {
+ const router = useRouter();
+ const nav = router.push;
+ const { address, codeHash } = router.query as { address: string; codeHash: string };
+ const [isMobile, setIsMobile] = useState(!!isPhoneCheckSSR(headers));
+ const [activeKey, setActiveKey] = useState(activeKeySSR || 'tokens');
+ const [contracts, setContracts] = useState(contractsSSR || {});
+ const [prices, setPrices] = useState(pricesSSR || {});
+ const [balances, setBalances] = useState(balancesSSR || []);
+ const [tokensLoading, setTokensLoading] = useState(false);
+ const [contractInfo, setContractInfo] = useState(contractInfoSSR);
+ const [contractHistory, setContractHistory] = useState(contractHistorySSR);
+
+ const isCA = useMemo(() => !!contracts[address as string], [contracts, address]);
+
+ const elfBalance = useMemo(() => {
+ const temp: IBalance = balances.find((item: IBalance) => item.symbol === 'ELF')!;
+ return temp?.balance;
+ }, [balances]);
+
+ const pageTitle = useMemo(() => {
+ return isCA ? 'Contract' : 'Address';
+ }, [isCA]);
+
+ useEffect(() => {
+ setIsMobile(!!isPhoneCheck());
+ }, []);
+ const fetchBalances = useCallback(async () => {
+ setTokensLoading(true);
+ const result = await get(VIEWER_BALANCES, { address });
+ if (result?.code === 0) {
+ const { data } = result;
+ setBalances(data);
+ } else {
+ nav('/search-failed');
+ }
+ }, [address]);
+
+ const fetchPrice = useCallback(async () => {
+ if (balances.length) {
+ await Promise.allSettled(
+ balances.map((item: IBalance) => get(TOKEN_PRICE, { fsym: item.symbol, tsyms: 'USD' })),
+ ).then(
+ (res: any) => {
+ setTokensLoading(false);
+ res.forEach(({ value: item }) => {
+ if (item && item.USD) {
+ setPrices((v) => ({ ...v, [item.symbol]: item.USD }));
+ }
+ });
+ },
+ () => {
+ setPrices({});
+ },
+ );
+ } else {
+ setTokensLoading(false);
+ }
+ }, [balances]);
+
+ const fetchHistory = useCallback(async () => {
+ const result = await get(VIEWER_HISTORY, { address });
+ if (result?.code === 0) {
+ const { data } = result;
+ setContractHistory(data);
+ } else {
+ nav('/search-failed');
+ }
+ }, [address]);
+
+ const fetchFile = useCallback(async () => {
+ const result = await get(VIEWER_GET_FILE, { address, codeHash });
+ if (result?.code === 0) {
+ const { data } = result;
+ setContractInfo(data);
+ } else {
+ nav('/search-failed');
+ }
+ }, [address, codeHash]);
+
+ useUpdateEffect(() => {
+ fetchBalances();
+ }, [fetchBalances]);
+
+ useUpdateEffect(() => {
+ fetchPrice();
+ }, [balances]);
+
+ useUpdateEffect(() => {
+ if (isCA) {
+ fetchFile();
+ fetchHistory();
+ setActiveKey('contract');
+ } else {
+ setActiveKey('tokens');
+ }
+ }, [isCA, fetchFile]);
+
+ useEffectOnce(() => {
+ getContractNames().then((res) => setContracts(res));
+ });
+
+ useEffect(() => {
+ const res = isAddress(address);
+ if (!res) {
+ nav(`/search-invalid/${address}`);
+ }
+ }, [address]);
+
+ return (
+
+
+ {pageTitle}
+
+ {address}
+
+ node}
+ trigger="click"
+ title={}>
+
+
+
+
+
+
+ setActiveKey(key)}>
+ {CommonTabPane({ balances, prices, tokensLoading, address, headers }).map(({ children, ...props }) => (
+
+ {children}
+
+ ))}
+ {isCA &&
+ ContractTabPane({
+ contractInfo,
+ contractHistory,
+ address,
+ codeHash,
+ activeKey,
+ headers,
+ }).map(({ children, ...props }) => (
+
+ {children}
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/page-components/AddressDetail/types.ts b/src/page-components/AddressDetail/types.ts
new file mode 100644
index 000000000..8d7d43d5c
--- /dev/null
+++ b/src/page-components/AddressDetail/types.ts
@@ -0,0 +1,17 @@
+export interface IBalance {
+ balance?: string;
+ symbol: string;
+}
+export interface IFile {
+ files?: IFile[];
+ name: string;
+ key?: string;
+ content?: string;
+ fileType?: string;
+}
+export interface IViewerConfig {
+ content?: string;
+ fileType?: string;
+ name?: string;
+ path?: string;
+}
diff --git a/src/page-components/BlockDetail/BlockDetail.jsx b/src/page-components/BlockDetail/BlockDetail.tsx
similarity index 77%
rename from src/page-components/BlockDetail/BlockDetail.jsx
rename to src/page-components/BlockDetail/BlockDetail.tsx
index 256ced157..fb58a5b88 100644
--- a/src/page-components/BlockDetail/BlockDetail.jsx
+++ b/src/page-components/BlockDetail/BlockDetail.tsx
@@ -2,29 +2,34 @@ import { Tag, Tabs, Button } from 'antd';
import React, { useState, useCallback, useEffect, useMemo } from 'react';
import IconFont from 'components/IconFont';
import { BLOCK_INFO_API_URL } from 'constants/api';
-import useMobile from 'hooks/useMobile';
+import { isPhoneCheck, isPhoneCheckSSR } from 'utils/deviceCheck';
import { getContractNames } from 'utils/utils';
import { get, aelf } from 'utils/axios';
import BasicInfo from './components/BasicInfo';
import ExtensionInfo from './components/ExtensionInfo';
import TransactionList from './components/TransactionList';
import Link from 'next/link';
-import { useDebounce } from 'react-use';
+import { useDebounce, useUpdateEffect } from 'react-use';
import CustomSkeleton from 'components/CustomSkeleton/CustomSkeleton';
import { withRouter } from 'next/router';
+import { IProps, IRes, ITransactions, Itx } from './types';
require('./BlockDetail.styles.less');
-
const { TabPane } = Tabs;
-function BlockDetail(props) {
- const [pageId, setPageId] = useState(undefined);
- const [blockHeight, setBlockHeight] = useState(undefined);
- const [blockInfo, setBlockInfo] = useState(undefined);
- const [transactionList, setTransactionList] = useState([]);
+
+function BlockDetail(props: IProps) {
+ const [pageId, setPageId] = useState(props.pageidssr);
+ const [blockHeight, setBlockHeight] = useState(props.blockheightssr);
+ const [blockInfo, setBlockInfo] = useState(props.blockinfossr);
+ const [transactionList, setTransactionList] = useState(props.txslistssr || []);
const [retryBlockInfoCount, setRetryBlockInfoCount] = useState(0);
- const [bestChainHeight, setBestChainHeight] = useState(undefined);
+ const [bestChainHeight, setBestChainHeight] = useState(props.bestchainheightssr);
const [showExtensionInfo, setShowExtensionInfo] = useState(false);
- const [activeKey, setActiveKey] = useState('overview');
- const isMobile = useMobile();
+ const [activeKey, setActiveKey] = useState(props.activekeyssr || 'overview');
+ const [isMobile, setIsMobile] = useState(!!isPhoneCheckSSR(props.headers));
+
+ useEffect(() => {
+ setIsMobile(!!isPhoneCheck());
+ }, []);
const retryBlockInfoLimit = 2;
const jumpLink = useMemo(() => {
@@ -33,7 +38,7 @@ function BlockDetail(props) {
return (
- +blockHeight === 1 || props.router.push(prevLink)}>
+ +blockHeight === 1 || props.router.push(prevLink)}>
@@ -45,7 +50,7 @@ function BlockDetail(props) {
);
}, [blockHeight]);
- useEffect(() => {
+ useUpdateEffect(() => {
const { query } = props.router;
const { id } = query;
if (id !== pageId) {
@@ -53,26 +58,23 @@ function BlockDetail(props) {
setBlockInfo(undefined);
setShowExtensionInfo(false);
}
- setActiveKey('overview');
- if (location.search && location.search.includes('tab=txns')) {
+ if (location.search?.includes('tab=txns')) {
setActiveKey('transactions');
+ } else {
+ setActiveKey('overview');
}
}, [props]);
- useDebounce(
- () => {
- try {
- fetchBlockInfo();
- } catch (error) {
- console.log('>>>error', error);
- }
- },
- 1000,
- [pageId],
- );
+ useUpdateEffect(() => {
+ try {
+ fetchBlockInfo();
+ } catch (error) {
+ console.log('>>>error', error);
+ }
+ }, [pageId]);
- const merge = useCallback((data = [], contractNames) => {
- return (data || []).map((item) => ({
+ const merge = useCallback((data: Itx[] | never[] = [], contractNames) => {
+ return (data || []).map((item: Itx) => ({
...item,
contractName: contractNames[item.address_to],
}));
@@ -84,24 +86,24 @@ function BlockDetail(props) {
.then((result) => {
return result;
})
- .catch((error) => {
+ .catch((_) => {
location.href = '/search-failed';
});
}, [aelf]);
- const getTxsList = useCallback(
- async (blockHash, page) => {
- let getTxsOption = {
+ const getTxsList: (blockHash: string, page?: number) => Promise = useCallback(
+ async (blockHash, page?) => {
+ const getTxsOption = {
limit: 1000,
page: page || 0,
order: 'asc',
block_hash: blockHash,
};
- let data = await get('/block/transactions', getTxsOption).catch((error) => {
+ let data: ITransactions = (await get('/block/transactions', getTxsOption).catch((error) => {
console.log('>>>>error', error);
location.href = '/search-failed';
- });
+ })) as ITransactions;
const contractNames = await getContractNames().catch((error) => {
console.log('>>>>error', error);
location.href = '/search-failed';
@@ -123,9 +125,9 @@ function BlockDetail(props) {
});
const { BlockHash: blockHash } = result;
const { transactions = [] } = blockHash
- ? await getTxsList(blockHash).catch((error) => {
+ ? ((await getTxsList(blockHash).catch((error) => {
location.href = '/search-failed';
- })
+ })) as ITransactions)
: {};
return { blockInfo: result, transactionList: transactions };
} catch (err) {
@@ -163,22 +165,20 @@ function BlockDetail(props) {
let result;
let blockHeight;
- let txsList = [];
- let error;
- if (parseInt(input, 10) == input) {
+ let txsList: Itx[] = [];
+ if (parseInt('' + input, 10) == input) {
blockHeight = input;
if (blockHeight > BestChainHeight) {
location.href = '/search-invalid/' + blockHeight;
} else {
const data = await getDataFromHeight(input);
result = data.blockInfo;
- txsList = data.transactionList;
+ txsList = data.transactionList!;
}
} else {
const data = await getDataFromHash(input);
result = data.blockInfo;
- txsList = data.transactionList;
- error = txsList.length ? '' : 'Not Found';
+ txsList = data.transactionList!;
blockHeight = result.Header.Height;
}
setBlockHeight(blockHeight);
@@ -187,7 +187,7 @@ function BlockDetail(props) {
get(BLOCK_INFO_API_URL, {
height: blockHeight,
})
- .then((res = { miner: '', dividends: '' }) => {
+ .then((res: IRes = { miner: '', dividends: '' }) => {
if (result) {
const { Header: header } = result;
setBlockInfo({
@@ -215,10 +215,10 @@ function BlockDetail(props) {
location.href = '/search-invalid/' + pageId;
}
})
- .catch((error) => {
+ .catch((_) => {
location.href = '/search-failed';
});
- // Dismiss manually and asynchronously
+ // if block is new and cannot txsList is null
if ((!txsList || !txsList.length) && blockHeight <= BestChainHeight + 6) {
if (retryBlockInfoCount >= retryBlockInfoLimit) {
return;
@@ -259,7 +259,7 @@ function BlockDetail(props) {
-
+
diff --git a/src/page-components/BlockDetail/components/BasicInfo.jsx b/src/page-components/BlockDetail/components/BasicInfo.tsx
similarity index 95%
rename from src/page-components/BlockDetail/components/BasicInfo.jsx
rename to src/page-components/BlockDetail/components/BasicInfo.tsx
index 3c18b064d..70d9c74ec 100644
--- a/src/page-components/BlockDetail/components/BasicInfo.jsx
+++ b/src/page-components/BlockDetail/components/BasicInfo.tsx
@@ -22,8 +22,8 @@ export default function BasicInfo({ basicInfo, bestChainHeight }) {
Timestamp: (
-
- {getFormattedDate(basicInfo.timestamp)}({moment(basicInfo.timestamp).format('MMM-DD-YYYY hh:mm:SS A')}
+
+ {getFormattedDate(basicInfo.timestamp)}({moment(basicInfo.timestamp).format('MMM-DD-YYYY hh:mm:ss A')}
)
diff --git a/src/page-components/BlockDetail/components/ExtensionInfo.jsx b/src/page-components/BlockDetail/components/ExtensionInfo.tsx
similarity index 100%
rename from src/page-components/BlockDetail/components/ExtensionInfo.jsx
rename to src/page-components/BlockDetail/components/ExtensionInfo.tsx
diff --git a/src/page-components/BlockDetail/components/TransactionList.jsx b/src/page-components/BlockDetail/components/TransactionList.tsx
similarity index 62%
rename from src/page-components/BlockDetail/components/TransactionList.jsx
rename to src/page-components/BlockDetail/components/TransactionList.tsx
index 39c60f0ff..2addb8ffa 100644
--- a/src/page-components/BlockDetail/components/TransactionList.jsx
+++ b/src/page-components/BlockDetail/components/TransactionList.tsx
@@ -1,12 +1,25 @@
import { Pagination } from 'antd';
-import React, { useCallback, useMemo, useState } from 'react';
+import React, { useCallback, useMemo, useState, useEffect } from 'react';
import TransactionTable from 'components/TransactionTable/TransactionTable';
-import useMobile from 'hooks/useMobile';
+import { isPhoneCheck, isPhoneCheckSSR } from 'utils/deviceCheck';
+import { Itx } from '../types';
-export default function TransactionList({ allData = [] }) {
+export default function TransactionList({
+ allData = [],
+ headers,
+ price,
+}: {
+ allData: Itx[];
+ headers: any;
+ price: object;
+}) {
const [pageIndex, setPageIndex] = useState(1);
const [pageSize, setPageSize] = useState(10);
- const isMobile = useMobile();
+ const [isMobile, setIsMobile] = useState(!!isPhoneCheckSSR(headers));
+
+ useEffect(() => {
+ setIsMobile(!!isPhoneCheck());
+ }, []);
const dataSource = useMemo(() => {
return allData.slice((pageIndex - 1) * pageSize, pageIndex * pageSize);
}, [pageIndex, pageSize, allData]);
@@ -19,7 +32,7 @@ export default function TransactionList({ allData = [] }) {
);
return (
-
+
handlePageChange(1, size)}
+ onShowSizeChange={(_, size) => handlePageChange(1, size)}
/>
diff --git a/src/page-components/BlockDetail/types.ts b/src/page-components/BlockDetail/types.ts
new file mode 100644
index 000000000..1a3093cd4
--- /dev/null
+++ b/src/page-components/BlockDetail/types.ts
@@ -0,0 +1,61 @@
+import { NextRouter } from 'next/router';
+export interface IRes {
+ miner?: string;
+ dividends?: string;
+}
+export interface Itx {
+ id: number;
+ tx_id: string;
+ params_to: string;
+ chain_id: string;
+ block_height: number;
+ address_from: string;
+ address_to: string;
+ params: string;
+ method: string;
+ block_hash: string;
+ tx_fee: string;
+ resources: string;
+ quantity: number;
+ tx_status: string;
+ time: string;
+ contractName: string;
+ transactions?: number;
+}
+interface IBasicInfo {
+ blockHeight: string;
+ timestamp: string;
+ blockHash: string;
+ transactions: number;
+ chainId: string;
+ miner?: string;
+ reward?: string;
+ previousBlockHash: string;
+}
+interface IExtensionInfo {
+ blockSize: number;
+ merkleTreeRootOfTransactions: string;
+ merkleTreeRootOfWorldState: string;
+ merkleTreeRootOfTransactionState: string;
+ extra: string;
+ bloom: string;
+ signerPubkey: string;
+}
+export interface IBlockInfo {
+ basicInfo: IBasicInfo;
+ extensionInfo: IExtensionInfo;
+}
+export interface ITransactions {
+ transactions?: Itx[];
+}
+export interface IProps {
+ pageidssr?: string | string[] | number;
+ activekeyssr: string;
+ bestchainheightssr: string;
+ blockheightssr: string;
+ txslistssr?: Itx[];
+ blockinfossr?: IBlockInfo;
+ headers: any;
+ router: NextRouter;
+ pricessr: object;
+}
diff --git a/src/page-components/Blocks/BlockList.styles.less b/src/page-components/Blocks/BlockList.styles.less
index e3c1dd266..97aad914a 100644
--- a/src/page-components/Blocks/BlockList.styles.less
+++ b/src/page-components/Blocks/BlockList.styles.less
@@ -4,30 +4,11 @@
padding-top: 24px;
h2 {
font-size: 18px;
- font-family: 'Roboto Bold';
+ font-family: 'Roboto Bold', sans-serif;
font-weight: 400;
margin-bottom: 16px;
color: @cloud-burst;
}
-
- .before-table {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 16px;
- .left {
- display: flex;
- flex-direction: column;
- row-gap: 2px;
- p {
- font-family: 'Roboto';
- height: 22px;
- line-height: 22px;
- color: @scorpion;
- font-size: 14px;
- }
- }
- }
.block-table {
display: flex;
background: @bgBlankWhite;
@@ -41,7 +22,7 @@
width: calc(100% - 32px);
thead {
th {
- font-family: 'Roboto-Medium';
+ font-family: 'Roboto-Medium', sans-serif;
background: @bgBlankWhite !important;
border-bottom: @border-color2 1px solid !important;
font-weight: 400;
@@ -114,45 +95,8 @@
}
}
}
- .after-table {
- margin-top: 16px;
- .ant-pagination-options {
- position: absolute;
- border: none;
- left: 0;
- margin-left: 0;
- &::before,
- &::after {
- font-size: 14px;
- margin-right: 8px;
- line-height: 32px;
- }
- &::before {
- content: 'Show';
- }
- &::after {
- content: 'records';
- }
- .ant-select-selector {
- padding: 0 23px 0 9px;
- .ant-select-selection-item {
- padding-right: 5px;
- }
- }
- }
- }
}
.blocks-page-container.mobile {
- .before-table {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- margin-bottom: 16px;
- .right {
- display: flex;
- padding-top: 16px;
- }
- }
.block-table {
background: white;
overflow-x: scroll;
@@ -162,28 +106,4 @@
min-width: 691px;
}
}
- .after-table {
- display: flex;
- .ant-pagination {
- height: 76px;
- width: 100%;
- justify-content: flex-start;
- }
- .ant-pagination-options {
- display: flex;
- border: none;
- align-items: center;
- top: 0;
- &::before {
- content: 'Show';
- font-size: 14px;
- margin-right: 8px;
- }
- &::after {
- content: 'records';
- font-size: 14px;
- margin-left: 8px;
- }
- }
- }
}
diff --git a/src/page-components/Blocks/columnConfig.jsx b/src/page-components/Blocks/columnConfig.tsx
similarity index 82%
rename from src/page-components/Blocks/columnConfig.jsx
rename to src/page-components/Blocks/columnConfig.tsx
index d151e93b8..dbf59c379 100644
--- a/src/page-components/Blocks/columnConfig.jsx
+++ b/src/page-components/Blocks/columnConfig.tsx
@@ -3,10 +3,13 @@ import Link from 'next/link';
import { getFormattedDate } from 'utils/timeUtils';
import IconFont from 'components/IconFont';
import { SYMBOL, CHAIN_ID } from 'constants/misc';
-import { isPhoneCheck } from 'utils/deviceCheck';
+import { isPhoneCheck, isPhoneCheckSSR } from 'utils/deviceCheck';
-export default (timeFormat, handleFormatChange) => {
- const isMobile = isPhoneCheck();
+export default (timeFormat, handleFormatChange, headers) => {
+ let isMobile = !!isPhoneCheckSSR(headers);
+ if (typeof window !== 'undefined') {
+ isMobile = !!isPhoneCheck();
+ }
return [
{
dataIndex: 'block_height',
@@ -31,7 +34,9 @@ export default (timeFormat, handleFormatChange) => {
),
render: (text) => {
- return {getFormattedDate(text, timeFormat)}
;
+ // time in client is different to in server
+ // so we have to use suppressHydrationWarning to stop the warning
+ return {getFormattedDate(text, timeFormat)}
;
},
},
{
diff --git a/src/page-components/Blocks/index.jsx b/src/page-components/Blocks/index.tsx
similarity index 66%
rename from src/page-components/Blocks/index.jsx
rename to src/page-components/Blocks/index.tsx
index 9d6d69916..bde834972 100644
--- a/src/page-components/Blocks/index.jsx
+++ b/src/page-components/Blocks/index.tsx
@@ -2,66 +2,76 @@ import { Pagination, Table } from 'antd';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import useDebounce from 'react-use/lib/useDebounce';
import { ALL_BLOCKS_API_URL, ALL_UNCONFIRMED_BLOCKS_API_URL } from 'constants/api';
-import useMobile from 'hooks/useMobile';
import { get } from 'utils/axios';
import ColumnConfig from './columnConfig';
import { useRouter } from 'next/router';
+import TableLayer from 'components/TableLayer/TableLayer';
+import { isPhoneCheck, isPhoneCheckSSR } from 'utils/deviceCheck';
+import { IBlocksResult } from 'page-components/Home/types';
+import { useUpdateEffect } from 'react-use';
require('./BlockList.styles.less');
-import TableLayer from 'components/TableLayer/TableLayer';
-export default function BlockList() {
+
+interface IProps {
+ headers: any;
+ allssr: number;
+ datasourcessr: any;
+}
+export default function BlockList({ allssr: allSSR, datasourcessr: dataSourceSSR, headers }: IProps) {
const { pathname = '' } = useRouter();
- const isMobile = useMobile();
const [timeFormat, setTimeFormat] = useState('Age');
- const [all, setAll] = useState(0);
- const [dataSource, setDataSource] = useState(undefined);
+ const [all, setAll] = useState(allSSR);
+ const [dataSource, setDataSource] = useState(dataSourceSSR);
const [dataLoading, setDataLoading] = useState(false);
const [pageIndex, setPageIndex] = useState(1);
const [pageSize, setPageSize] = useState(50);
-
+ const [isMobile, setIsMobile] = useState(!!isPhoneCheckSSR(headers));
+ // judge whether is confirmed
const pageTitle = useMemo(() => (pathname.includes('unconfirmed') ? 'Unconfirmed Blocks' : 'Blocks'), [pathname]);
-
const isConfirmed = useMemo(() => pathname.includes('unconfirmed'), [pathname]);
-
const api = useMemo(() => {
return pathname.indexOf('unconfirmed') === -1 ? ALL_BLOCKS_API_URL : ALL_UNCONFIRMED_BLOCKS_API_URL;
}, [pathname]);
const fetch = useCallback(
async (pageIndex) => {
- setDataLoading(true);
- setDataSource(undefined);
- const data = await get(api, {
- order: 'desc',
- page: pageIndex - 1,
- limit: pageSize,
- });
-
- setAll(data ? data.total : 0);
+ try {
+ const data: IBlocksResult = (await get(api, {
+ order: 'desc',
+ page: pageIndex - 1,
+ limit: pageSize,
+ })) as IBlocksResult;
+ setAll(data ? data.total : 0);
+ setDataSource(data && data.blocks.length ? data.blocks : null);
+ } catch {
+ setDataSource(undefined);
+ }
setDataLoading(false);
- setDataSource(data && data.blocks.length ? data.blocks : null);
},
[api, pageSize],
);
- useEffect(() => {
+ useUpdateEffect(() => {
+ setIsMobile(!!isPhoneCheck());
if (pageIndex === 1) {
+ setDataLoading(true);
+ setDataSource(undefined);
fetch(pageIndex);
} else {
+ // unconfirmed -> confirmed
+ setDataLoading(true);
+ setDataSource(undefined);
setPageIndex(1);
}
}, [pathname]);
- useDebounce(
- () => {
- fetch(pageIndex);
- },
- 300,
- [pageIndex, pageSize],
- );
+ useUpdateEffect(() => {
+ fetch(pageIndex);
+ }, [pageIndex, pageSize]);
const handlePageChange = useCallback(
(page, size) => {
+ setDataLoading(true);
setPageIndex(size === pageSize ? page : 1);
setPageSize(size);
},
@@ -70,9 +80,13 @@ export default function BlockList() {
const columns = useMemo(
() =>
- ColumnConfig(timeFormat, () => {
- setTimeFormat(timeFormat === 'Age' ? 'Date Time' : 'Age');
- }),
+ ColumnConfig(
+ timeFormat,
+ () => {
+ setTimeFormat(timeFormat === 'Age' ? 'Date Time' : 'Age');
+ },
+ headers,
+ ),
[timeFormat],
);
@@ -97,7 +111,7 @@ export default function BlockList() {
/>
-
+
handlePageChange(1, size)}
+ onShowSizeChange={(_, size) => handlePageChange(1, size)}
/>
diff --git a/src/page-components/Contracts/columnConfig.tsx b/src/page-components/Contracts/columnConfig.tsx
new file mode 100644
index 000000000..1e50c7fb1
--- /dev/null
+++ b/src/page-components/Contracts/columnConfig.tsx
@@ -0,0 +1,57 @@
+import { Tag } from 'antd';
+import clsx from 'clsx';
+import moment from 'moment';
+import React from 'react';
+import AddressLink from 'components/AddressLink';
+import { ColumnsType } from 'antd/lib/table';
+interface IRecord {
+ title: string;
+ dataIndex: string;
+ ellipsis: boolean;
+ width: number;
+ render(name: any): any;
+ align?: 'left' | 'right' | 'center';
+}
+export default ({ isMobile }) => {
+ const columns: ColumnsType = [
+ {
+ title: 'Address',
+ dataIndex: 'address',
+ width: isMobile ? 232 : 320,
+ ellipsis: true,
+ render: (address) => ,
+ },
+ {
+ title: 'Contract Name',
+ dataIndex: 'contractName',
+ ellipsis: true,
+ width: isMobile ? 126 : 220,
+ render(name) {
+ return name === '-1' ? '-' : name;
+ },
+ },
+ {
+ title: 'Type',
+ dataIndex: 'isSystemContract',
+ width: isMobile ? 110 : 230,
+ render(isSystem) {
+ return {isSystem ? 'System' : 'User'};
+ },
+ },
+ {
+ title: 'Version',
+ dataIndex: 'version',
+ width: isMobile ? 66 : 142,
+ },
+ {
+ title: 'Last Updated At',
+ width: isMobile ? 166 : 160,
+ align: 'right',
+ dataIndex: 'updateTime',
+ render(time) {
+ return {moment(time).format('yyyy-MM-DD HH:mm:ss')}
;
+ },
+ },
+ ];
+ return columns;
+};
diff --git a/src/page-components/Contracts/index.less b/src/page-components/Contracts/index.less
new file mode 100644
index 000000000..507cc73a2
--- /dev/null
+++ b/src/page-components/Contracts/index.less
@@ -0,0 +1,29 @@
+.contracts-page-container {
+ h2 {
+ font-size: 18px;
+ font-family: 'Roboto Bold', sans-serif;
+ font-weight: 400;
+ margin-bottom: 16px;
+ color: @cloud-burst;
+ }
+
+ table {
+ .ant-tag {
+ display: flex;
+ justify-content: center;
+ border: none;
+ padding: 2px 0;
+ width: 60px;
+ line-height: 20px;
+ font-size: 12px;
+ &.user {
+ background: #e6f8f0;
+ color: @jade;
+ }
+ &.system {
+ background: #fef6e7;
+ color: @sun;
+ }
+ }
+ }
+}
diff --git a/src/page-components/Contracts/index.tsx b/src/page-components/Contracts/index.tsx
new file mode 100644
index 000000000..b61ab126d
--- /dev/null
+++ b/src/page-components/Contracts/index.tsx
@@ -0,0 +1,109 @@
+import React, { useCallback, useMemo, useState, useEffect } from 'react';
+import clsx from 'clsx';
+import { Pagination, Table } from 'antd';
+import { useDebounce } from 'react-use';
+import TableLayer from 'components/TableLayer/TableLayer';
+import { get } from 'utils/axios';
+import getColumn from './columnConfig';
+import { isPhoneCheck, isPhoneCheckSSR } from 'utils/deviceCheck';
+import { VIEWER_CONTRACTS_LIST } from 'constants/viewerApi';
+require('./index.less');
+
+export default function Contracts({ actualtotalssr, datasourcessr, headers }) {
+ const [dataLoading, setDataLoading] = useState(false);
+ const [pageIndex, setPageIndex] = useState(1);
+ const [pageSize, setPageSize] = useState(50);
+ const [dataSource, setDataSource] = useState(datasourcessr);
+ const [actualTotal, setActualTotal] = useState(actualtotalssr || 0);
+ const [isMobile, setIsMobile] = useState(!!isPhoneCheckSSR(headers));
+
+ useEffect(() => {
+ setIsMobile(!!isPhoneCheck());
+ }, []);
+ const total = useMemo(() => {
+ if (actualTotal > 1000) return 1000;
+ return actualTotal;
+ }, [actualTotal]);
+
+ const columns = useMemo(() => {
+ return getColumn({
+ isMobile,
+ });
+ }, [isMobile, pageIndex, pageSize]);
+
+ const fetchContractList = useCallback(async () => {
+ const result = await get(VIEWER_CONTRACTS_LIST, {
+ pageSize,
+ pageNum: pageIndex,
+ });
+ if (result.code === 0) {
+ const { data } = result;
+ setActualTotal(data.total);
+ setDataSource(data.list);
+ setDataLoading(false);
+ } else {
+ // when error
+ setDataSource(undefined);
+ setDataLoading(false);
+ }
+ }, [pageSize, pageIndex]);
+
+ const handlePageChange = useCallback(
+ (page, size) => {
+ setDataSource(undefined);
+ setDataLoading(true);
+ setPageIndex(size === pageSize ? page : 1);
+ setPageSize(size);
+ },
+ [pageSize],
+ );
+
+ useDebounce(
+ () => {
+ fetchContractList();
+ },
+ 1000,
+ [pageIndex, pageSize],
+ );
+
+ return (
+
+
Contracts
+
+
+
+
+ A total of {'>'} {Number(actualTotal).toLocaleString()} contracts found
+
+
(Showing the last 1,000 contracts only)
+
+
+
+
+
+
+
+
handlePageChange(1, size)}
+ />
+
+
+
+ );
+}
diff --git a/src/page-components/Home/Home.styles.less b/src/page-components/Home/Home.styles.less
index b5ca8ba07..e2f9c6fcb 100644
--- a/src/page-components/Home/Home.styles.less
+++ b/src/page-components/Home/Home.styles.less
@@ -12,13 +12,13 @@
display: flex;
flex-direction: column;
align-items: center;
+ justify-content: space-around;
gap: 24px;
padding: 40px 0;
height: 184px;
h2 {
text-align: center;
font-family: 'Roboto Bold', sans-serif;
- font-weight: 400;
font-size: 24px;
color: @cloud-burst;
}
@@ -169,12 +169,15 @@
.blocks {
.block {
flex: 126;
+ max-width: 126px;
}
.age {
flex: 160;
+ max-width: 160px;
}
.txns {
flex: 100;
+ max-width: 100px;
}
.reward {
flex: 102;
@@ -205,6 +208,13 @@
text-align: right;
}
}
+ .row {
+ .hash,
+ .from,
+ .to {
+ color: @mariner;
+ }
+ }
}
}
.chart-section {
@@ -230,6 +240,7 @@
height: 181px;
padding: 35px 16px 0;
gap: 8px;
+ justify-content: start;
h2 {
font-size: 18px;
line-height: 18px;
diff --git a/src/page-components/Home/components/LatestInfo.tsx b/src/page-components/Home/components/LatestInfo.tsx
index b0cfe99ae..e02d61975 100644
--- a/src/page-components/Home/components/LatestInfo.tsx
+++ b/src/page-components/Home/components/LatestInfo.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
import Link from 'next/link';
import Dividends from 'components/Dividends';
import IconFont from 'components/IconFont';
@@ -11,9 +11,10 @@ interface IProps {
headers: any;
}
export default function LatestInfo({ blocks = [], transactions = [], headers }: IProps) {
- let isMobile = !!isPhoneCheckSSR(headers);
+ const [isMobile, setIsMobile] = useState(!!isPhoneCheckSSR(headers));
+
useEffect(() => {
- isMobile = !!isPhoneCheck();
+ setIsMobile(!!isPhoneCheck());
}, []);
return (
diff --git a/src/page-components/Home/components/Search.tsx b/src/page-components/Home/components/Search.tsx
index 4a8330701..cf7ca28c5 100644
--- a/src/page-components/Home/components/Search.tsx
+++ b/src/page-components/Home/components/Search.tsx
@@ -1,95 +1,20 @@
-import { Button, Input, message } from 'antd';
+import { Button, Input } from 'antd';
import React, { useState } from 'react';
import { useCallback } from 'react';
import IconFont from '../../../components/IconFont';
-import { get, isAElfAddress } from '../../../utils/axios';
-import { useMemo } from 'react';
-import { INPUT_ZERO_TIP, TXS_BLOCK_API_URL } from '../../../constants';
import { withRouter, NextRouter } from 'next/router';
+import { getHandleSearch } from 'utils/search';
interface IProps {
router: NextRouter;
}
function Search(props: IProps) {
const [value, setValue] = useState('');
const navigate = props.router.push;
+ const handleSearch = getHandleSearch(navigate, value);
- const searchRules = useMemo(
- () => ({
- hash: async (val: string) => {
- const getTxsOption = {
- limit: 1,
- page: 0,
- block_hash: val,
- };
-
- const blockInfo: any = await get(TXS_BLOCK_API_URL, getTxsOption);
- const isBlock = blockInfo.transactions && blockInfo.transactions.length;
- searchRules[isBlock ? 'block' : 'transaction'](val);
- },
- address: (val: string) => {
- navigate(`/address/${val}`);
- },
- transaction: async (val: string) => {
- navigate(`/tx/${val}`);
- },
- block: (val: string) => {
- navigate(`/block/${val}`);
- },
- blockHeight: (val: string) => {
- navigate(`/block/${val}`);
- },
- invalid: () => {
- navigate(`/search-invalid/${value}`);
- },
- }),
- [value],
- );
-
- const getInputType = useCallback((value: string) => {
- const isTxId = [64];
- if (isAElfAddress(value)) {
- return 'address';
- }
- const length = value.length;
-
- if (isTxId.includes(length)) {
- return 'hash';
- }
- const number = parseInt(value, 10).toString();
- if (number === value) {
- return 'blockHeight';
- }
- return 'invalid';
- }, []);
-
- const handleInput = useCallback((e: any) => {
+ const handleInput = useCallback((e) => {
setValue(e.target.value);
}, []);
- const handleSearch = useCallback(() => {
- let tempValue = value.trim();
- if (!tempValue) return;
- if (tempValue.indexOf('_') > 0) {
- tempValue = tempValue.split('_')[1];
- }
- // address.length === 38/66 && address.match(/^0x/)
- // search
- // 0. 0x
- // 1. transaction 66
- // 2. block 66
- // 3. address length=38
- if (`${tempValue}`.startsWith('-')) {
- location.href = '/search-invalid/' + tempValue;
- return;
- }
- if (+tempValue === 0) {
- message.error(INPUT_ZERO_TIP);
- location.href = '/search-invalid/' + tempValue;
- return;
- }
- const searchType = getInputType(tempValue);
-
- searchRules[searchType](tempValue);
- }, [value]);
return (
diff --git a/src/page-components/Home/index.tsx b/src/page-components/Home/index.tsx
index 502b5b37b..f348fddd6 100644
--- a/src/page-components/Home/index.tsx
+++ b/src/page-components/Home/index.tsx
@@ -37,7 +37,7 @@ export default function Home({
mobileprice: mobilePrice,
mobileprevprice: mobilePrevPrice,
tpsdata: tpsData,
- blockheight: blockHeight,
+ blockheight: blockHeightSSR,
rewardssr: rewardSSR,
localaccountsssr: localAccountsSSR,
unconfirmedblockheightssr: unconfirmedBlockHeightSSR,
@@ -56,12 +56,13 @@ export default function Home({
const [localTransactions, setLocalTransactions] = useState(localTransactionsSSR || 0);
const [localAccounts, setLocalAccounts] = useState(localAccountsSSR || 0);
const [unconfirmedBlockHeight, setUnconfirmedBlockHeight] = useState(unconfirmedBlockHeightSSR || '0');
- let isMobile = !!isPhoneCheckSSR(headers);
+ const [blockHeight, setBlockHeight] = useState(blockHeightSSR || 0);
+ const [isMobile, setIsMobile] = useState(!!isPhoneCheckSSR(headers));
+
const latestSection = useMemo(
() =>
,
[blocks, transactions],
);
- blockHeight = blockHeight || 0;
const range = useMemo(() => {
if (price.USD && previousPrice.usd) {
return ((price.USD - previousPrice.usd) / previousPrice.usd) * 100;
@@ -69,7 +70,7 @@ export default function Home({
return 0;
}, [price.USD, previousPrice.usd]);
useEffect(() => {
- isMobile = !!isPhoneCheck();
+ setIsMobile(!!isPhoneCheck());
}, []);
useEffect(() => {
if (CHAIN_ID === 'AELF' && NETWORK_TYPE === 'MAIN' && isMobile) {
@@ -109,9 +110,8 @@ export default function Home({
const initBasicInfo = useCallback(async () => {
const result: IBasicInfo = (await get(BASIC_INFO)) as IBasicInfo;
-
const { height = 0, totalTxs, unconfirmedBlockHeight = '0', accountNumber = 0 } = result;
- blockHeight = height;
+ setBlockHeight(height);
setLocalTransactions(totalTxs);
setUnconfirmedBlockHeight(unconfirmedBlockHeight);
setLocalAccounts(accountNumber);
@@ -152,7 +152,7 @@ export default function Home({
arr.sort((pre, next) => next.block.Header.Height - pre.block.Header.Height);
const new_transactions = arr.reduce((acc: ISocketTxItem[], i) => acc.concat(i.txs), []).map(transactionFormat);
const new_blocks = arr.map((item) => formatBlock(item.block));
- blockHeight = height;
+ setBlockHeight(height);
setUnconfirmedBlockHeight(unconfirmedHeight);
setTransactions((v) => {
const temp: ITXItem = Object.fromEntries([...new_transactions, ...v].map((item) => [item.tx_id, item]));
diff --git a/src/page-components/Home/socket/index.tsx b/src/page-components/Home/socket/index.tsx
index fe74c377e..9cd3b97bb 100644
--- a/src/page-components/Home/socket/index.tsx
+++ b/src/page-components/Home/socket/index.tsx
@@ -1,9 +1,10 @@
import io from 'socket.io-client';
import { SOCKET_URL } from '../../../constants';
import { ISocketData } from '../types';
+import * as Sentry from '@sentry/nextjs';
export function initSocket(handleSocketData: any) {
- const socket = io(location.origin, {
+ const socket = io(window.location.origin, {
path: SOCKET_URL,
transports: ['websocket', 'polling'],
});
@@ -28,5 +29,9 @@ export function initSocket(handleSocketData: any) {
});
socket.emit('getBlocksList');
+ socket.on('connect_error', (error) => {
+ // interface 404
+ // Sentry.captureException(error);
+ });
return socket;
}
diff --git a/src/page-components/Proposal/Log/index.jsx b/src/page-components/Proposal/Log/index.jsx
index 142de4441..ee45d3a43 100644
--- a/src/page-components/Proposal/Log/index.jsx
+++ b/src/page-components/Proposal/Log/index.jsx
@@ -8,7 +8,7 @@ import { useSelector, useDispatch } from 'react-redux';
import { If, Then, Else } from 'react-if';
import { DownOutlined } from '@ant-design/icons';
import { Button, Dropdown, Menu } from 'antd';
-import { logOut, logIn } from 'redux/features/proposal/common.js';
+import { logOut, logIn } from 'redux/features/proposal/common';
import { LOG_STATUS } from 'constants';
import { isPhoneCheck } from 'utils/deviceCheck';
diff --git a/src/page-components/Proposal/common/constants.js b/src/page-components/Proposal/common/constants.js
index 40398537e..ab7e7927c 100644
--- a/src/page-components/Proposal/common/constants.js
+++ b/src/page-components/Proposal/common/constants.js
@@ -5,9 +5,7 @@
import PropTypes from 'prop-types';
import AElf from 'aelf-sdk';
import config from 'constants/viewerApi';
-
const { constants, viewer, wallet } = config;
-
export const FAKE_WALLET = AElf.wallet.getWalletByPrivateKey(wallet.privateKey);
export const API_PATH = {
@@ -93,18 +91,15 @@ export const PROPOSAL_STATUS_CAPITAL = {
[proposalStatus.EXPIRED]: 'Expired',
[proposalStatus.RELEASED]: 'Released',
};
-
+const RPCSERVER_HOST =
+ typeof window !== 'undefined' ? `${window.location.protocol}//${window.location.host}` : process.env.BUILD_ENDPOINT;
+const DEFAUT_RPCSERVER = process.env.NODE_ENV === 'production' ? `${RPCSERVER_HOST}/chain` : `${RPCSERVER_HOST}`;
export default {
...constants,
proposalStatus: {
...proposalStatus,
},
viewer,
- DEFAUT_RPCSERVER:
- typeof window !== 'undefined'
- ? process.env.NODE_ENV === 'production'
- ? `${window?.location?.protocol}//${window?.location?.host}/chain`
- : `${window?.location?.protocol}//${window?.location?.host}`
- : '',
+ DEFAUT_RPCSERVER,
APP_NAME: 'explorer.aelf.io',
};
diff --git a/src/page-components/Proposal/common/wallet.js b/src/page-components/Proposal/common/wallet.js
index 106a77b6e..c1210de06 100644
--- a/src/page-components/Proposal/common/wallet.js
+++ b/src/page-components/Proposal/common/wallet.js
@@ -49,5 +49,3 @@ export function walletInstanceSingle() {
}
return walletInstance;
}
-
-export default walletInstance;
diff --git a/src/page-components/Resource/Resource.js b/src/page-components/Resource/Resource.js
index 93ca0189f..a7a87aaf9 100644
--- a/src/page-components/Resource/Resource.js
+++ b/src/page-components/Resource/Resource.js
@@ -13,7 +13,7 @@ import { APPNAME, resourceTokens } from 'constants/config/config';
import DownloadPlugins from 'components/DownloadPlugins/DownloadPlugins';
import ResourceAElfWallet from './components/ResourceAElfWallet/ResourceAElfWallet';
import NightElfCheck from 'utils/NightElfCheck';
-import getContractAddress from 'utils/getContractAddress.js';
+import getContractAddress from 'utils/getContractAddress';
import ResourceMoneyMarket from './components/ResourceMoneyMarket/ResourceMoneyMarket';
import getLogin from 'utils/getLogin';
import { isPhoneCheck } from 'utils/deviceCheck';
@@ -73,7 +73,7 @@ class Resource extends Component {
}
});
} else {
- let wallet = JSON.parse(localStorage.getItem('currentWallet'));
+ const wallet = JSON.parse(localStorage.getItem('currentWallet'));
if (wallet && new Date().valueOf() - Number(wallet.timestamp) < 15 * 60 * 1000) {
nightElf.chain.getChainStatus().then((result) => {
this.loginAndInsertKeyPairs(result);
diff --git a/src/page-components/SearchFailed/SearchFailed.styles.less b/src/page-components/SearchFailed/SearchFailed.styles.less
index df4088285..99d3111c3 100644
--- a/src/page-components/SearchFailed/SearchFailed.styles.less
+++ b/src/page-components/SearchFailed/SearchFailed.styles.less
@@ -5,7 +5,7 @@
flex-direction: column;
align-items: center;
justify-content: center;
- padding-top: 0px !important;
+ padding-top: 0 !important;
min-height: calc(100vh - 108px);
padding-bottom: 108px;
img {
@@ -17,7 +17,7 @@
line-height: 32px;
color: @cloud-burst;
font-size: 24px;
- font-family: 'Roboto Bold';
+ font-family: 'Roboto Bold', sans-serif;
text-align: center;
margin-bottom: 16px;
}
@@ -30,7 +30,7 @@
color: @mariner;
.anticon {
color: @mariner;
- margin-right: 10px;
+ margin-right: 2px;
}
}
}
diff --git a/src/page-components/SearchFailed/SearchFailed.jsx b/src/page-components/SearchFailed/SearchFailed.tsx
similarity index 66%
rename from src/page-components/SearchFailed/SearchFailed.jsx
rename to src/page-components/SearchFailed/SearchFailed.tsx
index 22c05bd85..c98ec625c 100644
--- a/src/page-components/SearchFailed/SearchFailed.jsx
+++ b/src/page-components/SearchFailed/SearchFailed.tsx
@@ -2,6 +2,8 @@ import React from 'react';
import IconFont from 'components/IconFont';
import useMobile from 'hooks/useMobile';
import { withRouter } from 'next/router';
+import clsx from 'clsx';
+import { Button } from 'antd';
const banner = require('assets/images/search_invalid.png');
require('./SearchFailed.styles.less');
@@ -9,14 +11,14 @@ function SearchFailed(props) {
const isMobile = useMobile();
const { router } = props;
return (
-
-

+
);
}
diff --git a/src/page-components/SearchInvalid/SearchInvalid.jsx b/src/page-components/SearchInvalid/SearchInvalid.tsx
similarity index 85%
rename from src/page-components/SearchInvalid/SearchInvalid.jsx
rename to src/page-components/SearchInvalid/SearchInvalid.tsx
index 5c008079b..bea684376 100644
--- a/src/page-components/SearchInvalid/SearchInvalid.jsx
+++ b/src/page-components/SearchInvalid/SearchInvalid.tsx
@@ -10,7 +10,7 @@ require('./SearchInvalid.styles.less');
const banner = require('assets/images/search_invalid.png');
function SearchInvalid() {
- const { pathname, query } = useRouter();
+ const { asPath, query } = useRouter();
const { string } = query;
const isMobile = useMobile();
return (
@@ -19,12 +19,12 @@ function SearchInvalid() {
Search not found !
Oops! The search string you entered was:{isMobile ?
: ' '}
- {string || pathname.replace('/search-invalid/', '')}
+ {string || decodeURIComponent(asPath).replace('/search-invalid/', '')}
Sorry! This is an invalid search string.
-
+
Back Home
diff --git a/src/page-components/TokenDetail/components/Contract.tsx b/src/page-components/TokenDetail/components/Contract.tsx
new file mode 100644
index 000000000..37ad65edb
--- /dev/null
+++ b/src/page-components/TokenDetail/components/Contract.tsx
@@ -0,0 +1,26 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { VIEWER_GET_FILE } from 'constants/viewerApi';
+import { get } from 'utils/axios';
+import ContractViewer from '../../AddressDetail/components/Contract/Contract';
+import { useRouter } from 'next/router';
+
+export default function Contract({ address, headers }) {
+ const nav = useRouter().push;
+ const [contractInfo, setContractInfo] = useState(undefined);
+
+ const fetchFile = useCallback(async () => {
+ const result = await get(VIEWER_GET_FILE, { address });
+ if (result?.code === 0) {
+ const { data } = result;
+ setContractInfo(data);
+ } else {
+ nav('/search-failed');
+ }
+ }, [address]);
+
+ useEffect(() => {
+ fetchFile();
+ }, [fetchFile]);
+
+ return
;
+}
diff --git a/src/page-components/TokenDetail/components/Holders.tsx b/src/page-components/TokenDetail/components/Holders.tsx
new file mode 100644
index 000000000..87e1ae0d7
--- /dev/null
+++ b/src/page-components/TokenDetail/components/Holders.tsx
@@ -0,0 +1,133 @@
+import { Pagination, Table } from 'antd';
+import React, { useCallback, useMemo, useState } from 'react';
+import { useDebounce } from 'react-use';
+import useMobile from 'hooks/useMobile';
+import TableLayer from 'components/TableLayer/TableLayer';
+import AddressLink from 'components/AddressLink';
+import { get } from 'utils/axios';
+import { VIEWER_ACCOUNT_LIST } from 'constants/viewerApi';
+import { numberFormatter } from 'utils/formater';
+import { ColumnsType } from 'antd/es/table';
+import { useRouter } from 'next/router';
+interface IRecord {
+ title: string;
+ width: number;
+ dataIndex: string;
+ render?: (prop: any) => void;
+ ellipsis?: boolean;
+ align?: 'left' | 'right' | 'center';
+}
+export default function Holders() {
+ const isMobile = useMobile();
+ const router = useRouter();
+ const nav = router.push;
+ const { symbol } = router.query;
+ const [dataLoading, setDataLoading] = useState(true);
+ const [pageIndex, setPageIndex] = useState(1);
+ const [pageSize, setPageSize] = useState(50);
+ const [dataSource, setDataSource] = useState(undefined);
+ const [actualTotal, setActualTotal] = useState(0);
+
+ const preTotal = useMemo(() => pageSize * (pageIndex - 1), [pageIndex, pageSize]);
+
+ const columns = useMemo(() => {
+ const res: ColumnsType
= [
+ {
+ title: 'Rank',
+ width: isMobile ? 96 : 196,
+ dataIndex: 'id',
+ render(id, record, index) {
+ return preTotal + index + 1;
+ },
+ },
+ {
+ title: 'Address',
+ width: isMobile ? 216 : 280,
+ ellipsis: true,
+ dataIndex: 'owner',
+ render(address) {
+ return (
+
+ );
+ },
+ },
+ {
+ title: 'Balance',
+ width: isMobile ? 156 : 280,
+ dataIndex: 'balance',
+ render(balance) {
+ return `${numberFormatter(balance)} ${symbol}`;
+ },
+ },
+ {
+ title: 'Percentage',
+ width: isMobile ? 116 : 180,
+ dataIndex: 'percentage',
+ },
+ {
+ title: 'Transfer',
+ width: isMobile ? 82 : 152,
+ dataIndex: 'count',
+ align: 'right',
+ },
+ ];
+ return res;
+ }, [isMobile, preTotal]);
+
+ const fetch = useCallback(async () => {
+ setDataLoading(true);
+ setDataSource(undefined);
+ const result = await get(VIEWER_ACCOUNT_LIST, {
+ symbol,
+ pageSize,
+ pageNum: pageIndex,
+ });
+ setDataLoading(false);
+ if (result.code === 0) {
+ setDataSource(result.data.list);
+ setActualTotal(result.data.total);
+ } else {
+ nav('/search-failed');
+ }
+ }, [symbol, pageIndex, pageSize]);
+
+ const handlePageChange = useCallback(
+ (page, size) => {
+ setDataSource(undefined);
+ setDataLoading(true);
+ setPageIndex(size === pageSize ? page : 1);
+ setPageSize(size);
+ },
+ [pageSize],
+ );
+
+ useDebounce(
+ () => {
+ fetch();
+ },
+ 300,
+ [fetch],
+ );
+
+ return (
+
+
+
+
+
+
handlePageChange(1, size)}
+ />
+
+
+ );
+}
diff --git a/src/page-components/TokenDetail/components/Overview.tsx b/src/page-components/TokenDetail/components/Overview.tsx
new file mode 100644
index 000000000..5386a3b81
--- /dev/null
+++ b/src/page-components/TokenDetail/components/Overview.tsx
@@ -0,0 +1,62 @@
+import { Skeleton } from 'antd';
+import React, { useMemo } from 'react';
+import AddressLink from 'components/AddressLink';
+import CopyButton from 'components/CopyButton/CopyButton';
+import { numberFormatter } from 'utils/formater';
+import { useRouter } from 'next/router';
+import { IOverviewProps } from '../types';
+
+export default function Overview({ tokenInfo = {}, price = 0 }: IOverviewProps) {
+ const { symbol } = useRouter().query;
+ const overviewList = useMemo(() => {
+ const { totalSupply = 0, supply = 0, holders = 0, transfers = '0', contractAddress = '', decimals = 0 } = tokenInfo;
+ return [
+ { title: 'Price', value: `$${numberFormatter(price)}` },
+ {
+ title: 'Total Supply',
+ value: `${numberFormatter(totalSupply)} ${symbol}`,
+ },
+ {
+ title: 'Circulating Supply',
+ value: `${numberFormatter(supply)} ${symbol}`,
+ },
+ {
+ title: 'Holders',
+ value: Number(holders).toLocaleString(),
+ },
+ {
+ title: 'Transfers',
+ value: Number(transfers).toLocaleString(),
+ },
+ {
+ title: 'Contract',
+ value: (
+
+ ),
+ },
+ {
+ title: 'Decimals',
+ value: decimals,
+ },
+ ];
+ }, [price, tokenInfo]);
+ return (
+
+
Overview
+
+ {tokenInfo ? (
+ overviewList.map((item) => (
+
+ {item.title}
+ {item.value}
+
+ ))
+ ) : (
+
+ )}
+
+
+ );
+}
diff --git a/src/page-components/TokenDetail/components/Transactions.tsx b/src/page-components/TokenDetail/components/Transactions.tsx
new file mode 100644
index 000000000..a0e511679
--- /dev/null
+++ b/src/page-components/TokenDetail/components/Transactions.tsx
@@ -0,0 +1,82 @@
+import { Pagination } from 'antd';
+import React, { useCallback, useState, useEffect } from 'react';
+import { useDebounce } from 'react-use';
+import { VIEWER_TOKEN_TX_LIST } from 'constants/viewerApi';
+import TransactionTable from 'components/TransactionTable/TransactionTable';
+import { isPhoneCheck, isPhoneCheckSSR } from 'utils/deviceCheck';
+import { get } from 'utils/axios';
+import { useRouter } from 'next/router';
+
+export default function Transactions({ dataSource: dataSourceSSR, actualTotal: actualTotalSSR, headers }) {
+ const { symbol } = useRouter().query;
+ const [dataLoading, setDataLoading] = useState(false);
+ const [pageIndex, setPageIndex] = useState(1);
+ const [pageSize, setPageSize] = useState(50);
+ const [dataSource, setDataSource] = useState(dataSourceSSR);
+ const [actualTotal, setActualTotal] = useState(actualTotalSSR || 0);
+ const [isMobile, setIsMobile] = useState(!!isPhoneCheckSSR(headers));
+
+ useEffect(() => {
+ setIsMobile(!!isPhoneCheck());
+ }, []);
+
+ const fetchTransactions = useCallback(async () => {
+ const result = await get(VIEWER_TOKEN_TX_LIST, {
+ symbol,
+ pageSize,
+ pageNum: pageIndex,
+ });
+ if (result.code === 0) {
+ setDataSource(
+ result.data.list.map((item) => ({
+ ...item,
+ tx_id: item.txId,
+ block_height: item.blockHeight,
+ address_from: item.addressFrom,
+ address_to: item.addressTo,
+ tx_fee: JSON.stringify(item.txFee),
+ })),
+ );
+ setActualTotal(result.data.total);
+ } else {
+ setDataSource(undefined);
+ }
+ setDataLoading(false);
+ }, [symbol, pageIndex, pageSize]);
+
+ const handlePageChange = useCallback(
+ (page, size) => {
+ setDataSource(undefined);
+ setDataLoading(true);
+ setPageIndex(size === pageSize ? page : 1);
+ setPageSize(size);
+ },
+ [pageSize],
+ );
+
+ useDebounce(
+ () => {
+ fetchTransactions();
+ },
+ 300,
+ [fetchTransactions],
+ );
+
+ return (
+
+
+
+
handlePageChange(1, size)}
+ />
+
+
+ );
+}
diff --git a/src/page-components/TokenDetail/index.less b/src/page-components/TokenDetail/index.less
new file mode 100644
index 000000000..ec91cd4e8
--- /dev/null
+++ b/src/page-components/TokenDetail/index.less
@@ -0,0 +1,102 @@
+.token-page-container {
+ h2 {
+ line-height: 28px;
+ font-size: 18px;
+ font-family: 'Roboto-Bold', sans-serif;
+ color: @cloud-burst;
+ margin-bottom: 16px;
+ span {
+ color: @manatee;
+ margin-left: 8px;
+ font-family: 'Roboto-Medium', sans-serif;
+ }
+ }
+ .overview {
+ background: white;
+ border-radius: 8px;
+ margin-bottom: 24px;
+ > p {
+ font-family: 'Roboto-Medium', sans-serif;
+ padding: 13px 24px;
+ border-bottom: 1px solid @border-color2;
+ }
+ .content {
+ padding: 24px;
+ display: flex;
+ gap: 16px;
+ flex-direction: column;
+ .row {
+ display: flex;
+ gap: 16px;
+ .label {
+ display: inline-flex;
+ min-width: 300px;
+ color: @mine-shaft;
+ font-family: 'Roboto Medium', sans-serif;
+ }
+ .value {
+ display: inline-flex;
+ align-items: center;
+ .copy-btn {
+ margin-left: 8px;
+ }
+ }
+ }
+ }
+ }
+ .more-info {
+ margin-top: 24px;
+ background: white;
+ border-radius: 8px;
+ .ant-tabs-nav-wrap {
+ padding-left: 24px;
+ }
+ .transactions-pane,
+ .holders-pane {
+ background: #f7f8f9;
+ .table-layer {
+ padding-top: 0;
+ }
+ }
+ .holders-pane {
+ .address {
+ color: @mariner;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+ .contract-pane {
+ .reader {
+ min-width: 100%;
+ }
+ }
+ }
+}
+.token-page-container.mobile {
+ .overview {
+ > p {
+ padding-left: 16px;
+ }
+ .content {
+ padding: 24px 16px;
+ gap: 24px;
+ .row {
+ flex-direction: column;
+ gap: 6px;
+ .label {
+ min-width: none;
+ }
+ .value {
+ a {
+ display: inline;
+ }
+ }
+ }
+ }
+ }
+ .holders-pane {
+ .ant-table-wrapper {
+ min-width: 678px;
+ }
+ }
+}
diff --git a/src/page-components/TokenDetail/index.tsx b/src/page-components/TokenDetail/index.tsx
new file mode 100644
index 000000000..1fcb04b0f
--- /dev/null
+++ b/src/page-components/TokenDetail/index.tsx
@@ -0,0 +1,79 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { Tabs } from 'antd';
+import clsx from 'clsx';
+import { getTokenAllInfo } from 'utils/utils';
+import { TOKEN_PRICE } from 'constants/viewerApi';
+import { get } from 'utils/axios';
+import Overview from './components/Overview';
+import Transactions from './components/Transactions';
+import Holders from './components/Holders';
+import Contract from './components/Contract';
+import { isPhoneCheck, isPhoneCheckSSR } from 'utils/deviceCheck';
+import { useRouter } from 'next/router';
+import { useUpdateEffect } from 'react-use';
+
+require('./index.less');
+
+export default function Token({
+ tokeninfossr: tokenInfoSSR,
+ pricessr: priceSSR,
+ datasourcessr: dataSourceSSR,
+ actual_total_ssr: actualTotalSSR,
+ headers,
+}) {
+ const router = useRouter();
+ const { symbol } = router.query;
+ const nav = router.push;
+ const [tokenInfo, setTokenInfo] = useState(tokenInfoSSR);
+ const [price, setPrice] = useState(priceSSR || 0);
+ const [isMobile, setIsMobile] = useState(!!isPhoneCheckSSR(headers));
+
+ useEffect(() => {
+ setIsMobile(!!isPhoneCheck());
+ }, []);
+ const fetchTokenInfo = useCallback(async () => {
+ const result = await getTokenAllInfo(symbol).catch(() => {
+ nav('/search-failed');
+ });
+ if (result?.symbol === symbol) {
+ setTokenInfo(result);
+ } else {
+ nav(`/search-invalid/${symbol}`);
+ }
+ }, [symbol]);
+
+ const fetchPrice = useCallback(async () => {
+ const result = await get(TOKEN_PRICE, { fsym: symbol, tsyms: 'USD' });
+ if (result?.symbol === symbol) {
+ const { USD } = result;
+ setPrice(USD);
+ }
+ }, [symbol]);
+
+ useUpdateEffect(() => {
+ fetchTokenInfo();
+ fetchPrice();
+ }, [fetchPrice, fetchTokenInfo]);
+
+ return (
+
+
+ Token{symbol}
+
+
+
+
+ );
+}
diff --git a/src/page-components/TokenDetail/types.ts b/src/page-components/TokenDetail/types.ts
new file mode 100644
index 000000000..d2bab23bb
--- /dev/null
+++ b/src/page-components/TokenDetail/types.ts
@@ -0,0 +1,18 @@
+export interface ITokenInfo {
+ id?: number;
+ contractAddress?: string;
+ symbol?: string;
+ chainId?: string;
+ issueChainId?: string;
+ txId?: string;
+ name?: string;
+ totalSupply?: number;
+ supply?: number;
+ decimals?: number;
+ holders?: number;
+ transfers?: string;
+}
+export interface IOverviewProps {
+ tokenInfo: ITokenInfo;
+ price: number;
+}
diff --git a/src/page-components/Tokens/Tokens.tsx b/src/page-components/Tokens/Tokens.tsx
new file mode 100644
index 000000000..dc1e5d247
--- /dev/null
+++ b/src/page-components/Tokens/Tokens.tsx
@@ -0,0 +1,93 @@
+import React, { useCallback, useMemo, useState, useEffect } from 'react';
+import { Pagination, Table } from 'antd';
+import { useDebounce, useUpdateEffect } from 'react-use';
+import clsx from 'clsx';
+import TableLayer from 'components/TableLayer/TableLayer';
+import getColumnConfig from './columnConfig';
+import { get } from 'utils/axios';
+import { VIEWER_GET_ALL_TOKENS } from 'constants/api';
+import { isPhoneCheck, isPhoneCheckSSR } from 'utils/deviceCheck';
+
+require('./index.less');
+
+export default function Tokens({ actualtotalssr: actualTotalSSR, datasourcessr: dataSourceSSR, headers }) {
+ const [isMobile, setIsMobile] = useState(!!isPhoneCheckSSR(headers));
+ const [dataLoading, setDataLoading] = useState(false);
+ const [pageIndex, setPageIndex] = useState(1);
+ const [pageSize, setPageSize] = useState(50);
+ const [dataSource, setDataSource] = useState(dataSourceSSR);
+ const [actualTotal, setActualTotal] = useState(actualTotalSSR || 0);
+
+ const preTotal = useMemo(() => pageSize * (pageIndex - 1), [pageSize, pageIndex]);
+
+ const columns = useMemo(() => getColumnConfig(isMobile, preTotal), [isMobile, preTotal]);
+
+ const fetchData = useCallback(async () => {
+ const result = await get(VIEWER_GET_ALL_TOKENS, {
+ pageSize,
+ pageNum: pageIndex,
+ });
+ if (result.code === 0) {
+ const { data } = result;
+ setActualTotal(data.total);
+ setDataSource(data.list);
+ } else {
+ setDataSource(undefined);
+ }
+ setDataLoading(false);
+ }, [pageIndex, pageSize]);
+
+ const handlePageChange = useCallback(
+ (page, size) => {
+ setDataSource(undefined);
+ setDataLoading(true);
+ setPageIndex(size === pageSize ? page : 1);
+ setPageSize(size);
+ },
+ [pageSize],
+ );
+
+ useEffect(() => {
+ setIsMobile(!!isPhoneCheck());
+ }, []);
+
+ useUpdateEffect(() => {
+ fetchData();
+ }, [pageIndex, pageSize]);
+
+ return (
+
+
Tokens
+
+
+
A total of {Number(actualTotal).toLocaleString()} Token Contracts found
+
+
+
+
+
+
+
+
handlePageChange(1, size)}
+ />
+
+
+ );
+}
diff --git a/src/page-components/Tokens/columnConfig.tsx b/src/page-components/Tokens/columnConfig.tsx
new file mode 100644
index 000000000..cebff702b
--- /dev/null
+++ b/src/page-components/Tokens/columnConfig.tsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import Link from 'next/link';
+import { numberFormatter } from 'utils/formater';
+import { ColumnsType } from 'antd/lib/table';
+import { AlignType } from 'rc-table/lib/interface';
+interface IRecord {
+ title: string;
+ dataIndex: string;
+ width: number;
+ render: (prop: any) => void;
+ symbol?: string;
+ align?: AlignType;
+}
+const getColumnConfig = (isMobile, preTotal) => {
+ const columnConfig: ColumnsType = [
+ {
+ title: 'Rank',
+ dataIndex: 'id',
+ width: isMobile ? 82 : 196,
+ render(id, record, index) {
+ return preTotal + index + 1;
+ },
+ },
+ {
+ title: 'Token Name',
+ dataIndex: 'symbol',
+ width: isMobile ? 96 : 180,
+ render(symbol) {
+ return {symbol};
+ },
+ },
+ {
+ title: 'Total Supply',
+ dataIndex: 'totalSupply',
+ width: isMobile ? 156 : 230,
+ render(totalSupply, record) {
+ return `${numberFormatter(totalSupply)} ${record.symbol}`;
+ },
+ },
+ {
+ title: 'Circulating Supply',
+ dataIndex: 'supply',
+ width: isMobile ? 156 : 260,
+ render(supply, record) {
+ return `${numberFormatter(supply)} ${record.symbol}`;
+ },
+ },
+ {
+ title: 'Holders',
+ dataIndex: 'holders',
+ width: isMobile ? 76 : 120,
+ },
+ {
+ title: 'Transfers',
+ align: 'right',
+ dataIndex: 'transfers',
+ width: isMobile ? 76 : 86,
+ },
+ ];
+ return columnConfig;
+};
+
+export default getColumnConfig;
diff --git a/src/page-components/Tokens/index.less b/src/page-components/Tokens/index.less
new file mode 100644
index 000000000..b726c0fb0
--- /dev/null
+++ b/src/page-components/Tokens/index.less
@@ -0,0 +1,12 @@
+.tokens-page-container {
+ h2 {
+ font-size: 18px;
+ font-family: 'Roboto-Bold', sans-serif;
+ margin-bottom: 16px;
+ }
+}
+.tokens-page-container.mobile {
+ .ant-table-wrapper {
+ min-width: 648px;
+ }
+}
diff --git a/src/page-components/Txs/TransactionList.style.less b/src/page-components/Txs/TransactionList.style.less
index 4076b6153..e7da8cf71 100644
--- a/src/page-components/Txs/TransactionList.style.less
+++ b/src/page-components/Txs/TransactionList.style.less
@@ -3,95 +3,14 @@
padding-top: 24px;
h2 {
font-size: 18px;
- font-family: 'Roboto Bold';
+ font-family: 'Roboto Bold', sans-serif;
margin-bottom: 16px;
color: @cloud-burst;
}
.before-table {
- display: flex;
- justify-content: space-between;
- margin-bottom: 16px;
- .left {
- display: flex;
- flex-direction: column;
- row-gap: 2px;
- p {
- font-family: 'Roboto';
- height: 22px;
- line-height: 22px;
- color: @scorpion;
- font-size: 14px;
- }
- }
.right {
padding-top: 8px;
}
}
- .after-table {
- margin-top: 16px;
- .ant-pagination-options {
- position: absolute;
- border: none;
- left: 0;
- margin-left: 0;
- &::before,
- &::after {
- font-size: 14px;
- margin-right: 8px;
- line-height: 32px;
- }
- &::before {
- content: 'Show';
- }
- &::after {
- content: 'records';
- }
- .ant-select-selector {
- padding: 0 23px 0 9px;
- .ant-select-selection-item {
- padding-right: 5px;
- }
- }
- }
- }
-}
-.txs-page-container.mobile {
- .before-table {
- display: flex;
- flex-direction: column;
- margin-bottom: 16px;
- .right {
- display: flex;
- padding-top: 16px;
- }
- }
- .transaction-table {
- background: white !important;
- border-radius: 4px;
- }
- .after-table {
- display: flex;
- .ant-pagination {
- height: 76px;
- width: 100%;
- justify-content: flex-start;
- }
- .ant-pagination-options {
- display: flex;
- border: none;
- align-items: center;
- top: 0;
- &::before {
- content: 'Show';
- font-size: 14px;
- margin-right: 8px;
- }
- &::after {
- content: 'records';
- font-size: 14px;
- margin-left: 8px;
- }
- }
- }
}
diff --git a/src/page-components/Txs/TransactionList.jsx b/src/page-components/Txs/TransactionList.tsx
similarity index 59%
rename from src/page-components/Txs/TransactionList.jsx
rename to src/page-components/Txs/TransactionList.tsx
index 4be5668b8..88464efbc 100644
--- a/src/page-components/Txs/TransactionList.jsx
+++ b/src/page-components/Txs/TransactionList.tsx
@@ -1,38 +1,45 @@
import { Pagination } from 'antd';
import React, { useEffect, useState, useMemo, useCallback } from 'react';
-import { ALL_TXS_API_URL, ALL_UNCONFIRMED_TXS_API_URL, TXS_BLOCK_API_URL } from 'constants/api';
+import { ALL_TXS_API_URL, ALL_UNCONFIRMED_TXS_API_URL } from 'constants/api';
import { getContractNames } from 'utils/utils';
import { get } from 'utils/axios';
-import { useDebounce } from 'react-use';
-import useMobile from 'hooks/useMobile';
+import { useDebounce, useUpdateEffect } from 'react-use';
+import { isPhoneCheck, isPhoneCheckSSR } from 'utils/deviceCheck';
import TransactionTable from 'components/TransactionTable/TransactionTable';
import { useRouter } from 'next/router';
+import { IProps, ITxs } from './types';
require('./TransactionList.style.less');
-export default function TransactionList() {
+export default function TransactionList({ actualtotalssr = 0, datasourcessr, headers }: IProps) {
const router = useRouter();
- const { pathname = '' } = router;
- const search = router.asPath.indexOf('?') !== -1 ? router.asPath.substring(router.asPath.indexOf('?')) : undefined;
- const isMobile = useMobile();
+ const { pathname = '', asPath } = router;
+ const search = asPath.indexOf('?') !== -1 ? asPath.substring(asPath.indexOf('?')) : undefined;
+ const [isMobile, setIsMobile] = useState(!!isPhoneCheckSSR(headers));
+
+ useEffect(() => {
+ if (search) {
+ router.push(`/block/${search.slice(1)}?tab=txns`);
+ }
+ setIsMobile(!!isPhoneCheck());
+ }, []);
const [dataLoading, setDataLoading] = useState(false);
const [pageIndex, setPageIndex] = useState(1);
const [pageSize, setPageSize] = useState(50);
- const [dataSource, setDataSource] = useState(undefined);
- const [actualTotal, setActualTotal] = useState(0);
+ const [dataSource, setDataSource] = useState(datasourcessr);
+ const [actualTotal, setActualTotal] = useState(actualtotalssr);
const total = useMemo(() => {
if (actualTotal > 500000) return 500000;
return actualTotal;
- });
+ }, [actualTotal]);
const isUnconfirmed = useMemo(() => pathname.includes('unconfirmed'), [pathname]);
-
const pageTitle = useMemo(() => {
return isUnconfirmed ? 'Unconfirmed Transactions' : 'Transactions';
}, [isUnconfirmed]);
- const merge = (data = {}, contractNames) => {
+ const merge = (data: ITxs = {}, contractNames) => {
const { transactions = [] } = data;
return (transactions || []).map((item) => ({
...item,
@@ -42,53 +49,51 @@ export default function TransactionList() {
const fetch = useCallback(
async (page = 1) => {
- setDataSource(undefined);
- setDataLoading(true);
- let url;
- if (search) {
- url = TXS_BLOCK_API_URL;
- } else {
- url = pathname.indexOf('unconfirmed') === -1 ? ALL_TXS_API_URL : ALL_UNCONFIRMED_TXS_API_URL;
- }
- const data = await get(url, {
- order: 'desc',
- page: page - 1,
- limit: pageSize,
- block_hash: (search && search.slice(1)) || undefined,
- });
- const contractNames = await getContractNames();
+ try {
+ const url = pathname.indexOf('unconfirmed') === -1 ? ALL_TXS_API_URL : ALL_UNCONFIRMED_TXS_API_URL;
+ const data = await get(url, {
+ order: 'desc',
+ page: page - 1,
+ limit: pageSize,
+ block_hash: (search && search.slice(1)) || undefined,
+ });
+ const contractNames = await getContractNames();
- setActualTotal(data ? data.total || data.transactions.length : 0);
- const transactions = merge(data, contractNames);
+ setActualTotal(data ? data.total || data.transactions.length : 0);
+ const transactions = merge(data, contractNames);
+ setDataSource(transactions);
+ } catch {
+ setDataSource([]);
+ }
setDataLoading(false);
- setDataSource(transactions);
},
- [pathname, get, getContractNames, merge, pageSize],
+ [pathname, pageSize],
);
const handlePageChange = useCallback(
(page, size) => {
+ setDataLoading(true);
setPageIndex(size === pageSize ? page : 1);
setPageSize(size);
},
[pageSize],
);
- useEffect(() => {
+ useUpdateEffect(() => {
if (pageIndex === 1) {
+ setDataLoading(true);
+ setDataSource([]);
fetch(pageIndex);
} else {
+ setDataLoading(true);
+ setDataSource([]);
setPageIndex(1);
}
}, [pathname]);
- useDebounce(
- () => {
- fetch(pageIndex);
- },
- 300,
- [pageIndex, pageSize],
- );
+ useUpdateEffect(() => {
+ fetch(pageIndex);
+ }, [pageIndex, pageSize]);
return (
@@ -112,7 +117,7 @@ export default function TransactionList() {
/>
-
+
handlePageChange(1, size)}
+ onShowSizeChange={(_, size) => handlePageChange(1, size)}
/>
diff --git a/src/page-components/Txs/types.ts b/src/page-components/Txs/types.ts
new file mode 100644
index 000000000..f248539fa
--- /dev/null
+++ b/src/page-components/Txs/types.ts
@@ -0,0 +1,36 @@
+interface ITx {
+ id: number;
+ tx_id: string;
+ params_to: string;
+ chain_id: string;
+ block_height: number;
+ address_from: string;
+ address_to: string;
+ params: string;
+ method: string;
+ block_hash: string;
+ tx_fee: string;
+ resources: string;
+ quantity: number;
+ tx_status: 'MINED' | 'FAILED' | 'PENDING';
+ time: string;
+ contractName?: string;
+}
+export interface ITxs {
+ transactions?: ITx[];
+ total?: number;
+}
+interface IContractName {
+ isSystemContract: boolean;
+ address: string;
+ contractName: string;
+}
+export interface IAllContractName {
+ list?: IContractName[];
+ total?: number;
+}
+export interface IProps {
+ actualtotalssr: number;
+ datasourcessr: ITx[];
+ headers: any;
+}
diff --git a/src/page-components/TxsDetail/TransactionDetail.styles.less b/src/page-components/TxsDetail/TransactionDetail.styles.less
index 9dfb34039..311a36806 100644
--- a/src/page-components/TxsDetail/TransactionDetail.styles.less
+++ b/src/page-components/TxsDetail/TransactionDetail.styles.less
@@ -2,9 +2,8 @@
.tx-block-detail-container {
h2 {
- font-family: 'Roboto Bold';
+ font-family: 'Roboto Bold', sans-serif;
color: @cloud-burst;
- font-weight: 400;
font-size: 18px;
}
.ant-tag {
@@ -39,7 +38,7 @@
display: flex;
flex-direction: column;
row-gap: 16px;
- padding: 8px 140px 64px;
+ padding: 16px 140px 64px;
.wrap {
display: flex;
flex-direction: column;
@@ -58,7 +57,7 @@
min-width: 206px;
text-align: right;
color: @mine-shaft;
- font-family: 'Roboto-Medium';
+ font-family: 'Roboto-Medium', sans-serif;
font-size: 14px;
}
.value {
@@ -113,7 +112,7 @@
}
}
.logs-container {
- padding: 8px 140px 80px;
+ padding: 16px 140px 80px;
.event-list {
display: flex;
flex-direction: column;
@@ -138,7 +137,7 @@
text-align: right;
display: block;
min-width: 66px;
- font-family: 'Roboto-Medium';
+ font-family: 'Roboto-Medium', sans-serif;
font-size: 14px;
line-height: 22px;
height: 22px;
@@ -169,7 +168,7 @@
.tx-block-detail-container.mobile {
padding-bottom: 32px !important;
.overview-container {
- padding: 10px 16px;
+ padding: 18px 16px;
.wrap {
row-gap: 24px;
.row {
diff --git a/src/page-components/TxsDetail/TransactionDetail.jsx b/src/page-components/TxsDetail/TransactionDetail.tsx
similarity index 75%
rename from src/page-components/TxsDetail/TransactionDetail.jsx
rename to src/page-components/TxsDetail/TransactionDetail.tsx
index 645051716..e4acb2858 100644
--- a/src/page-components/TxsDetail/TransactionDetail.jsx
+++ b/src/page-components/TxsDetail/TransactionDetail.tsx
@@ -2,30 +2,34 @@ import { Button, Tabs } from 'antd';
import React, { useMemo, useState } from 'react';
import { aelf } from 'utils/axios';
import { getContractNames, deserializeLog, getFee, removeAElfPrefix } from '../../utils/utils';
-
-const { TabPane } = Tabs;
-
-require('./TransactionDetail.styles.less');
+import { ILog } from './types';
import Events from 'components/Events';
import { useEffect } from 'react';
import ExtensionInfo from './components/ExtensionInfo';
import BasicInfo from './components/BasicInfo';
import CodeBlock from 'components/CodeBlock/CodeBlock';
import IconFont from 'components/IconFont';
-import useMobile from 'hooks/useMobile';
+import { isPhoneCheck, isPhoneCheckSSR } from 'utils/deviceCheck';
import { useCallback } from 'react';
import CustomSkeleton from 'components/CustomSkeleton/CustomSkeleton';
import { withRouter } from 'next/router';
+import { useUpdateEffect } from 'react-use';
+require('./TransactionDetail.styles.less');
+const { TabPane } = Tabs;
function TransactionDetail(props) {
const { id } = props.router.query;
- const [lastHeight, setLastHeight] = useState(undefined);
- const [info, setInfo] = useState(undefined);
- const [contractName, setContractName] = useState('');
- const [parsedLogs, setParsedLogs] = useState([]);
+ const [lastHeight, setLastHeight] = useState(props.lastheightssr);
+ const [info, setInfo] = useState(props.infossr);
+ const [contractName, setContractName] = useState(props.contractnamessr || '');
+ const [parsedLogs, setParsedLogs] = useState(props.parsedlogsssr || []);
const [showExtensionInfo, setShowExtensionInfo] = useState(false);
const [activeKey, setActiveKey] = useState('overview');
- const isMobile = useMobile();
+ const [isMobile, setIsMobile] = useState(!!isPhoneCheckSSR(props.headers));
+
+ useEffect(() => {
+ setIsMobile(!!isPhoneCheck());
+ }, []);
const hasLogs = useMemo(() => {
if (info) {
@@ -40,16 +44,15 @@ function TransactionDetail(props) {
return false;
}, [info]);
- useEffect(() => {
+ useUpdateEffect(() => {
setShowExtensionInfo(false);
setActiveKey('overview');
- setInfo(undefined);
aelf.chain
.getChainStatus()
.then(({ LastIrreversibleBlockHeight }) => {
setLastHeight(LastIrreversibleBlockHeight);
})
- .catch((error) => {
+ .catch((_) => {
location.href = '/search-failed';
});
aelf.chain
@@ -61,25 +64,28 @@ function TransactionDetail(props) {
getData(res);
}
})
- .catch((e) => {
- getData(e);
+ .catch((_) => {
+ location.href = '/search-failed';
});
}, [id]);
- useEffect(() => {
- const { Logs = [] } = info || {};
- const logs = [...parsedLogs];
- if (Logs.length) {
- const arr = Logs.filter((item) => item.Name === 'Transferred');
- arr.forEach((item, index) => {
- deserializeLog(item).then((res) => {
- logs.push({ ...res, key: arr[index].Name + arr[index].Address });
- setParsedLogs([...logs]);
- });
- });
- } else {
- setParsedLogs([]);
- }
+ useUpdateEffect(() => {
+ // make useEffect async
+ const changeParsedLogs = async () => {
+ const { Logs = [] } = info || {};
+ const logs: ILog[] = [];
+ if (Logs.length) {
+ const arr = Logs.filter((item) => item.Name === 'Transferred');
+ for (const item of arr) {
+ const res = await deserializeLog(item);
+ logs.push({ ...res, key: item.Name + item.Address });
+ }
+ setParsedLogs([...logs]);
+ } else {
+ setParsedLogs([]);
+ }
+ };
+ changeParsedLogs();
}, [info]);
const logIsAllParsed = useMemo(() => {
@@ -110,12 +116,16 @@ function TransactionDetail(props) {
const name = isSystemContract ? removeAElfPrefix(contractName) : contractName;
setContractName(name || res.Transaction.To);
})
- .catch((error) => {
+ .catch((_) => {
location.href = '/search-failed';
});
- getInfoBackUp(res).then((backup) => {
- setInfo({ ...res, ...backup });
- });
+ getInfoBackUp(res)
+ .then((backup) => {
+ setInfo({ ...res, ...backup });
+ })
+ .catch((_) => {
+ setInfo(undefined);
+ });
},
[getContractNames, getInfoBackUp],
);
@@ -135,6 +145,8 @@ function TransactionDetail(props) {
isDone={logIsAllParsed}
lastHeight={lastHeight}
contractName={contractName}
+ tokenPrice={props.tokenpricessr}
+ decimals={props.decimalsssr}
/>