From d90086732d21d20ba535a39997ea30591cb3dba0 Mon Sep 17 00:00:00 2001 From: Tuan Nguyen Date: Wed, 22 Mar 2023 16:29:53 +0700 Subject: [PATCH 01/12] Add balance history component --- public/locales/en/translation.json | 18 +- src/components/SearchBar/SearchBar.helpers.ts | 33 ++-- src/components/SearchBar/SearchBar.tsx | 47 ++++- src/components/Summary/LineChart.tsx | 35 +++- src/components/SvgIcon/Balance.tsx | 169 ++++++++++++++++++ src/components/SvgIcon/Received.tsx | 69 +++++++ src/components/SvgIcon/Sent.tsx | 68 +++++++ src/hooks/useAddressDetails.ts | 55 ++++-- .../AddressDetails/AddressDetails.helpers.tsx | 25 +++ .../AddressDetails/AddressDetails.styles.ts | 146 ++++++++++++++- .../Details/AddressDetails/AddressDetails.tsx | 74 ++++---- .../Details/AddressDetails/BalanceHistory.tsx | 107 +++++++++++ src/pages/Details/AddressDetails/Summary.tsx | 69 +++++++ src/utils/constants/types.ts | 2 + src/utils/constants/urls.ts | 2 + src/utils/helpers/chartOptions.ts | 112 +++++++++++- src/utils/helpers/statisticsLib.ts | 18 +- 17 files changed, 974 insertions(+), 75 deletions(-) create mode 100644 src/components/SvgIcon/Balance.tsx create mode 100644 src/components/SvgIcon/Received.tsx create mode 100644 src/components/SvgIcon/Sent.tsx create mode 100644 src/pages/Details/AddressDetails/BalanceHistory.tsx create mode 100644 src/pages/Details/AddressDetails/Summary.tsx diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index e3b37492..e9aab75b 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -1100,7 +1100,7 @@ "description": "Used for the CascadeAndSenseStatistics page" }, "latestTransactions": { - "message": "Latest Transactions", + "message": "Transactions", "description": "Used for the CascadeAndSenseStatistics page" }, "downloadCSV": { @@ -1108,8 +1108,22 @@ "description": "Used for the CascadeAndSenseStatistics page" }, "address": { - "message": "{{currency}} address", + "message": "{{currency}} Address", "description": "Used for the CascadeAndSenseStatistics page" + }, + "balanceHistory": { + "balance": { + "message": "Balance", + "description": "Used for the CascadeAndSenseStatistics page" + }, + "sent": { + "message": "Sent", + "description": "Used for the CascadeAndSenseStatistics page" + }, + "received": { + "message": "Received", + "description": "Used for the CascadeAndSenseStatistics page" + } } }, "blockDetails": { diff --git a/src/components/SearchBar/SearchBar.helpers.ts b/src/components/SearchBar/SearchBar.helpers.ts index d11b0929..28aa56c5 100644 --- a/src/components/SearchBar/SearchBar.helpers.ts +++ b/src/components/SearchBar/SearchBar.helpers.ts @@ -1,12 +1,20 @@ import * as ROUTES from '@utils/constants/routes'; -export const ADDRESSES_LABEL = 'components.searchBar.addresses'; -export const BLOCKS_HEIGHTS_LABEL = 'components.searchBar.blocksHeights'; -export const BLOCKS_IDS_LABEL = 'components.searchBar.blocksIds'; -export const TRANSACTIONS_LABEL = 'components.searchBar.transactions'; -export const SENSES_LABEL = 'components.searchBar.senses'; -export const PASTEL_ID_LABEL = 'components.searchBar.pastelID'; -export const USERNAME = 'components.searchBar.username'; +export const ADDRESSES_TEXT_LABEL = 'components.searchBar.addresses'; +export const BLOCKS_HEIGHTS_TEXT_LABEL = 'components.searchBar.blocksHeights'; +export const BLOCKS_IDS_TEXT_LABEL = 'components.searchBar.blocksIds'; +export const TRANSACTIONS_TEXT_LABEL = 'components.searchBar.transactions'; +export const SENSES_TEXT_LABEL = 'components.searchBar.senses'; +export const PASTEL_ID_TEXT_LABEL = 'components.searchBar.pastelID'; +export const USERNAME_TEXT_LABEL = 'components.searchBar.username'; + +export const ADDRESSES_LABEL = 'addresses'; +export const BLOCKS_HEIGHTS_LABEL = 'blocksHeights'; +export const BLOCKS_IDS_LABEL = 'blocksIds'; +export const TRANSACTIONS_LABEL = 'transactions'; +export const SENSES_LABEL = 'senses'; +export const PASTEL_ID_LABEL = 'pastelID'; +export const USERNAME = 'username'; export type TOptionsCategories = | typeof ADDRESSES_LABEL @@ -20,19 +28,25 @@ export type TOptionsCategories = export type TAutocompleteOptions = { category: TOptionsCategories; value: string; + categoryText: string; pastelID: string; }; -export const collectData = (data: Array, category: TOptionsCategories) => - data?.map((item: string | number) => ({ value: `${item}`, category })); +export const collectData = ( + data: Array, + category: TOptionsCategories, + categoryText: TOptionsCategories, +) => data?.map((item: string | number) => ({ value: `${item}`, category, categoryText })); export const collectUsernameData = ( data: Array<{ pastelID: string; username: string }>, category: TOptionsCategories, + categoryText: TOptionsCategories, ) => data?.map((item: { pastelID: string; username: string }) => ({ value: `${item.username}`, category, + categoryText, pastelID: item.pastelID, })); @@ -46,6 +60,5 @@ export const getRoute = (optionType: TOptionsCategories) => { [PASTEL_ID_LABEL]: ROUTES.PASTEL_ID_DETAILS, [USERNAME]: ROUTES.PASTEL_ID_DETAILS, }; - return routeTypes[optionType] || ROUTES.NOT_FOUND; }; diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx index 2d10f22d..81a0890a 100644 --- a/src/components/SearchBar/SearchBar.tsx +++ b/src/components/SearchBar/SearchBar.tsx @@ -19,6 +19,13 @@ import { translate } from '@utils/helpers/i18n'; import SwitchMode from './SwitchMode'; import * as Styles from './SearchBar.styles'; import { + ADDRESSES_TEXT_LABEL, + BLOCKS_IDS_TEXT_LABEL, + TRANSACTIONS_TEXT_LABEL, + BLOCKS_HEIGHTS_TEXT_LABEL, + SENSES_TEXT_LABEL, + PASTEL_ID_TEXT_LABEL, + USERNAME_TEXT_LABEL, ADDRESSES_LABEL, BLOCKS_IDS_LABEL, TRANSACTIONS_LABEL, @@ -113,13 +120,37 @@ const SearchBar: React.FC = ({ onDrawerToggle }) => { if (!data) return []; const groupedData = [ - ...collectData(data.address, translate(ADDRESSES_LABEL) as TOptionsCategories), - ...collectData(data.blocksIds, translate(BLOCKS_IDS_LABEL) as TOptionsCategories), - ...collectData(data.blocksHeights, translate(BLOCKS_HEIGHTS_LABEL) as TOptionsCategories), - ...collectData(data.transactions, translate(TRANSACTIONS_LABEL) as TOptionsCategories), - ...collectData(data.senses, translate(SENSES_LABEL) as TOptionsCategories), - ...collectData(data.pastelIds, translate(PASTEL_ID_LABEL) as TOptionsCategories), - ...collectUsernameData(data.usernameList, translate(USERNAME) as TOptionsCategories), + ...collectData( + data.address, + ADDRESSES_LABEL, + translate(ADDRESSES_TEXT_LABEL) as TOptionsCategories, + ), + ...collectData( + data.blocksIds, + BLOCKS_IDS_LABEL, + translate(BLOCKS_IDS_TEXT_LABEL) as TOptionsCategories, + ), + ...collectData( + data.blocksHeights, + BLOCKS_HEIGHTS_LABEL, + translate(BLOCKS_HEIGHTS_TEXT_LABEL) as TOptionsCategories, + ), + ...collectData( + data.transactions, + TRANSACTIONS_LABEL, + translate(TRANSACTIONS_TEXT_LABEL) as TOptionsCategories, + ), + ...collectData(data.senses, SENSES_LABEL, translate(SENSES_TEXT_LABEL) as TOptionsCategories), + ...collectData( + data.pastelIds, + PASTEL_ID_LABEL, + translate(PASTEL_ID_TEXT_LABEL) as TOptionsCategories, + ), + ...collectUsernameData( + data.usernameList, + USERNAME, + translate(USERNAME_TEXT_LABEL) as TOptionsCategories, + ), ]; return setSearchData(groupedData.sort((a, b) => -b.category.localeCompare(a.category))); @@ -166,7 +197,7 @@ const SearchBar: React.FC = ({ onDrawerToggle }) => { paper: classes.listboxOptions, }} filterOptions={filterOptions} - groupBy={option => (option as TAutocompleteOptions).category} + groupBy={option => (option as TAutocompleteOptions).categoryText} getOptionLabel={option => `${(option as TAutocompleteOptions).value}`} loading={loading} onInputChange={handleInputChange} diff --git a/src/components/Summary/LineChart.tsx b/src/components/Summary/LineChart.tsx index 53049d38..dbcc9018 100644 --- a/src/components/Summary/LineChart.tsx +++ b/src/components/Summary/LineChart.tsx @@ -7,7 +7,8 @@ import { TThemeInitOption, TThemeColor } from '@utils/constants/types'; import { getSummaryThemeUpdateOption } from '@utils/helpers/chartOptions'; import { getThemeState } from '@redux/reducers/appThemeReducer'; import { themes } from '@utils/constants/statistics'; -import { generateMinMaxChartData } from '@utils/helpers/statisticsLib'; +import { generateMinMaxChartData, PeriodTypes } from '@utils/helpers/statisticsLib'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import { getRouteForChart } from './Summary.helpers'; import * as Styles from './Summary.styles'; @@ -20,10 +21,26 @@ type TLineChartProps = { dataY2?: number[]; offset: number; disableClick?: boolean; + className?: string; + period?: PeriodTypes; + seriesName?: string; + chartColor?: string; }; export const LineChart = (props: TLineChartProps): JSX.Element | null => { - const { chartName, dataX, dataY, offset, dataY1, dataY2 } = props; + const { width } = useWindowDimensions(); + const { + className, + chartName, + dataX, + dataY, + offset, + dataY1, + dataY2, + period, + seriesName, + chartColor, + } = props; const { darkMode } = useSelector(getThemeState); const history = useHistory(); const [currentTheme, setCurrentTheme] = useState(null); @@ -74,6 +91,10 @@ export const LineChart = (props: TLineChartProps): JSX.Element | null => { const result = generateMinMaxChartData(min, max, offset, 5); setMinY(result.min); setMaxY(result.max); + } else if (['balanceHistory'].includes(chartName)) { + const result = generateMinMaxChartData(min, max, offset, 5, period); + setMinY(result.min); + setMaxY(result.max); } else { setMinY(Math.round(min) - offset); setMaxY(Math.floor(max) + offset); @@ -91,6 +112,10 @@ export const LineChart = (props: TLineChartProps): JSX.Element | null => { minY, maxY, darkMode, + period, + width, + seriesName, + chartColor, }; const options = getSummaryThemeUpdateOption(params); @@ -101,7 +126,7 @@ export const LineChart = (props: TLineChartProps): JSX.Element | null => { }; return ( - + ); @@ -112,5 +137,9 @@ LineChart.defaultProps = { dataY: undefined, dataY1: undefined, dataY2: undefined, + className: undefined, + period: undefined, + seriesName: undefined, + chartColor: undefined, disableClick: false, }; diff --git a/src/components/SvgIcon/Balance.tsx b/src/components/SvgIcon/Balance.tsx new file mode 100644 index 00000000..17e164bb --- /dev/null +++ b/src/components/SvgIcon/Balance.tsx @@ -0,0 +1,169 @@ +const Balance = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Balance; diff --git a/src/components/SvgIcon/Received.tsx b/src/components/SvgIcon/Received.tsx new file mode 100644 index 00000000..ecf83840 --- /dev/null +++ b/src/components/SvgIcon/Received.tsx @@ -0,0 +1,69 @@ +const Received = () => { + return ( + + + + + + + + + + + + + ); +}; + +export default Received; diff --git a/src/components/SvgIcon/Sent.tsx b/src/components/SvgIcon/Sent.tsx new file mode 100644 index 00000000..89a2c9f3 --- /dev/null +++ b/src/components/SvgIcon/Sent.tsx @@ -0,0 +1,68 @@ +const Sent = () => { + return ( + + + + + + + + + + + + ); +}; + +export default Sent; diff --git a/src/hooks/useAddressDetails.ts b/src/hooks/useAddressDetails.ts index 71f835d8..72ff15f6 100644 --- a/src/hooks/useAddressDetails.ts +++ b/src/hooks/useAddressDetails.ts @@ -1,17 +1,50 @@ import useSWRInfinite from 'swr/infinite'; +import useSWR from 'swr'; +import { TChartStatisticsResponse } from '@utils/types/IStatistics'; import { SWR_OPTIONS } from '@utils/constants/statistics'; import { axiosGet } from '@utils/helpers/useFetch/useFetch'; import * as URLS from '@utils/constants/urls'; import { IAddress } from '@utils/types/IAddress'; import { formattedDate } from '@utils/helpers/date/date'; import { SortDirectionsType } from '@components/InfinityTable/InfinityTable'; -import { - DATA_FETCH_LIMIT, - DEFAULT_ADDRESS_DATA, -} from '@pages/Details/AddressDetails/AddressDetails.helpers'; +import { DATA_FETCH_LIMIT } from '@pages/Details/AddressDetails/AddressDetails.helpers'; -export default function useAddressDetails( +interface IAddressDetails { + outgoingSum: number; + incomingSum: number; +} + +export default function useAddressDetails(id: string) { + const { data, isLoading } = useSWR( + `${URLS.ADDRESS_URL}/${id}`, + axiosGet, + SWR_OPTIONS, + ); + + return { + isLoading, + outgoingSum: data?.outgoingSum || 0, + incomingSum: data?.incomingSum || 0, + }; +} + +export function useBalanceHistory(id: string, period: string) { + const { data, isLoading } = useSWRInfinite<{ + data: Array; + incoming: Array; + outgoing: Array; + }>(() => `${URLS.BALANCE_HISTORY_URL}/${id}?period=${period}`, axiosGet, SWR_OPTIONS); + + return { + balance: data ? data[0].data : [], + incoming: data ? data[0].incoming : [], + outgoing: data ? data[0].outgoing : [], + isLoading, + }; +} + +export function useLatestTransactions( id: string, limit: number, sortBy: string, @@ -19,12 +52,13 @@ export default function useAddressDetails( ) { const { data, isLoading, size, setSize } = useSWRInfinite( index => - `${URLS.ADDRESS_URL}/${id}?offset=${ + `${URLS.LATEST_TRANSACTIONS_URL}/${id}?offset=${ index * DATA_FETCH_LIMIT }&limit=${limit}&sortBy=${sortBy}&sortDirection=${sortDirection}`, axiosGet, SWR_OPTIONS, ); + const isLoadingMore = isLoading || (size > 0 && data && typeof data[size - 1] === 'undefined'); const newData = []; const csvData = []; @@ -45,14 +79,7 @@ export default function useAddressDetails( } return { - swrData: newData.length - ? { - address: data?.[0]?.address || '', - data: newData, - incomingSum: data?.[0]?.incomingSum || 0, - outgoingSum: data?.[0]?.outgoingSum || 0, - } - : DEFAULT_ADDRESS_DATA, + addresses: data?.length ? newData : null, isLoading: isLoadingMore, csvData, swrSize: size, diff --git a/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx b/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx index 6ffad7d7..5c62c5a0 100644 --- a/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx +++ b/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx @@ -1,4 +1,5 @@ import { Grid } from '@material-ui/core'; +import format from 'date-fns/format'; import RouterLink from '@components/RouterLink/RouterLink'; import CopyButton from '@components/CopyButton/CopyButton'; @@ -10,6 +11,7 @@ import { formattedDate } from '@utils/helpers/date/date'; import { IAddressData, IAddress } from '@utils/types/IAddress'; import { formatNumber } from '@utils/helpers/formatNumbers/formatNumbers'; import { formatAddress } from '@utils/helpers/format'; +import { TChartStatisticsResponse } from '@utils/types/IStatistics'; import { ADDRESS_TRANSACTION_TIMESTAMP_KEY, @@ -74,3 +76,26 @@ export const addressHeaders: Array = [ { id: 2, header: 'pages.addressDetails.totalReceived' }, { id: 3, header: 'pages.addressDetails.balance' }, ]; + +export const transformChartData = (data: TChartStatisticsResponse[] | null) => { + const dataX: string[] = []; + const dataY: number[] = []; + if (data?.length) { + data.forEach(item => { + dataX.push(format(item.time, 'MM/dd/yyyy')); + dataY.push(item.value); + }); + + const nowHour = format(new Date(), 'MM/dd/yyyy'); + const targetHour = format(new Date(dataX[dataX.length - 1]), 'MM/dd/yyyy'); + if (nowHour !== targetHour) { + dataX.push(format(new Date(), 'MM/dd/yyyy')); + dataY.push(dataY[dataY.length - 1]); + } + } + + return { + dataX, + dataY, + }; +}; diff --git a/src/pages/Details/AddressDetails/AddressDetails.styles.ts b/src/pages/Details/AddressDetails/AddressDetails.styles.ts index 563c4c7f..178afe95 100644 --- a/src/pages/Details/AddressDetails/AddressDetails.styles.ts +++ b/src/pages/Details/AddressDetails/AddressDetails.styles.ts @@ -2,10 +2,20 @@ import styled from 'styled-components/macro'; import { Grid } from '@material-ui/core'; export const TableWrapper = styled(Grid)` + position: relative; + min-height: 30vh; + .latest-transaction-table { padding-left: 0; padding-right: 0; } + + .loading-wrapper { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } `; export const Wrapper = styled.div` @@ -17,7 +27,7 @@ export const Wrapper = styled.div` } .address-wrapper { - margin-bottom: 12px; + margin-bottom: 8px; & > .MuiPaper-root { margin-bottom: 0 !important; @@ -154,3 +164,137 @@ export const TitleWrapper = styled('div')` font-size: 14px; } `; + +export const BalanceHistoryWrapper = styled.div` + display: block; + width: 100%; + margin-top: 0; + + .balance-history-dropdown { + .MuiSelect-select { + width: auto; + } + } +`; + +export const Heading = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + padding: 20px 16px; + box-shadow: 0px 5px 6px rgb(16 16 16 / 6%); + background: ${props => props.theme.card.titleColor}; + border-radius: 10px 10px 0 0; + overflow: hidden; +`; + +export const HeadingTitle = styled.h4` + margin: 0; + font-size: 16px; + line-height: 22px; + font-weight: 700; +`; + +export const ChartWrapper = styled.div` + position: relative; + width: 100%; + height: 100%; + min-height: 266px; + padding: 10px 8px; + background: ${props => props.theme.sidebar.menu.background}; + + .balance-history-loader { + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + } + + .echarts-for-react { + height: 246px !important; + } + + .line-chart { + padding: 0; + } + + .period { + justify-content: flex-end; + margin-right: 12px; + margin-bottom: 0; + } +`; + +export const SummaryWrapper = styled.div` + display: flex; + width: 30%; + margin-left: 8px; +`; + +export const SummaryItem = styled.div` + display: flex; + align-items: center; + margin-right: 30px; + cursor: pointer; +`; + +export const SummaryLabel = styled.span` + color: ${props => props.theme.sidebar.menu.default}; + font-weight: 500; + font-size: 14px; + line-height: 1; + white-space: nowrap; +`; + +export const SummaryValue = styled.p` + margin: 0; + font-size: 22px; + line-height: 1.1; + font-weight: 700; + overflow: hidden; + color: ${props => props.theme.card.color}; +`; + +export const BalanceHistorySummaryWrapper = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 5px; +`; + +export const SummaryIcon = styled.div` + width: 38px; + height: 38px; + margin-top: 1px; + margin-right: 7px; + padding: 10px; + background: ${props => props.theme.sidebar.menu.background}; + border-radius: 50%; + border: 1px solid rgb(16 16 16 / 6%); + opacity: 0.3; + + svg { + width: 100%; + height: 100%; + } + + &.active { + opacity: 1; + + &.balance { + border-color: #5470c6; + } + + &.sent { + border-color: #e94830; + } + + &.received { + border-color: #219653; + } + } +`; + +export const ItemWrapper = styled.div` + line-height: 1; +`; diff --git a/src/pages/Details/AddressDetails/AddressDetails.tsx b/src/pages/Details/AddressDetails/AddressDetails.tsx index 3d4709ca..b95c1d25 100644 --- a/src/pages/Details/AddressDetails/AddressDetails.tsx +++ b/src/pages/Details/AddressDetails/AddressDetails.tsx @@ -3,7 +3,6 @@ import { useParams } from 'react-router-dom'; import { CSVLink } from 'react-csv'; import { CircularProgress, Grid } from '@material-ui/core'; -import Table from '@components/Table/Table'; import InfinityTable, { SortDirectionsType, ISortData, @@ -13,16 +12,16 @@ import { translate } from '@utils/helpers/i18n'; import { getCurrencyName } from '@utils/appInfo'; import { eChartLineStyles } from '@pages/HistoricalStatistics/Chart/styles'; import * as TransactionStyles from '@pages/Details/TransactionDetails/TransactionDetails.styles'; -import useAddressDetails from '@hooks/useAddressDetails'; +import * as TableStyles from '@components/Table/Table.styles'; +import { useLatestTransactions } from '@hooks/useAddressDetails'; import { - addressHeaders, DATA_FETCH_LIMIT, DATA_DEFAULT_SORT, generateLatestTransactions, - generateAddressSummary, } from './AddressDetails.helpers'; import { ADDRESS_TRANSACTION_TIMESTAMP_KEY, columns } from './AddressDetails.columns'; +import BalanceHistory from './BalanceHistory'; import * as Styles from './AddressDetails.styles'; interface ParamTypes { @@ -49,7 +48,7 @@ const AddressDetails = () => { sortBy: ADDRESS_TRANSACTION_TIMESTAMP_KEY, sortDirection: DATA_DEFAULT_SORT, }); - const { swrData: addresses, csvData, swrSize, swrSetSize, isLoading } = useAddressDetails( + const { addresses, isLoading, swrSize, swrSetSize, csvData } = useLatestTransactions( id, DATA_FETCH_LIMIT, apiParams.sortBy, @@ -103,7 +102,7 @@ const AddressDetails = () => { const generateTitle = () => { const date = new Date(); - const fileName = `Transaction_History__${addresses.address}__${date.getFullYear()}_${ + const fileName = `Transaction_History__${id}__${date.getFullYear()}_${ date.getMonth() + 1 }_${date.getDate()}.csv`; return ( @@ -123,43 +122,48 @@ const AddressDetails = () => { ); }; - return addresses ? ( + return ( - + + + + {generateAddTitle()} + + + + + + - + {addresses ? ( + + ) : null} + {isLoading && !addresses?.length ? ( + + + + + + ) : null} - ) : ( - - - - - ); }; diff --git a/src/pages/Details/AddressDetails/BalanceHistory.tsx b/src/pages/Details/AddressDetails/BalanceHistory.tsx new file mode 100644 index 00000000..9914af19 --- /dev/null +++ b/src/pages/Details/AddressDetails/BalanceHistory.tsx @@ -0,0 +1,107 @@ +import { useState } from 'react'; +import { Skeleton } from '@material-ui/lab'; + +import { periods } from '@utils/constants/statistics'; +import { translate } from '@utils/helpers/i18n'; +import { LineChart } from '@components/Summary/LineChart'; +import { PeriodTypes } from '@utils/helpers/statisticsLib'; +import { useBalanceHistory } from '@hooks/useAddressDetails'; +import * as StatisticsStyles from '@pages/Statistics/Statistics.styles'; +import * as ChartStyles from '@pages/HistoricalStatistics/Chart/Chart.styles'; + +import Summary from './Summary'; +import { transformChartData } from './AddressDetails.helpers'; +import * as Styles from './AddressDetails.styles'; + +interface IBalanceHistoryProps { + id: string; +} + +const BalanceHistory: React.FC = ({ id }) => { + const [selectedPeriod, setSelectedPeriod] = useState(periods[1][0]); + const [selectedChartType, setSelectedChartType] = useState('balance'); + const { isLoading, balance, incoming, outgoing } = useBalanceHistory(id, selectedPeriod); + + const handlePeriodFilterChange = (value: PeriodTypes) => { + setSelectedPeriod(value); + }; + + const getActivePeriodButtonStyle = (index: string): string => { + if (selectedPeriod === index) { + return 'active'; + } + return ''; + }; + + const handleChangeTypeChange = (_value: string) => { + setSelectedChartType(_value); + }; + + let chartData = transformChartData(balance); + switch (selectedChartType) { + case 'sent': + chartData = transformChartData(outgoing); + break; + case 'received': + chartData = transformChartData(incoming); + break; + default: + break; + } + + const getChartColor = () => { + switch (selectedChartType) { + case 'sent': + return '#E94830'; + case 'received': + return '#219653'; + default: + return '#5470c6'; + } + }; + + return ( + + + + + {translate('pages.historicalStatistics.period')}: + {periods[1].map(period => ( + handlePeriodFilterChange(period)} + type="button" + key={`button-filter-${period}`} + > + {period} + + ))} + + + + {isLoading ? ( + + + + {translate('common.loadingData')} + + + ) : ( + + )} + + + ); +}; + +export default BalanceHistory; diff --git a/src/pages/Details/AddressDetails/Summary.tsx b/src/pages/Details/AddressDetails/Summary.tsx new file mode 100644 index 00000000..0fb131f8 --- /dev/null +++ b/src/pages/Details/AddressDetails/Summary.tsx @@ -0,0 +1,69 @@ +import { formatNumber } from '@utils/helpers/formatNumbers/formatNumbers'; +import { getCurrencyName } from '@utils/appInfo'; +import { translate } from '@utils/helpers/i18n'; +import useAddressDetails from '@hooks/useAddressDetails'; +import Balance from '@components/SvgIcon/Balance'; +import Sent from '@components/SvgIcon/Sent'; +import Received from '@components/SvgIcon/Received'; + +import * as Styles from './AddressDetails.styles'; + +interface ISummaryProps { + id: string; + onChange: (_value: string) => void; + selectedChart: string; +} + +const Summary: React.FC = ({ id, onChange, selectedChart }) => { + const { outgoingSum, incomingSum } = useAddressDetails(id); + + return ( + + onChange('balance')}> + + + + + + {translate('pages.addressDetails.balance', { currency: getCurrencyName() })} + + + {formatNumber(incomingSum + outgoingSum, { + decimalsLength: 2, + })} + + + + onChange('sent')}> + + + + + + {translate('pages.addressDetails.totalSent', { currency: getCurrencyName() })} + + + {formatNumber(outgoingSum, { decimalsLength: 2 })} + + + + onChange('received')}> + + + + + + {translate('pages.addressDetails.totalReceived', { + currency: getCurrencyName(), + })} + + + {formatNumber(incomingSum, { decimalsLength: 2 })} + + + + + ); +}; + +export default Summary; diff --git a/src/utils/constants/types.ts b/src/utils/constants/types.ts index 805d064d..d0f0e4ff 100644 --- a/src/utils/constants/types.ts +++ b/src/utils/constants/types.ts @@ -31,6 +31,8 @@ export type TThemeInitOption = { granularity?: TGranularity; height?: number; width?: number; + seriesName?: string; + chartColor?: string; }; export type TLineChartProps = { diff --git a/src/utils/constants/urls.ts b/src/utils/constants/urls.ts index b064a763..46ba9a51 100644 --- a/src/utils/constants/urls.ts +++ b/src/utils/constants/urls.ts @@ -17,6 +17,8 @@ export const BLOCK_SIZE_URL = 'v1/blocks/size'; export const STATISTICS_BLOCK_URL = 'v1/blocks/statistics'; export const ADDRESS_URL = 'v1/addresses'; export const RICHLIST_URL = 'v1/addresses/rank/100'; +export const BALANCE_HISTORY_URL = 'v1/addresses/balance-history'; +export const LATEST_TRANSACTIONS_URL = 'v1/addresses/latest-transactions'; export const NETWORK_URL = 'v1/network'; export const SUMMARY_URL = 'v1/stats'; export const GET_STATISTICS = 'v1/stats/list'; diff --git a/src/utils/helpers/chartOptions.ts b/src/utils/helpers/chartOptions.ts index 3df24287..e01cb6cb 100644 --- a/src/utils/helpers/chartOptions.ts +++ b/src/utils/helpers/chartOptions.ts @@ -11,6 +11,7 @@ import { PeriodTypes, generateXAxisInterval, generateXAxisIntervalForScatterChart, + balanceHistoryXAxisInterval, TGranularity, getYAxisLabel, convertYAxisLabel, @@ -2059,7 +2060,20 @@ type TSizeProps = { }; export function getSummaryThemeUpdateOption(args: TThemeInitOption): EChartsOption { - const { theme, dataX, dataY, dataY1, chartName, minY, maxY, darkMode } = args; + const { + theme, + dataX, + dataY, + dataY1, + chartName, + minY, + maxY, + darkMode, + period, + width, + seriesName, + chartColor, + } = args; const blueColor = darkMode ? '#1fbfff' : '#5470c6'; const chartOptions: TChartOption = { gigaHashPerSec: { @@ -3517,6 +3531,102 @@ export function getSummaryThemeUpdateOption(args: TThemeInitOption): EChartsOpti }, animation: false, }, + balanceHistory: { + backgroundColor: theme?.backgroundColor, + textStyle: { + color: theme?.color, + }, + color: [chartColor || blueColor], + grid: { + top: 8, + right: 0, + bottom: 20, + left: 0, + show: false, + }, + tooltip: { + trigger: 'axis', + formatter: (params: TToolTipParamsProps[]) => { + return `
${params[0].axisValue}
${params[0].marker} ${ + params[0].seriesName + }: ${ + params[0].data ? formatNumber(params[0].data, { decimalsLength: 2 }) : '0' + } ${getCurrencyName()}`; + }, + }, + xAxis: { + type: 'category', + data: dataX, + boundaryGap: false, + axisLabel: { + show: true, + formatter(value: string) { + return value ? generateXAxisLabel(new Date(value), period, false) : null; + }, + showMaxLabel: false, + showMinLabel: true, + interval: balanceHistoryXAxisInterval(dataX, width), + align: 'left', + }, + axisLine: { + show: false, + }, + splitLine: { + show: false, + }, + axisTick: { + show: false, + }, + name: dataX?.length + ? generateXAxisLabel(new Date(dataX[dataX.length - 1]), period, false) + : '', + nameGap: 0, + nameLocation: 'end', + nameTextStyle: { + align: 'right', + verticalAlign: 'top', + padding: [8, 2, 0, 0], + }, + }, + yAxis: { + type: 'value', + min: minY, + max: maxY, + interval: (maxY - minY) / 5, + splitLine: { + show: false, + }, + axisLine: { + show: false, + }, + axisTick: { + show: false, + }, + axisLabel: { + show: false, + }, + }, + series: { + name: translate(seriesName || 'pages.addressDetails.balanceHistory.balance') || '', + type: 'line', + sampling: 'lttb', + data: dataY, + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: chartColor || blueColor, + }, + { + offset: 1, + color: theme?.backgroundColor || '#fff', + }, + ]), + }, + showSymbol: false, + }, + animation: false, + }, }; return chartOptions[chartName]; } diff --git a/src/utils/helpers/statisticsLib.ts b/src/utils/helpers/statisticsLib.ts index 99820bd1..adc5254a 100644 --- a/src/utils/helpers/statisticsLib.ts +++ b/src/utils/helpers/statisticsLib.ts @@ -615,7 +615,7 @@ export const generateXAxisInterval = ( return Math.floor(dataX.length / 7); case '30d': if (dataX.length !== 31) { - return Math.floor(dataX.length / 15); + return Math.floor(dataX.length / 11); } return 1; case '90d': @@ -853,3 +853,19 @@ export function transformTransactionsChartData( } return { dataX, dataY }; } + +export const balanceHistoryXAxisInterval = (dataX?: string[], width?: number) => { + if (!dataX?.length || !width) { + return 'auto'; + } + + if (width > 960 && width < 1200) { + return Math.floor(dataX.length / 5); + } + + if (width <= 960) { + return Math.floor(dataX.length / 3); + } + + return Math.floor(dataX.length / 8); +}; From 75d3b6ab051a4b1d9badf162ec96c307cafd5d0c Mon Sep 17 00:00:00 2001 From: Tuan Nguyen Date: Thu, 23 Mar 2023 11:10:26 +0700 Subject: [PATCH 02/12] updated responsive for the Address Details pages --- src/components/Summary/LineChart.tsx | 2 +- .../AddressDetails/AddressDetails.styles.ts | 26 +++++++++++++++++++ src/utils/helpers/statisticsLib.ts | 3 ++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/components/Summary/LineChart.tsx b/src/components/Summary/LineChart.tsx index dbcc9018..60095e23 100644 --- a/src/components/Summary/LineChart.tsx +++ b/src/components/Summary/LineChart.tsx @@ -92,7 +92,7 @@ export const LineChart = (props: TLineChartProps): JSX.Element | null => { setMinY(result.min); setMaxY(result.max); } else if (['balanceHistory'].includes(chartName)) { - const result = generateMinMaxChartData(min, max, offset, 5, period); + const result = generateMinMaxChartData(min, max, offset, 5, period, 0, 0.5); setMinY(result.min); setMaxY(result.max); } else { diff --git a/src/pages/Details/AddressDetails/AddressDetails.styles.ts b/src/pages/Details/AddressDetails/AddressDetails.styles.ts index 178afe95..7eec65ad 100644 --- a/src/pages/Details/AddressDetails/AddressDetails.styles.ts +++ b/src/pages/Details/AddressDetails/AddressDetails.styles.ts @@ -223,12 +223,28 @@ export const ChartWrapper = styled.div` margin-right: 12px; margin-bottom: 0; } + + @media (max-width: 960px) { + .period { + width: 100%; + margin-top: 10px; + } + } `; export const SummaryWrapper = styled.div` display: flex; width: 30%; margin-left: 8px; + + @media (max-width: 960px) { + width: 100%; + } + + @media (max-width: 600px) { + flex-direction: column; + align-items: flex-start; + } `; export const SummaryItem = styled.div` @@ -236,6 +252,11 @@ export const SummaryItem = styled.div` align-items: center; margin-right: 30px; cursor: pointer; + + @media (max-width: 600px) { + margin-right: 0; + margin-top: 10px; + } `; export const SummaryLabel = styled.span` @@ -260,6 +281,11 @@ export const BalanceHistorySummaryWrapper = styled.div` align-items: center; justify-content: space-between; margin-top: 5px; + + @media (max-width: 960px) { + flex-direction: column; + align-items: flex-start; + } `; export const SummaryIcon = styled.div` diff --git a/src/utils/helpers/statisticsLib.ts b/src/utils/helpers/statisticsLib.ts index adc5254a..fd3e00cf 100644 --- a/src/utils/helpers/statisticsLib.ts +++ b/src/utils/helpers/statisticsLib.ts @@ -633,6 +633,7 @@ export const generateMinMaxChartData = ( step: number, period?: PeriodTypes, decimalsLength?: number, + percent?: number, ): TMinMaxChartData => { let result = { min: 0, @@ -650,7 +651,7 @@ export const generateMinMaxChartData = ( max: Math.ceil(max), }; } else { - let minVal = Math.floor(inputMin - Math.floor(inputMin) * 0.02); + let minVal = Math.floor(inputMin - Math.floor(inputMin) * (percent || 0.02)); if (minVal % step !== 0) { const minRange = minVal / step; const tmpMin = minVal; From b30f3a2c8efc05f950eb8b938edd256fe11f78e6 Mon Sep 17 00:00:00 2001 From: Tuan Nguyen Date: Thu, 23 Mar 2023 16:40:13 +0700 Subject: [PATCH 03/12] add Received by Month and Sent by Month charts --- public/locales/en/translation.json | 12 + .../InfinityTable/InfinityTable.tsx | 2 +- src/components/Summary/LineChart.tsx | 9 +- src/hooks/useAddressDetails.ts | 12 + .../AddressDetails/AddressDetails.columns.ts | 14 +- .../AddressDetails/AddressDetails.helpers.tsx | 34 ++- .../AddressDetails/AddressDetails.styles.ts | 96 +++++++- .../Details/AddressDetails/AddressDetails.tsx | 62 +++-- .../Details/AddressDetails/BalanceHistory.tsx | 22 +- .../Details/AddressDetails/DirectionChart.tsx | 110 +++++++++ src/pages/Details/AddressDetails/Summary.tsx | 44 +++- src/utils/constants/statistics.ts | 7 +- src/utils/constants/urls.ts | 1 + src/utils/helpers/chartOptions.ts | 214 ++++++++++++++++++ src/utils/helpers/statisticsLib.ts | 17 +- src/utils/types/IAddress.ts | 1 + 16 files changed, 591 insertions(+), 66 deletions(-) create mode 100644 src/pages/Details/AddressDetails/DirectionChart.tsx diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index e9aab75b..de0d003e 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -1087,6 +1087,10 @@ "message": "Timestamp", "description": "Used for the CascadeAndSenseStatistics page" }, + "direction": { + "message": "Type", + "description": "Used for the CascadeAndSenseStatistics page" + }, "totalSent": { "message": "Total Sent ({{currency}})", "description": "Used for the CascadeAndSenseStatistics page" @@ -1123,6 +1127,14 @@ "received": { "message": "Received", "description": "Used for the CascadeAndSenseStatistics page" + }, + "receivedByMonth": { + "message": "Received by Month", + "description": "Used for the CascadeAndSenseStatistics page" + }, + "sentByMonth": { + "message": "Sent by Month", + "description": "Used for the CascadeAndSenseStatistics page" } } }, diff --git a/src/components/InfinityTable/InfinityTable.tsx b/src/components/InfinityTable/InfinityTable.tsx index 7a5070a5..04f24ed1 100644 --- a/src/components/InfinityTable/InfinityTable.tsx +++ b/src/components/InfinityTable/InfinityTable.tsx @@ -217,7 +217,7 @@ const InfinityTableComponent: React.FC = ({ ) : null} {!isLoading ? ( - + {({ width }) => (
diff --git a/src/components/Summary/LineChart.tsx b/src/components/Summary/LineChart.tsx index 60095e23..b644ee65 100644 --- a/src/components/Summary/LineChart.tsx +++ b/src/components/Summary/LineChart.tsx @@ -87,7 +87,14 @@ export const LineChart = (props: TLineChartProps): JSX.Element | null => { const result = generateMinMaxChartData(min, max, offset, 5, '1h', 4); setMinY(result.min); setMaxY(result.max); - } else if (['totalSizeOfDataStored', 'totalFingerprintsOnSense'].includes(chartName)) { + } else if ( + [ + 'totalSizeOfDataStored', + 'totalFingerprintsOnSense', + 'directionIncoming', + 'directionOutgoing', + ].includes(chartName) + ) { const result = generateMinMaxChartData(min, max, offset, 5); setMinY(result.min); setMaxY(result.max); diff --git a/src/hooks/useAddressDetails.ts b/src/hooks/useAddressDetails.ts index 72ff15f6..71a8dfbb 100644 --- a/src/hooks/useAddressDetails.ts +++ b/src/hooks/useAddressDetails.ts @@ -86,3 +86,15 @@ export function useLatestTransactions( swrSetSize: setSize, }; } + +export function useDirection(id: string, period: string, direction: string) { + const { data, isLoading } = useSWRInfinite>( + () => `${URLS.DIRECTION_URL}/${id}?period=${period}&direction=${direction}`, + axiosGet, + SWR_OPTIONS, + ); + return { + data: data ? data[0] : [], + isLoading, + }; +} diff --git a/src/pages/Details/AddressDetails/AddressDetails.columns.ts b/src/pages/Details/AddressDetails/AddressDetails.columns.ts index fe684683..05dd441a 100644 --- a/src/pages/Details/AddressDetails/AddressDetails.columns.ts +++ b/src/pages/Details/AddressDetails/AddressDetails.columns.ts @@ -1,10 +1,11 @@ export const ADDRESS_TRANSACTION_TIMESTAMP_KEY = 'timestamp'; export const ADDRESS_TRANSACTION_HASH_KEY = 'transactionHash'; export const ADDRESS_TRANSACTION_AMOUNT_KEY = 'amount'; +export const ADDRESS_TRANSACTION_DIRECTION_KEY = 'direction'; export const columns = [ { - width: 500, + width: 230, flexGrow: 1, label: 'pages.addressDetails.hash', dataKey: ADDRESS_TRANSACTION_HASH_KEY, @@ -13,7 +14,7 @@ export const columns = [ dataTitle: 'pages.addressDetails.hash', }, { - width: 40, + width: 80, flexGrow: 1, label: `pages.addressDetails.amount`, dataKey: ADDRESS_TRANSACTION_AMOUNT_KEY, @@ -21,6 +22,15 @@ export const columns = [ className: 'col-amount', dataTitle: 'pages.addressDetails.amount', }, + { + width: 45, + flexGrow: 1, + label: `pages.addressDetails.direction`, + dataKey: ADDRESS_TRANSACTION_DIRECTION_KEY, + disableSort: false, + className: 'col-direction', + dataTitle: 'pages.addressDetails.direction', + }, { width: 90, flexGrow: 1, diff --git a/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx b/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx index 5c62c5a0..71ca706c 100644 --- a/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx +++ b/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx @@ -12,11 +12,13 @@ import { IAddressData, IAddress } from '@utils/types/IAddress'; import { formatNumber } from '@utils/helpers/formatNumbers/formatNumbers'; import { formatAddress } from '@utils/helpers/format'; import { TChartStatisticsResponse } from '@utils/types/IStatistics'; +import { translate } from '@utils/helpers/i18n'; import { ADDRESS_TRANSACTION_TIMESTAMP_KEY, ADDRESS_TRANSACTION_HASH_KEY, ADDRESS_TRANSACTION_AMOUNT_KEY, + ADDRESS_TRANSACTION_DIRECTION_KEY, } from './AddressDetails.columns'; export const DEFAULT_ADDRESS_DATA: IAddress = { @@ -30,11 +32,8 @@ export const DATA_FETCH_LIMIT = 20; export const DATA_OFFSET = 0; export const DATA_DEFAULT_SORT = 'DESC'; -export const generateLatestTransactions = ( - transactionsList: Array, - isMobile: boolean, -): RowsProps[] => - transactionsList.map(({ amount, timestamp, transactionHash }) => ({ +export const generateLatestTransactions = (transactionsList: Array): RowsProps[] => + transactionsList.map(({ amount, timestamp, transactionHash, direction }) => ({ [ADDRESS_TRANSACTION_TIMESTAMP_KEY]: formattedDate(timestamp, { dayName: false, }), @@ -43,13 +42,20 @@ export const generateLatestTransactions = ( ), + [ADDRESS_TRANSACTION_DIRECTION_KEY]: ( + <> + {direction === 'Outgoing' + ? translate('pages.addressDetails.balanceHistory.sent') + : translate('pages.addressDetails.balanceHistory.received')} + + ), [ADDRESS_TRANSACTION_AMOUNT_KEY]: formatNumber(amount, { decimalsLength: 2 }), })); @@ -99,3 +105,19 @@ export const transformChartData = (data: TChartStatisticsResponse[] | null) => { dataY, }; }; + +export const transformDirectionChartData = (data: TChartStatisticsResponse[] | null) => { + const dataX: string[] = []; + const dataY: number[] = []; + if (data?.length) { + data.forEach(item => { + dataX.push(item.time.toString()); + dataY.push(item.value); + }); + } + + return { + dataX, + dataY, + }; +}; diff --git a/src/pages/Details/AddressDetails/AddressDetails.styles.ts b/src/pages/Details/AddressDetails/AddressDetails.styles.ts index 7eec65ad..005fe23d 100644 --- a/src/pages/Details/AddressDetails/AddressDetails.styles.ts +++ b/src/pages/Details/AddressDetails/AddressDetails.styles.ts @@ -10,15 +10,39 @@ export const TableWrapper = styled(Grid)` padding-right: 0; } + .table-wrapper { + overflow-x: hidden; + } + .loading-wrapper { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } + + .relative { + position: relative; + } + + .transaction-table-mask { + min-height: 595px; + background: ${props => props.theme.sidebar.menu.background}; + box-shadow: 0px 5px 6px rgb(16 16 16 / 6%); + border-radius: 0 0 10px 10px; + } + + .mt-15 { + margin-top: 15px; + } `; export const Wrapper = styled.div` + .disable { + cursor: default; + pointer-events: none; + } + .address-table-wrapper { padding: 10px 0; border-top-left-radius: 0; @@ -26,6 +50,10 @@ export const Wrapper = styled.div` background: ${props => props.theme.sidebar.menu.background}; } + .ReactVirtualized__Grid { + min-width: unset; + } + .address-wrapper { margin-bottom: 8px; @@ -72,10 +100,6 @@ export const Wrapper = styled.div` max-width: calc(100vw - 225px); } - .ReactVirtualized__Grid { - min-width: unset; - } - .ReactVirtualized__Table__headerRow { display: none; } @@ -187,6 +211,10 @@ export const Heading = styled.div` background: ${props => props.theme.card.titleColor}; border-radius: 10px 10px 0 0; overflow: hidden; + + &.direction-item { + padding: 14px 16px; + } `; export const HeadingTitle = styled.h4` @@ -286,6 +314,16 @@ export const BalanceHistorySummaryWrapper = styled.div` flex-direction: column; align-items: flex-start; } + + @media (max-width: 375px) { + .period { + span { + display: block; + width: 100%; + padding-left: 12px; + } + } + } `; export const SummaryIcon = styled.div` @@ -324,3 +362,53 @@ export const SummaryIcon = styled.div` export const ItemWrapper = styled.div` line-height: 1; `; + +export const DirectionChartWrapper = styled.div` + display: block; + width: 100%; + margin-top: 16px; + + .line-chart { + padding: 16px; + } +`; + +export const ChartItem = styled.div` + width: 100%; + + .chart-box { + position: relative; + min-height: 260px; + background: ${props => props.theme.sidebar.menu.background}; + border-radius: 0 0 10px 10px; + box-shadow: 0px 5px 6px rgb(16 16 16 / 6%); + } + + .echarts-for-react { + height: 240px !important; + } + + .direction-period { + .MuiSelect-select { + width: auto; + } + } + + .direction-period { + justify-content: flex-end; + } +`; + +export const Loader = styled.div` + position: absolute; + top: 50%; + left: 50%; + display: inline-block; + text-align: center; + transform: translate(-50%, -50%); +`; + +export const LoadingText = styled.div` + margin-top: 0; + font-size: 20px; +`; diff --git a/src/pages/Details/AddressDetails/AddressDetails.tsx b/src/pages/Details/AddressDetails/AddressDetails.tsx index b95c1d25..54224f81 100644 --- a/src/pages/Details/AddressDetails/AddressDetails.tsx +++ b/src/pages/Details/AddressDetails/AddressDetails.tsx @@ -2,6 +2,7 @@ import React, { useState, useRef, useEffect } from 'react'; import { useParams } from 'react-router-dom'; import { CSVLink } from 'react-csv'; import { CircularProgress, Grid } from '@material-ui/core'; +import Box from '@material-ui/core/Box'; import InfinityTable, { SortDirectionsType, @@ -11,7 +12,6 @@ import { translate } from '@utils/helpers/i18n'; import { getCurrencyName } from '@utils/appInfo'; import { eChartLineStyles } from '@pages/HistoricalStatistics/Chart/styles'; -import * as TransactionStyles from '@pages/Details/TransactionDetails/TransactionDetails.styles'; import * as TableStyles from '@components/Table/Table.styles'; import { useLatestTransactions } from '@hooks/useAddressDetails'; @@ -22,6 +22,7 @@ import { } from './AddressDetails.helpers'; import { ADDRESS_TRANSACTION_TIMESTAMP_KEY, columns } from './AddressDetails.columns'; import BalanceHistory from './BalanceHistory'; +import DirectionChart from './DirectionChart'; import * as Styles from './AddressDetails.styles'; interface ParamTypes { @@ -114,7 +115,7 @@ const AddressDetails = () => { headers={transactionHistoryCSVHeaders} separator="," ref={downloadRef} - className={styles.uploadButton} + className={`${styles.uploadButton} ${!addresses ? 'disable' : ''}`} > {translate('pages.addressDetails.downloadCSV')} @@ -138,29 +139,40 @@ const AddressDetails = () => { - {addresses ? ( - - ) : null} - {isLoading && !addresses?.length ? ( - - - - - - ) : null} + + + {addresses ? ( + + ) : null} + {isLoading && !addresses?.length ? ( + + {generateTitle()} + + + + {translate('common.loadingData')} + + + + ) : null} + + + + + diff --git a/src/pages/Details/AddressDetails/BalanceHistory.tsx b/src/pages/Details/AddressDetails/BalanceHistory.tsx index 9914af19..87206a05 100644 --- a/src/pages/Details/AddressDetails/BalanceHistory.tsx +++ b/src/pages/Details/AddressDetails/BalanceHistory.tsx @@ -1,12 +1,11 @@ import { useState } from 'react'; -import { Skeleton } from '@material-ui/lab'; +import CircularProgress from '@material-ui/core/CircularProgress'; import { periods } from '@utils/constants/statistics'; import { translate } from '@utils/helpers/i18n'; import { LineChart } from '@components/Summary/LineChart'; import { PeriodTypes } from '@utils/helpers/statisticsLib'; import { useBalanceHistory } from '@hooks/useAddressDetails'; -import * as StatisticsStyles from '@pages/Statistics/Statistics.styles'; import * as ChartStyles from '@pages/HistoricalStatistics/Chart/Chart.styles'; import Summary from './Summary'; @@ -63,12 +62,17 @@ const BalanceHistory: React.FC = ({ id }) => { return ( - + {translate('pages.historicalStatistics.period')}: {periods[1].map(period => ( handlePeriodFilterChange(period)} type="button" key={`button-filter-${period}`} @@ -80,12 +84,10 @@ const BalanceHistory: React.FC = ({ id }) => { {isLoading ? ( - - - - {translate('common.loadingData')} - - + + + {translate('common.loadingData')} + ) : ( = ({ id, direction, chartName, title }) => { + const [period, setPeriod] = useState(periods[10][0]); + const { data, isLoading } = useDirection(id, period, direction); + + const handlePeriodChange = (_period: string) => { + setPeriod(_period as PeriodTypes); + }; + + const getActivePeriodButtonStyle = (_period: string): string => { + if (period === _period) { + return 'active'; + } + return ''; + }; + const chartData = transformDirectionChartData(data); + + return ( + + + + {title} + + {translate('pages.historicalStatistics.period')}: + {periods[10].map(_period => ( + handlePeriodChange(_period)} + type="button" + key={`${chartName}-filter-${_period}`} + > + {_period} + + ))} + + + + + {isLoading ? ( + + + {translate('common.loadingData')} + + ) : ( + + )} + + + ); +}; + +const DirectionChart: React.FC = ({ id }) => { + return ( + + + + + + + + + + + ); +}; + +export default DirectionChart; diff --git a/src/pages/Details/AddressDetails/Summary.tsx b/src/pages/Details/AddressDetails/Summary.tsx index 0fb131f8..d1b19b59 100644 --- a/src/pages/Details/AddressDetails/Summary.tsx +++ b/src/pages/Details/AddressDetails/Summary.tsx @@ -1,3 +1,5 @@ +import { Skeleton } from '@material-ui/lab'; + import { formatNumber } from '@utils/helpers/formatNumbers/formatNumbers'; import { getCurrencyName } from '@utils/appInfo'; import { translate } from '@utils/helpers/i18n'; @@ -12,14 +14,18 @@ interface ISummaryProps { id: string; onChange: (_value: string) => void; selectedChart: string; + isBalanceLoading: boolean; } -const Summary: React.FC = ({ id, onChange, selectedChart }) => { - const { outgoingSum, incomingSum } = useAddressDetails(id); +const Summary: React.FC = ({ id, onChange, selectedChart, isBalanceLoading }) => { + const { outgoingSum, incomingSum, isLoading } = useAddressDetails(id); return ( - onChange('balance')}> + onChange('balance')} + > @@ -28,13 +34,20 @@ const Summary: React.FC = ({ id, onChange, selectedChart }) => { {translate('pages.addressDetails.balance', { currency: getCurrencyName() })} - {formatNumber(incomingSum + outgoingSum, { - decimalsLength: 2, - })} + {isLoading ? ( + + ) : ( + formatNumber(incomingSum + outgoingSum, { + decimalsLength: 2, + }) + )} - onChange('sent')}> + onChange('sent')} + > @@ -43,11 +56,18 @@ const Summary: React.FC = ({ id, onChange, selectedChart }) => { {translate('pages.addressDetails.totalSent', { currency: getCurrencyName() })} - {formatNumber(outgoingSum, { decimalsLength: 2 })} + {isLoading ? ( + + ) : ( + formatNumber(outgoingSum, { decimalsLength: 2 }) + )} - onChange('received')}> + onChange('received')} + > @@ -58,7 +78,11 @@ const Summary: React.FC = ({ id, onChange, selectedChart }) => { })} - {formatNumber(incomingSum, { decimalsLength: 2 })} + {isLoading ? ( + + ) : ( + formatNumber(incomingSum, { decimalsLength: 2 }) + )} diff --git a/src/utils/constants/statistics.ts b/src/utils/constants/statistics.ts index 1c8abdf4..4159cf60 100644 --- a/src/utils/constants/statistics.ts +++ b/src/utils/constants/statistics.ts @@ -222,16 +222,17 @@ export const pricesCSVHeaders = [ ]; export const periods: PeriodTypes[][] = [ - ['2h', '2d', '4d', 'all'], - ['30d', '60d', '180d', '1y', 'all'], + ['2h', '2d', '4d', 'max'], + ['30d', '60d', '180d', '1y', 'max'], ['1h', '3h', '6h', '12h'], - ['24h', '7d', '14d', '30d', '60d', '180d', '1y', 'all'], + ['24h', '7d', '14d', '30d', '60d', '180d', '1y', 'max'], ['30d', '60d', '180d'], ['1h', '1d', '7d', '30d'], ['24h', '7d', '14d', '30d', '90d', '180d', '1y', 'max'], ['24h', '7d', '30d', '1y', 'max'], ['24h', '7d', '14d'], ['7d', '14d'], + ['1y', '2y', 'max'], ]; export const CHART_THEME_BACKGROUND_DEFAULT_COLOR = '#2D3748'; diff --git a/src/utils/constants/urls.ts b/src/utils/constants/urls.ts index 46ba9a51..6dc43faf 100644 --- a/src/utils/constants/urls.ts +++ b/src/utils/constants/urls.ts @@ -19,6 +19,7 @@ export const ADDRESS_URL = 'v1/addresses'; export const RICHLIST_URL = 'v1/addresses/rank/100'; export const BALANCE_HISTORY_URL = 'v1/addresses/balance-history'; export const LATEST_TRANSACTIONS_URL = 'v1/addresses/latest-transactions'; +export const DIRECTION_URL = 'v1/addresses/direction'; export const NETWORK_URL = 'v1/network'; export const SUMMARY_URL = 'v1/stats'; export const GET_STATISTICS = 'v1/stats/list'; diff --git a/src/utils/helpers/chartOptions.ts b/src/utils/helpers/chartOptions.ts index e01cb6cb..5231d210 100644 --- a/src/utils/helpers/chartOptions.ts +++ b/src/utils/helpers/chartOptions.ts @@ -19,6 +19,11 @@ import { import { TChartParams } from '@utils/types/IStatistics'; import { translate } from '@utils/helpers/i18n'; +type TAxisPointerProps = { + axisDimension: string; + value: number; +}; + type TChartOption = { [index: string]: EChartsOption; }; @@ -2075,6 +2080,7 @@ export function getSummaryThemeUpdateOption(args: TThemeInitOption): EChartsOpti chartColor, } = args; const blueColor = darkMode ? '#1fbfff' : '#5470c6'; + const seriesLabelColor = darkMode ? '#fff' : '#000'; const chartOptions: TChartOption = { gigaHashPerSec: { backgroundColor: theme?.backgroundColor, @@ -3546,6 +3552,18 @@ export function getSummaryThemeUpdateOption(args: TThemeInitOption): EChartsOpti }, tooltip: { trigger: 'axis', + axisPointer: { + type: 'cross', + label: { + formatter: (param: TAxisPointerProps) => { + if (param.axisDimension === 'x') { + return param.value; + } + + return `${formatNumber(param.value, { decimalsLength: 2 })} ${getCurrencyName()}`; + }, + }, + }, formatter: (params: TToolTipParamsProps[]) => { return `
${params[0].axisValue}
${params[0].marker} ${ params[0].seriesName @@ -3627,6 +3645,202 @@ export function getSummaryThemeUpdateOption(args: TThemeInitOption): EChartsOpti }, animation: false, }, + directionOutgoing: { + backgroundColor: theme?.backgroundColor, + textStyle: { + color: theme?.color, + }, + color: ['#E94830'], + grid: { + top: 8, + right: 0, + bottom: 0, + left: 0, + show: false, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross', + label: { + formatter: (param: TAxisPointerProps) => { + if (param.axisDimension === 'x') { + return format(Number(param.value), 'MM/yyyy'); + } + + return `${formatNumber(param.value, { decimalsLength: 2 })} ${getCurrencyName()}`; + }, + }, + }, + formatter: (params: TToolTipParamsProps[]) => { + return `
${format( + Number(params[0].axisValue), + 'MM/yyyy', + )}
${params[0].marker} ${params[0].seriesName}: ${ + params[0].data ? formatNumber(params[0].value, { decimalsLength: 2 }) : '0' + } ${getCurrencyName()}`; + }, + }, + xAxis: { + type: 'category', + data: dataX, + axisLabel: { + show: false, + }, + axisLine: { + show: false, + }, + splitLine: { + show: false, + }, + axisTick: { + show: false, + }, + }, + yAxis: { + type: 'value', + min: 0, + max: maxY, + interval: maxY / 5, + splitLine: { + show: false, + }, + axisLine: { + show: false, + }, + axisTick: { + show: false, + }, + axisLabel: { + show: false, + }, + }, + series: { + name: translate('pages.addressDetails.balanceHistory.sentByMonth') || '', + type: 'bar', + data: dataY?.map((d, index) => { + return { + value: d, + label: { + show: true, + fontSize: 10, + rotate: 90, + position: 'insideBottom', + align: 'left', + verticalAlign: 'middle', + distance: 10, + color: seriesLabelColor, + formatter: () => { + return dataX ? format(Number(dataX[index]), 'MM/yyyy') : ''; + }, + }, + }; + }), + showBackground: true, + backgroundStyle: { + color: 'rgba(180, 180, 180, 0.2)', + }, + }, + animation: false, + }, + directionIncoming: { + backgroundColor: theme?.backgroundColor, + textStyle: { + color: theme?.color, + }, + color: ['#219653'], + grid: { + top: 8, + right: 0, + bottom: 0, + left: 0, + show: false, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross', + label: { + formatter: (param: TAxisPointerProps) => { + if (param.axisDimension === 'x') { + return format(Number(param.value), 'MM/yyyy'); + } + + return `${formatNumber(param.value, { decimalsLength: 2 })} ${getCurrencyName()}`; + }, + }, + }, + formatter: (params: TToolTipParamsProps[]) => { + return `
${format( + Number(params[0].axisValue), + 'MM/yyyy', + )}
${params[0].marker} ${params[0].seriesName}: ${ + params[0].data ? formatNumber(params[0].value, { decimalsLength: 2 }) : '0' + } ${getCurrencyName()}`; + }, + }, + xAxis: { + type: 'category', + data: dataX, + axisLabel: { + show: false, + }, + axisLine: { + show: false, + }, + splitLine: { + show: false, + }, + axisTick: { + show: false, + }, + }, + yAxis: { + type: 'value', + min: 0, + max: maxY, + interval: maxY / 5, + splitLine: { + show: false, + }, + axisLine: { + show: false, + }, + axisTick: { + show: false, + }, + axisLabel: { + show: false, + }, + }, + series: { + name: translate('pages.addressDetails.balanceHistory.receivedByMonth') || '', + type: 'bar', + data: dataY?.map((d, index) => { + return { + value: d, + label: { + show: true, + fontSize: 10, + rotate: 90, + position: 'insideBottom', + align: 'left', + verticalAlign: 'middle', + distance: 10, + color: seriesLabelColor, + formatter: () => { + return dataX ? format(Number(dataX[index]), 'MM/yyyy') : ''; + }, + }, + }; + }), + showBackground: true, + backgroundStyle: { + color: 'rgba(180, 180, 180, 0.2)', + }, + }, + animation: false, + }, }; return chartOptions[chartName]; } diff --git a/src/utils/helpers/statisticsLib.ts b/src/utils/helpers/statisticsLib.ts index fd3e00cf..493d81ba 100644 --- a/src/utils/helpers/statisticsLib.ts +++ b/src/utils/helpers/statisticsLib.ts @@ -44,7 +44,9 @@ export type PeriodTypes = | '60d' | '90d' | '180d' + | '6m' | '1y' + | '2y' | 'max' | 'all'; export type TGranularity = '1d' | '30d' | '1y' | 'all' | 'none'; @@ -540,10 +542,17 @@ export function transformTotalSupplyDataChart( export const generatePeriodToDropdownOptions = (periods: PeriodTypes[]) => { const results = []; for (let i = 0; i < periods.length; i += 1) { - results.push({ - name: translate('pages.statistics.filterLabel', { period: periods[i] }), - value: periods[i], - }); + if (periods[i] !== 'max') { + results.push({ + name: translate('pages.statistics.filterLabel', { period: periods[i] }), + value: periods[i], + }); + } else { + results.push({ + name: translate('pages.statistics.filterLabel'), + value: periods[i], + }); + } } return results; }; diff --git a/src/utils/types/IAddress.ts b/src/utils/types/IAddress.ts index 7d267b56..a797e5ba 100644 --- a/src/utils/types/IAddress.ts +++ b/src/utils/types/IAddress.ts @@ -2,6 +2,7 @@ export interface IAddressData { amount: number; timestamp: number; transactionHash: string; + direction: string; } export interface IAddress { From 2dbaf59f33b37ef7a17070cba4c03f89511af18b Mon Sep 17 00:00:00 2001 From: Tuan Nguyen Date: Thu, 23 Mar 2023 18:24:48 +0700 Subject: [PATCH 04/12] update UI for the Address details page --- .../AddressDetails/AddressDetails.helpers.tsx | 4 +- .../AddressDetails/AddressDetails.styles.ts | 72 ++++++++++++++++++- .../Details/AddressDetails/AddressDetails.tsx | 4 +- .../Details/AddressDetails/BalanceHistory.tsx | 22 +++--- .../Details/AddressDetails/DirectionChart.tsx | 4 +- src/utils/helpers/chartOptions.ts | 6 +- 6 files changed, 90 insertions(+), 22 deletions(-) diff --git a/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx b/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx index 71ca706c..68e4fd25 100644 --- a/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx +++ b/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx @@ -50,11 +50,11 @@ export const generateLatestTransactions = (transactionsList: Array ), [ADDRESS_TRANSACTION_DIRECTION_KEY]: ( - <> +
{direction === 'Outgoing' ? translate('pages.addressDetails.balanceHistory.sent') : translate('pages.addressDetails.balanceHistory.received')} - +
), [ADDRESS_TRANSACTION_AMOUNT_KEY]: formatNumber(amount, { decimalsLength: 2 }), })); diff --git a/src/pages/Details/AddressDetails/AddressDetails.styles.ts b/src/pages/Details/AddressDetails/AddressDetails.styles.ts index 005fe23d..0d681244 100644 --- a/src/pages/Details/AddressDetails/AddressDetails.styles.ts +++ b/src/pages/Details/AddressDetails/AddressDetails.styles.ts @@ -1,10 +1,27 @@ import styled from 'styled-components/macro'; import { Grid } from '@material-ui/core'; +import themeVariant from '@theme/variants'; + export const TableWrapper = styled(Grid)` position: relative; min-height: 30vh; + .direction-status { + display: inline-flex; + padding: 6px 10px 7px; + font-size: 16px; + font-weight: 400; + line-height: 1; + color: ${themeVariant.custom.white}; + background-color: ${themeVariant.custom.red.dark}; + border-radius: 4px; + + &.incoming { + background-color: ${themeVariant.custom.green.main}; + } + } + .latest-transaction-table { padding-left: 0; padding-right: 0; @@ -35,6 +52,32 @@ export const TableWrapper = styled(Grid)` .mt-15 { margin-top: 15px; } + + @media (min-width: 1024px) { + .col-hash { + .MuiTableCell-root { + padding-left: 7px; + } + } + + .col-amount { + .MuiTableCell-root { + padding-left: 16px; + } + } + + .col-direction { + .MuiTableCell-root { + padding-left: 21px; + } + } + + .col-timestamp { + .MuiTableCell-root { + padding-left: 22px; + } + } + } `; export const Wrapper = styled.div` @@ -215,6 +258,14 @@ export const Heading = styled.div` &.direction-item { padding: 14px 16px; } + + @media (max-width: 390px) { + flex-direction: column; + + .direction-period { + margin-top: 5px; + } + } `; export const HeadingTitle = styled.h4` @@ -256,6 +307,18 @@ export const ChartWrapper = styled.div` .period { width: 100%; margin-top: 10px; + + span, + button { + margin-button: 0; + } + } + } + + @media (max-width: 390px) { + .period { + flex-direction: column; + margin-top: 20px; } } `; @@ -389,13 +452,16 @@ export const ChartItem = styled.div` } .direction-period { + justify-content: flex-end; + .MuiSelect-select { width: auto; } - } - .direction-period { - justify-content: flex-end; + button, + span { + margin-bottom: 0; + } } `; diff --git a/src/pages/Details/AddressDetails/AddressDetails.tsx b/src/pages/Details/AddressDetails/AddressDetails.tsx index 54224f81..f2cd777c 100644 --- a/src/pages/Details/AddressDetails/AddressDetails.tsx +++ b/src/pages/Details/AddressDetails/AddressDetails.tsx @@ -140,7 +140,7 @@ const AddressDetails = () => { - + {addresses ? ( { ) : null} - + diff --git a/src/pages/Details/AddressDetails/BalanceHistory.tsx b/src/pages/Details/AddressDetails/BalanceHistory.tsx index 87206a05..0be87904 100644 --- a/src/pages/Details/AddressDetails/BalanceHistory.tsx +++ b/src/pages/Details/AddressDetails/BalanceHistory.tsx @@ -70,16 +70,18 @@ const BalanceHistory: React.FC = ({ id }) => { /> {translate('pages.historicalStatistics.period')}: - {periods[1].map(period => ( - handlePeriodFilterChange(period)} - type="button" - key={`button-filter-${period}`} - > - {period} - - ))} +
+ {periods[1].map(period => ( + handlePeriodFilterChange(period)} + type="button" + key={`button-filter-${period}`} + > + {period} + + ))} +
diff --git a/src/pages/Details/AddressDetails/DirectionChart.tsx b/src/pages/Details/AddressDetails/DirectionChart.tsx index f0146148..4f6c39b8 100644 --- a/src/pages/Details/AddressDetails/DirectionChart.tsx +++ b/src/pages/Details/AddressDetails/DirectionChart.tsx @@ -86,7 +86,7 @@ const DirectionChart: React.FC = ({ id }) => { return ( - + = ({ id }) => { title={translate('pages.addressDetails.balanceHistory.receivedByMonth')} /> - + Date: Fri, 24 Mar 2023 10:23:15 +0700 Subject: [PATCH 05/12] Responsive improvement --- .../DoughnutChart/DoughnutChart.styles.ts | 39 ++++++++++++++++++- src/components/Hourglass/Hourglass.styles.ts | 2 - src/components/Summary/LineChart.tsx | 18 ++++++++- .../AddressDetails/AddressDetails.styles.ts | 27 ++++++++++++- .../Details/AddressDetails/AddressDetails.tsx | 2 +- .../Details/AddressDetails/DirectionChart.tsx | 4 +- src/pages/Explorer/Explorer.styles.ts | 1 + 7 files changed, 84 insertions(+), 9 deletions(-) diff --git a/src/components/Charts/DoughnutChart/DoughnutChart.styles.ts b/src/components/Charts/DoughnutChart/DoughnutChart.styles.ts index a0d01a87..299e9697 100644 --- a/src/components/Charts/DoughnutChart/DoughnutChart.styles.ts +++ b/src/components/Charts/DoughnutChart/DoughnutChart.styles.ts @@ -40,18 +40,44 @@ export const CardContent = styled(MuiCardContent)` `; export const ChartWrapper = styled(Grid)` - height: 160px; + height: 273px; position: relative; padding: 20px; @media (min-width: 960px) { padding: 10px 20px; height: 273px; + + .echarts-for-react { + height: 253px !important; + } } @media (min-width: 1280px) { padding: 20px; height: 332px; + height: 300px !important; + } + + @media (max-width: 959px) { + height: 273px; + + .echarts-for-react { + height: 234px !important; + } + } + + @media (max-width: 600px) { + height: 190px; + padding: 0; + + &:last-child { + height: auto; + } + + .echarts-for-react { + height: 190px !important; + } } .chartjs-render-monitor { @@ -61,7 +87,6 @@ export const ChartWrapper = styled(Grid)` canvas { position: relative; z-index: 1; - } } `; @@ -87,6 +112,11 @@ export const StakingWrapper = styled.div` flex-direction: column; justify-content: center; align-items: center; + + @media (max-width: 600px) { + padding-top: 10px; + font-size: 32px; + } `; export const StakingTitle = styled.div` @@ -94,6 +124,11 @@ export const StakingTitle = styled.div` font-size: 20px; font-weight: 400; line-height: 1; + + @media (max-width: 600px) { + margin-top: 5px; + font-size: 16px; + } `; export const Link = styled(RouterLink)` diff --git a/src/components/Hourglass/Hourglass.styles.ts b/src/components/Hourglass/Hourglass.styles.ts index e9d6f008..62802e63 100644 --- a/src/components/Hourglass/Hourglass.styles.ts +++ b/src/components/Hourglass/Hourglass.styles.ts @@ -1,8 +1,6 @@ import styled from 'styled-components'; export const Image = styled.div` - margin-left: 12px; - svg { width: 24px; height: 24px; diff --git a/src/components/Summary/LineChart.tsx b/src/components/Summary/LineChart.tsx index b644ee65..ee789870 100644 --- a/src/components/Summary/LineChart.tsx +++ b/src/components/Summary/LineChart.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; import ReactECharts from 'echarts-for-react'; +import * as echarts from 'echarts'; import { useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; @@ -28,6 +29,7 @@ type TLineChartProps = { }; export const LineChart = (props: TLineChartProps): JSX.Element | null => { + const [eChartRef, setEChartRef] = useState(); const { width } = useWindowDimensions(); const { className, @@ -109,6 +111,13 @@ export const LineChart = (props: TLineChartProps): JSX.Element | null => { } }, [dataY]); + useEffect(() => { + if (eChartRef) { + const chartInstance: echarts.ECharts = eChartRef.getEchartsInstance(); + chartInstance.resize(); + } + }, [width]); + const params: TThemeInitOption = { theme: currentTheme, dataX, @@ -134,7 +143,14 @@ export const LineChart = (props: TLineChartProps): JSX.Element | null => { return ( - + { + setEChartRef(e); + }} + /> ); }; diff --git a/src/pages/Details/AddressDetails/AddressDetails.styles.ts b/src/pages/Details/AddressDetails/AddressDetails.styles.ts index 0d681244..47e8440e 100644 --- a/src/pages/Details/AddressDetails/AddressDetails.styles.ts +++ b/src/pages/Details/AddressDetails/AddressDetails.styles.ts @@ -43,7 +43,7 @@ export const TableWrapper = styled(Grid)` } .transaction-table-mask { - min-height: 595px; + min-height: 590px; background: ${props => props.theme.sidebar.menu.background}; box-shadow: 0px 5px 6px rgb(16 16 16 / 6%); border-radius: 0 0 10px 10px; @@ -78,6 +78,10 @@ export const TableWrapper = styled(Grid)` } } } + + @media (max-width: 960px) { + max-width: 98vw; + } `; export const Wrapper = styled.div` @@ -345,8 +349,15 @@ export const SummaryItem = styled.div` cursor: pointer; @media (max-width: 600px) { + width: 100%; margin-right: 0; margin-top: 10px; + padding-bottom: 15px; + border-bottom: 1px solid ${props => props.theme.card.richlist.border}; + + &:last-child { + border-bottom: 0; + } } `; @@ -356,6 +367,10 @@ export const SummaryLabel = styled.span` font-size: 14px; line-height: 1; white-space: nowrap; + + @media (max-width: 960px) { + font-size: 12px; + } `; export const SummaryValue = styled.p` @@ -365,6 +380,10 @@ export const SummaryValue = styled.p` font-weight: 700; overflow: hidden; color: ${props => props.theme.card.color}; + + @media (max-width: 960px) { + font-size: 16px; + } `; export const BalanceHistorySummaryWrapper = styled.div` @@ -420,6 +439,12 @@ export const SummaryIcon = styled.div` border-color: #219653; } } + + @media (max-width: 960px) { + width: 32px; + height: 32px; + padding: 6px; + } `; export const ItemWrapper = styled.div` diff --git a/src/pages/Details/AddressDetails/AddressDetails.tsx b/src/pages/Details/AddressDetails/AddressDetails.tsx index f2cd777c..8655762e 100644 --- a/src/pages/Details/AddressDetails/AddressDetails.tsx +++ b/src/pages/Details/AddressDetails/AddressDetails.tsx @@ -153,7 +153,7 @@ const AddressDetails = () => { className="latest-transaction-table" headerBackground rowHeight={isMobile ? 135 : 45} - tableHeight={isMobile ? 600 : 615} + tableHeight={isMobile ? 600 : 610} customLoading={isLoading} /> ) : null} diff --git a/src/pages/Details/AddressDetails/DirectionChart.tsx b/src/pages/Details/AddressDetails/DirectionChart.tsx index 4f6c39b8..dcd6df1a 100644 --- a/src/pages/Details/AddressDetails/DirectionChart.tsx +++ b/src/pages/Details/AddressDetails/DirectionChart.tsx @@ -86,7 +86,7 @@ const DirectionChart: React.FC = ({ id }) => { return ( - + = ({ id }) => { title={translate('pages.addressDetails.balanceHistory.receivedByMonth')} /> - + Date: Fri, 24 Mar 2023 13:44:19 +0700 Subject: [PATCH 06/12] Create chart to allow user can see total burned and other info --- .env.example | 1 + public/locales/en/translation.json | 20 + src/components/SvgIcon/Balance.tsx | 67 + src/components/SvgIcon/Fire.tsx | 1100 +++++++++++++++++ src/components/SvgIcon/Received.tsx | 68 + .../SvgIcon/__test__/Balance.test.tsx | 35 + .../SvgIcon/__test__/DiscordIcon.test.tsx | 4 +- src/components/SvgIcon/__test__/Fire.test.tsx | 19 + .../SvgIcon/__test__/Medium.test.tsx | 4 +- .../SvgIcon/__test__/RedReceived.test.tsx | 43 + src/components/SvgIcon/__test__/Sent.test.tsx | 19 + .../SvgIcon/__test__/TelegramIcon.test.tsx | 4 +- .../SvgIcon/__test__/TwitterIcon.test.tsx | 6 +- .../__snapshots__/Balance.test.tsx.snap | 5 + .../__test__/__snapshots__/Fire.test.tsx.snap | 3 + .../__snapshots__/RedReceived.test.tsx.snap | 5 + .../__test__/__snapshots__/Sent.test.tsx.snap | 3 + src/hooks/useAddressDetails.ts | 10 + .../AddressDetails/AddressDetails.helpers.tsx | 18 +- .../AddressDetails/AddressDetails.styles.ts | 23 + .../Details/AddressDetails/AddressDetails.tsx | 16 +- .../Details/AddressDetails/BalanceHistory.tsx | 10 +- .../Details/AddressDetails/DirectionChart.tsx | 25 +- src/pages/Details/AddressDetails/Summary.tsx | 31 +- .../HistoricalStatistics/Chart/styles.ts | 4 + src/utils/appInfo.ts | 10 + .../helpers/__test__/statisticsLib.test.ts | 6 +- src/utils/helpers/chartOptions.ts | 4 +- 28 files changed, 1532 insertions(+), 31 deletions(-) create mode 100644 src/components/SvgIcon/Fire.tsx create mode 100644 src/components/SvgIcon/__test__/Balance.test.tsx create mode 100644 src/components/SvgIcon/__test__/Fire.test.tsx create mode 100644 src/components/SvgIcon/__test__/RedReceived.test.tsx create mode 100644 src/components/SvgIcon/__test__/Sent.test.tsx create mode 100644 src/components/SvgIcon/__test__/__snapshots__/Balance.test.tsx.snap create mode 100644 src/components/SvgIcon/__test__/__snapshots__/Fire.test.tsx.snap create mode 100644 src/components/SvgIcon/__test__/__snapshots__/RedReceived.test.tsx.snap create mode 100644 src/components/SvgIcon/__test__/__snapshots__/Sent.test.tsx.snap diff --git a/.env.example b/.env.example index 402605f5..70b285ac 100644 --- a/.env.example +++ b/.env.example @@ -4,3 +4,4 @@ REACT_APP_EXPLORER_WEB_API_URL_DEVNET=https://api-dev.pastel.network.com REACT_APP_EXPLORER_DEFAULT_API_URL=https://api-dev.pastel.network.com REACT_APP_EXPLORER_DEFAULT_CURRENCY_NAME=PSL REACT_APP_EXPLORER_TEST_CURRENCY_NAME=LSP +REACT_APP_EXPLORER_PASTEL_BURN_ADDRESS=tPpasteLBurnAddressXXXXXXXXXXX3wy7u diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index de0d003e..f6f73fb0 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -1099,6 +1099,10 @@ "message": "Total Received ({{currency}})", "description": "Used for the CascadeAndSenseStatistics page" }, + "totalBurned": { + "message": "Total Burned ({{currency}})", + "description": "Used for the CascadeAndSenseStatistics page" + }, "balance": { "message": "Balance ({{currency}})", "description": "Used for the CascadeAndSenseStatistics page" @@ -1115,6 +1119,10 @@ "message": "{{currency}} Address", "description": "Used for the CascadeAndSenseStatistics page" }, + "pastelBurnAddress": { + "message": "Burn Address", + "description": "Used for the CascadeAndSenseStatistics page" + }, "balanceHistory": { "balance": { "message": "Balance", @@ -1135,6 +1143,18 @@ "sentByMonth": { "message": "Sent by Month", "description": "Used for the CascadeAndSenseStatistics page" + }, + "burnedByMonth": { + "message": "Burned by Month", + "description": "Used for the CascadeAndSenseStatistics page" + }, + "burned": { + "message": "Burned", + "description": "Used for the CascadeAndSenseStatistics page" + }, + "totalBurned": { + "message": "Total Burned", + "description": "Used for the CascadeAndSenseStatistics page" } } }, diff --git a/src/components/SvgIcon/Balance.tsx b/src/components/SvgIcon/Balance.tsx index 17e164bb..2b7507b8 100644 --- a/src/components/SvgIcon/Balance.tsx +++ b/src/components/SvgIcon/Balance.tsx @@ -1,3 +1,70 @@ +export const BurnBalance = () => { + return ( + + + + + + + + + + + + ); +}; + const Balance = () => { return ( diff --git a/src/components/SvgIcon/Fire.tsx b/src/components/SvgIcon/Fire.tsx new file mode 100644 index 00000000..498b8ed3 --- /dev/null +++ b/src/components/SvgIcon/Fire.tsx @@ -0,0 +1,1100 @@ +const Fire = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Fire; diff --git a/src/components/SvgIcon/Received.tsx b/src/components/SvgIcon/Received.tsx index ecf83840..5ded8ddd 100644 --- a/src/components/SvgIcon/Received.tsx +++ b/src/components/SvgIcon/Received.tsx @@ -1,3 +1,71 @@ +export const RedReceived = () => { + return ( + + + + + + + + + + + + + ); +}; + const Received = () => { return ( diff --git a/src/components/SvgIcon/__test__/Balance.test.tsx b/src/components/SvgIcon/__test__/Balance.test.tsx new file mode 100644 index 00000000..434dbdf6 --- /dev/null +++ b/src/components/SvgIcon/__test__/Balance.test.tsx @@ -0,0 +1,35 @@ +import { shallow } from 'enzyme'; + +import Balance, { BurnBalance } from '../Balance'; + +describe('components/SvgIcon/Balance', () => { + const wrapper = shallow(); + + test('renders correctly', () => { + expect(wrapper).toMatchSnapshot(); + }); + + test('should render ', () => { + expect(wrapper.find('svg').length).toBe(1); + }); + + test('should render ', () => { + expect(wrapper.find('path').length).toBe(9); + }); +}); + +describe('components/SvgIcon/BurnBalance', () => { + const wrapper = shallow(); + + test('renders correctly', () => { + expect(wrapper).toMatchSnapshot(); + }); + + test('should render ', () => { + expect(wrapper.find('svg').length).toBe(1); + }); + + test('should render ', () => { + expect(wrapper.find('path').length).toBe(9); + }); +}); diff --git a/src/components/SvgIcon/__test__/DiscordIcon.test.tsx b/src/components/SvgIcon/__test__/DiscordIcon.test.tsx index 4bb956dd..dd4c0196 100644 --- a/src/components/SvgIcon/__test__/DiscordIcon.test.tsx +++ b/src/components/SvgIcon/__test__/DiscordIcon.test.tsx @@ -10,10 +10,10 @@ describe('components/SvgIcon/DiscordIcon', () => { }); test('should render ', () => { - expect(wrapper.find('svg').length).toBeGreaterThanOrEqual(1); + expect(wrapper.find('svg').length).toBe(1); }); test('should render ', () => { - expect(wrapper.find('path').length).toBeGreaterThanOrEqual(1); + expect(wrapper.find('path').length).toBe(1); }); }); diff --git a/src/components/SvgIcon/__test__/Fire.test.tsx b/src/components/SvgIcon/__test__/Fire.test.tsx new file mode 100644 index 00000000..5487c742 --- /dev/null +++ b/src/components/SvgIcon/__test__/Fire.test.tsx @@ -0,0 +1,19 @@ +import { shallow } from 'enzyme'; + +import Fire from '../Fire'; + +describe('components/SvgIcon/Fire', () => { + const wrapper = shallow(); + + test('renders correctly', () => { + expect(wrapper).toMatchSnapshot(); + }); + + test('should render ', () => { + expect(wrapper.find('svg').length).toBe(1); + }); + + test('should render ', () => { + expect(wrapper.find('path').length).toBe(273); + }); +}); diff --git a/src/components/SvgIcon/__test__/Medium.test.tsx b/src/components/SvgIcon/__test__/Medium.test.tsx index 1947681d..bb68c4fc 100644 --- a/src/components/SvgIcon/__test__/Medium.test.tsx +++ b/src/components/SvgIcon/__test__/Medium.test.tsx @@ -10,10 +10,10 @@ describe('components/SvgIcon/Medium', () => { }); test('should render ', () => { - expect(wrapper.find('svg').length).toBeGreaterThanOrEqual(1); + expect(wrapper.find('svg').length).toBe(1); }); test('should render ', () => { - expect(wrapper.find('path').length).toBeGreaterThanOrEqual(1); + expect(wrapper.find('path').length).toBe(1); }); }); diff --git a/src/components/SvgIcon/__test__/RedReceived.test.tsx b/src/components/SvgIcon/__test__/RedReceived.test.tsx new file mode 100644 index 00000000..00a0ea19 --- /dev/null +++ b/src/components/SvgIcon/__test__/RedReceived.test.tsx @@ -0,0 +1,43 @@ +import { shallow } from 'enzyme'; + +import Received, { RedReceived } from '../Received'; + +describe('components/SvgIcon/Received', () => { + const wrapper = shallow(); + + test('renders correctly', () => { + expect(wrapper).toMatchSnapshot(); + }); + + test('should render ', () => { + expect(wrapper.find('svg').length).toBe(1); + }); + + test('should render ', () => { + expect(wrapper.find('rect').length).toBe(1); + }); + + test('should render ', () => { + expect(wrapper.find('path').length).toBe(9); + }); +}); + +describe('components/SvgIcon/Received/RedReceived', () => { + const wrapper = shallow(); + + test('renders correctly', () => { + expect(wrapper).toMatchSnapshot(); + }); + + test('should render ', () => { + expect(wrapper.find('svg').length).toBe(1); + }); + + test('should render ', () => { + expect(wrapper.find('rect').length).toBe(1); + }); + + test('should render ', () => { + expect(wrapper.find('path').length).toBe(9); + }); +}); diff --git a/src/components/SvgIcon/__test__/Sent.test.tsx b/src/components/SvgIcon/__test__/Sent.test.tsx new file mode 100644 index 00000000..5e5ee015 --- /dev/null +++ b/src/components/SvgIcon/__test__/Sent.test.tsx @@ -0,0 +1,19 @@ +import { shallow } from 'enzyme'; + +import Sent from '../Sent'; + +describe('components/SvgIcon/Sent', () => { + const wrapper = shallow(); + + test('renders correctly', () => { + expect(wrapper).toMatchSnapshot(); + }); + + test('should render ', () => { + expect(wrapper.find('svg').length).toBe(1); + }); + + test('should render ', () => { + expect(wrapper.find('path').length).toBe(9); + }); +}); diff --git a/src/components/SvgIcon/__test__/TelegramIcon.test.tsx b/src/components/SvgIcon/__test__/TelegramIcon.test.tsx index 18046056..87aa7a9e 100644 --- a/src/components/SvgIcon/__test__/TelegramIcon.test.tsx +++ b/src/components/SvgIcon/__test__/TelegramIcon.test.tsx @@ -10,10 +10,10 @@ describe('components/SvgIcon/TelegramIcon', () => { }); test('should render ', () => { - expect(wrapper.find('svg').length).toBeGreaterThanOrEqual(1); + expect(wrapper.find('svg').length).toBe(1); }); test('should render ', () => { - expect(wrapper.find('path').length).toBeGreaterThanOrEqual(1); + expect(wrapper.find('path').length).toBe(1); }); }); diff --git a/src/components/SvgIcon/__test__/TwitterIcon.test.tsx b/src/components/SvgIcon/__test__/TwitterIcon.test.tsx index 43dcf556..f3505576 100644 --- a/src/components/SvgIcon/__test__/TwitterIcon.test.tsx +++ b/src/components/SvgIcon/__test__/TwitterIcon.test.tsx @@ -10,14 +10,14 @@ describe('components/SvgIcon/TwitterIcon', () => { }); test('should render ', () => { - expect(wrapper.find('svg').length).toBeGreaterThanOrEqual(1); + expect(wrapper.find('svg').length).toBe(1); }); test('should render ', () => { - expect(wrapper.find('g').length).toBeGreaterThanOrEqual(1); + expect(wrapper.find('g').length).toBe(1); }); test('should render ', () => { - expect(wrapper.find('path').length).toBeGreaterThanOrEqual(1); + expect(wrapper.find('path').length).toBe(1); }); }); diff --git a/src/components/SvgIcon/__test__/__snapshots__/Balance.test.tsx.snap b/src/components/SvgIcon/__test__/__snapshots__/Balance.test.tsx.snap new file mode 100644 index 00000000..f2229167 --- /dev/null +++ b/src/components/SvgIcon/__test__/__snapshots__/Balance.test.tsx.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`components/SvgIcon/Balance renders correctly 1`] = `ShallowWrapper {}`; + +exports[`components/SvgIcon/BurnBalance renders correctly 1`] = `ShallowWrapper {}`; diff --git a/src/components/SvgIcon/__test__/__snapshots__/Fire.test.tsx.snap b/src/components/SvgIcon/__test__/__snapshots__/Fire.test.tsx.snap new file mode 100644 index 00000000..6218981e --- /dev/null +++ b/src/components/SvgIcon/__test__/__snapshots__/Fire.test.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`components/SvgIcon/Fire renders correctly 1`] = `ShallowWrapper {}`; diff --git a/src/components/SvgIcon/__test__/__snapshots__/RedReceived.test.tsx.snap b/src/components/SvgIcon/__test__/__snapshots__/RedReceived.test.tsx.snap new file mode 100644 index 00000000..f8ad9124 --- /dev/null +++ b/src/components/SvgIcon/__test__/__snapshots__/RedReceived.test.tsx.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`components/SvgIcon/Received renders correctly 1`] = `ShallowWrapper {}`; + +exports[`components/SvgIcon/Received/RedReceived renders correctly 1`] = `ShallowWrapper {}`; diff --git a/src/components/SvgIcon/__test__/__snapshots__/Sent.test.tsx.snap b/src/components/SvgIcon/__test__/__snapshots__/Sent.test.tsx.snap new file mode 100644 index 00000000..a90c2138 --- /dev/null +++ b/src/components/SvgIcon/__test__/__snapshots__/Sent.test.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`components/SvgIcon/Sent renders correctly 1`] = `ShallowWrapper {}`; diff --git a/src/hooks/useAddressDetails.ts b/src/hooks/useAddressDetails.ts index 71a8dfbb..e6d2c38a 100644 --- a/src/hooks/useAddressDetails.ts +++ b/src/hooks/useAddressDetails.ts @@ -7,6 +7,8 @@ import { axiosGet } from '@utils/helpers/useFetch/useFetch'; import * as URLS from '@utils/constants/urls'; import { IAddress } from '@utils/types/IAddress'; import { formattedDate } from '@utils/helpers/date/date'; +import { isPastelBurnAddress } from '@utils/appInfo'; +import { translate } from '@utils/helpers/i18n'; import { SortDirectionsType } from '@components/InfinityTable/InfinityTable'; import { DATA_FETCH_LIMIT } from '@pages/Details/AddressDetails/AddressDetails.helpers'; @@ -70,6 +72,14 @@ export function useLatestTransactions( csvData.push({ transactionHash: data[i].data[j].transactionHash, amount: data[i].data[j].amount, + direction: + data[i].data[j].direction === 'Outgoing' + ? translate('pages.addressDetails.balanceHistory.sent') + : translate( + `pages.addressDetails.balanceHistory.${ + isPastelBurnAddress(id) ? 'burned' : 'received' + }`, + ), timestamp: formattedDate(data[i].data[j].timestamp, { dayName: false, }), diff --git a/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx b/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx index 68e4fd25..08e82912 100644 --- a/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx +++ b/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx @@ -13,6 +13,7 @@ import { formatNumber } from '@utils/helpers/formatNumbers/formatNumbers'; import { formatAddress } from '@utils/helpers/format'; import { TChartStatisticsResponse } from '@utils/types/IStatistics'; import { translate } from '@utils/helpers/i18n'; +import { isPastelBurnAddress } from '@utils/appInfo'; import { ADDRESS_TRANSACTION_TIMESTAMP_KEY, @@ -32,7 +33,10 @@ export const DATA_FETCH_LIMIT = 20; export const DATA_OFFSET = 0; export const DATA_DEFAULT_SORT = 'DESC'; -export const generateLatestTransactions = (transactionsList: Array): RowsProps[] => +export const generateLatestTransactions = ( + transactionsList: Array, + address: string, +): RowsProps[] => transactionsList.map(({ amount, timestamp, transactionHash, direction }) => ({ [ADDRESS_TRANSACTION_TIMESTAMP_KEY]: formattedDate(timestamp, { dayName: false, @@ -50,10 +54,18 @@ export const generateLatestTransactions = (transactionsList: Array ), [ADDRESS_TRANSACTION_DIRECTION_KEY]: ( -
+
{direction === 'Outgoing' ? translate('pages.addressDetails.balanceHistory.sent') - : translate('pages.addressDetails.balanceHistory.received')} + : translate( + `pages.addressDetails.balanceHistory.${ + isPastelBurnAddress(address) ? 'burned' : 'received' + }`, + )}
), [ADDRESS_TRANSACTION_AMOUNT_KEY]: formatNumber(amount, { decimalsLength: 2 }), diff --git a/src/pages/Details/AddressDetails/AddressDetails.styles.ts b/src/pages/Details/AddressDetails/AddressDetails.styles.ts index 47e8440e..207ca48a 100644 --- a/src/pages/Details/AddressDetails/AddressDetails.styles.ts +++ b/src/pages/Details/AddressDetails/AddressDetails.styles.ts @@ -20,6 +20,10 @@ export const TableWrapper = styled(Grid)` &.incoming { background-color: ${themeVariant.custom.green.main}; } + + &.burned { + background-color: ${themeVariant.custom.red.dark}; + } } .latest-transaction-table { @@ -190,11 +194,16 @@ export const Wrapper = styled.div` export const AddressTitleBlock = styled.div` display: flex; + width: 100%; span { margin-left: 5px; } + svg { + width: 18px; + } + ${props => props.theme.breakpoints.down(1024)} { span { max-width: calc(100vw - 145px); @@ -233,6 +242,11 @@ export const TitleWrapper = styled('div')` margin-right: 0; padding: 6px 24px; font-size: 14px; + + @media (max-width: 768px) { + padding: 4px 20px; + font-size: 12px; + } } `; @@ -438,6 +452,10 @@ export const SummaryIcon = styled.div` &.received { border-color: #219653; } + + &.burn { + border-color: #e94830; + } } @media (max-width: 960px) { @@ -503,3 +521,8 @@ export const LoadingText = styled.div` margin-top: 0; font-size: 20px; `; + +export const FireIcon = styled.div` + display: inline-flex; + align-items: center; +`; diff --git a/src/pages/Details/AddressDetails/AddressDetails.tsx b/src/pages/Details/AddressDetails/AddressDetails.tsx index 8655762e..387faaa2 100644 --- a/src/pages/Details/AddressDetails/AddressDetails.tsx +++ b/src/pages/Details/AddressDetails/AddressDetails.tsx @@ -3,6 +3,7 @@ import { useParams } from 'react-router-dom'; import { CSVLink } from 'react-csv'; import { CircularProgress, Grid } from '@material-ui/core'; import Box from '@material-ui/core/Box'; +import Tooltip from '@material-ui/core/Tooltip'; import InfinityTable, { SortDirectionsType, @@ -10,9 +11,10 @@ import InfinityTable, { } from '@components/InfinityTable/InfinityTable'; import { translate } from '@utils/helpers/i18n'; -import { getCurrencyName } from '@utils/appInfo'; +import { getCurrencyName, isPastelBurnAddress } from '@utils/appInfo'; import { eChartLineStyles } from '@pages/HistoricalStatistics/Chart/styles'; import * as TableStyles from '@components/Table/Table.styles'; +import Fire from '@components/SvgIcon/Fire'; import { useLatestTransactions } from '@hooks/useAddressDetails'; import { @@ -41,6 +43,7 @@ const AddressDetails = () => { label: translate('pages.addressDetails.amount', { currency: getCurrencyName() }), key: 'amount', }, + { label: translate('pages.addressDetails.direction'), key: 'direction' }, { label: translate('pages.addressDetails.timestamp'), key: 'timestamp' }, ]; @@ -92,11 +95,20 @@ const AddressDetails = () => { }; }, []); + const isBurnAddress = isPastelBurnAddress(id); + const generateAddTitle = () => { return ( {translate('pages.addressDetails.address', { currency: getCurrencyName() })}:{' '} {id} + {isBurnAddress ? ( + + + + + + ) : null} ); }; @@ -146,7 +158,7 @@ const AddressDetails = () => { title={generateTitle()} sortBy={apiParams.sortBy} sortDirection={apiParams.sortDirection} - rows={generateLatestTransactions(addresses)} + rows={generateLatestTransactions(addresses, id)} columns={columns} onBottomReach={handleFetchMoreTransactions} onHeaderClick={handleSort} diff --git a/src/pages/Details/AddressDetails/BalanceHistory.tsx b/src/pages/Details/AddressDetails/BalanceHistory.tsx index 0be87904..83b99075 100644 --- a/src/pages/Details/AddressDetails/BalanceHistory.tsx +++ b/src/pages/Details/AddressDetails/BalanceHistory.tsx @@ -6,6 +6,7 @@ import { translate } from '@utils/helpers/i18n'; import { LineChart } from '@components/Summary/LineChart'; import { PeriodTypes } from '@utils/helpers/statisticsLib'; import { useBalanceHistory } from '@hooks/useAddressDetails'; +import { isPastelBurnAddress } from '@utils/appInfo'; import * as ChartStyles from '@pages/HistoricalStatistics/Chart/Chart.styles'; import Summary from './Summary'; @@ -17,6 +18,7 @@ interface IBalanceHistoryProps { } const BalanceHistory: React.FC = ({ id }) => { + const isBurnAddress = isPastelBurnAddress(id); const [selectedPeriod, setSelectedPeriod] = useState(periods[1][0]); const [selectedChartType, setSelectedChartType] = useState('balance'); const { isLoading, balance, incoming, outgoing } = useBalanceHistory(id, selectedPeriod); @@ -49,6 +51,10 @@ const BalanceHistory: React.FC = ({ id }) => { } const getChartColor = () => { + if (isBurnAddress) { + return '#E94830'; + } + switch (selectedChartType) { case 'sent': return '#E94830'; @@ -99,7 +105,9 @@ const BalanceHistory: React.FC = ({ id }) => { disableClick className="line-chart" period={selectedPeriod} - seriesName={`pages.addressDetails.balanceHistory.${selectedChartType}`} + seriesName={`pages.addressDetails.balanceHistory.${ + isBurnAddress && selectedChartType === 'received' ? 'totalBurned' : selectedChartType + }`} chartColor={getChartColor()} /> )} diff --git a/src/pages/Details/AddressDetails/DirectionChart.tsx b/src/pages/Details/AddressDetails/DirectionChart.tsx index dcd6df1a..9494e1cb 100644 --- a/src/pages/Details/AddressDetails/DirectionChart.tsx +++ b/src/pages/Details/AddressDetails/DirectionChart.tsx @@ -8,6 +8,7 @@ import { useDirection } from '@hooks/useAddressDetails'; import { translate } from '@utils/helpers/i18n'; import { periods } from '@utils/constants/statistics'; import { PeriodTypes } from '@utils/helpers/statisticsLib'; +import { isPastelBurnAddress } from '@utils/appInfo'; import * as ChartStyles from '@pages/HistoricalStatistics/Chart/Chart.styles'; @@ -23,9 +24,18 @@ interface IDirectionItemProps { direction: string; chartName: string; title: string; + seriesName?: string; + chartColor?: string; } -const DirectionItem: React.FC = ({ id, direction, chartName, title }) => { +const DirectionItem: React.FC = ({ + id, + direction, + chartName, + title, + seriesName, + chartColor, +}) => { const [period, setPeriod] = useState(periods[10][0]); const { data, isLoading } = useDirection(id, period, direction); @@ -75,6 +85,8 @@ const DirectionItem: React.FC = ({ id, direction, chartName offset={0} disableClick className="line-chart" + seriesName={seriesName} + chartColor={chartColor} /> )} @@ -83,6 +95,7 @@ const DirectionItem: React.FC = ({ id, direction, chartName }; const DirectionChart: React.FC = ({ id }) => { + const isBurnAddress = isPastelBurnAddress(id); return ( @@ -91,7 +104,15 @@ const DirectionChart: React.FC = ({ id }) => { id={id} direction="Incoming" chartName="directionIncoming" - title={translate('pages.addressDetails.balanceHistory.receivedByMonth')} + title={translate( + `pages.addressDetails.balanceHistory.${ + isBurnAddress ? 'burnedByMonth' : 'receivedByMonth' + }`, + )} + seriesName={`pages.addressDetails.balanceHistory.${ + isBurnAddress ? 'burnedByMonth' : 'receivedByMonth' + }`} + chartColor={isBurnAddress ? '#E94830' : undefined} /> diff --git a/src/pages/Details/AddressDetails/Summary.tsx b/src/pages/Details/AddressDetails/Summary.tsx index d1b19b59..3c32d0ed 100644 --- a/src/pages/Details/AddressDetails/Summary.tsx +++ b/src/pages/Details/AddressDetails/Summary.tsx @@ -1,12 +1,12 @@ import { Skeleton } from '@material-ui/lab'; import { formatNumber } from '@utils/helpers/formatNumbers/formatNumbers'; -import { getCurrencyName } from '@utils/appInfo'; +import { getCurrencyName, isPastelBurnAddress } from '@utils/appInfo'; import { translate } from '@utils/helpers/i18n'; import useAddressDetails from '@hooks/useAddressDetails'; -import Balance from '@components/SvgIcon/Balance'; +import Balance, { BurnBalance } from '@components/SvgIcon/Balance'; import Sent from '@components/SvgIcon/Sent'; -import Received from '@components/SvgIcon/Received'; +import Received, { RedReceived } from '@components/SvgIcon/Received'; import * as Styles from './AddressDetails.styles'; @@ -18,6 +18,7 @@ interface ISummaryProps { } const Summary: React.FC = ({ id, onChange, selectedChart, isBalanceLoading }) => { + const isBurnAddress = isPastelBurnAddress(id); const { outgoingSum, incomingSum, isLoading } = useAddressDetails(id); return ( @@ -26,8 +27,12 @@ const Summary: React.FC = ({ id, onChange, selectedChart, isBalan className={isBalanceLoading || isLoading ? 'disable' : ''} onClick={() => onChange('balance')} > - - + + {isBurnAddress ? : } @@ -48,7 +53,11 @@ const Summary: React.FC = ({ id, onChange, selectedChart, isBalan className={isBalanceLoading || isLoading ? 'disable' : ''} onClick={() => onChange('sent')} > - + @@ -68,12 +77,16 @@ const Summary: React.FC = ({ id, onChange, selectedChart, isBalan className={isBalanceLoading || isLoading ? 'disable' : ''} onClick={() => onChange('received')} > - - + + {isBurnAddress ? : } - {translate('pages.addressDetails.totalReceived', { + {translate(`pages.addressDetails.${isBurnAddress ? 'totalBurned' : 'totalReceived'}`, { currency: getCurrencyName(), })} diff --git a/src/pages/HistoricalStatistics/Chart/styles.ts b/src/pages/HistoricalStatistics/Chart/styles.ts index 7124b261..164586dd 100644 --- a/src/pages/HistoricalStatistics/Chart/styles.ts +++ b/src/pages/HistoricalStatistics/Chart/styles.ts @@ -27,6 +27,10 @@ export const eChartLineStyles = makeStyles((theme: TAppTheme) => ({ backgroundColor: '#0971DC', borderColor: '#0971DC', }, + [theme.breakpoints.down('sm')]: { + padding: '4px 20px', + fontSize: '12px', + }, }, reactECharts: { width: '100%', diff --git a/src/utils/appInfo.ts b/src/utils/appInfo.ts index 967fe766..de623fd8 100644 --- a/src/utils/appInfo.ts +++ b/src/utils/appInfo.ts @@ -1,5 +1,6 @@ export const DEFAULT_CURRENCY = process.env.REACT_APP_EXPLORER_DEFAULT_CURRENCY_NAME || 'PSL'; export const TEST_CURRENCY_NAME = process.env.REACT_APP_EXPLORER_TEST_CURRENCY_NAME || 'LSP'; +const PASTEL_BURN_ADDRESS = process.env.REACT_APP_EXPLORER_PASTEL_BURN_ADDRESS || ''; export const getCurrencyName = (): string => { const persist = localStorage.getItem('persist:root'); @@ -15,3 +16,12 @@ export const getCurrencyName = (): string => { return DEFAULT_CURRENCY; }; + +export const isPastelBurnAddress = (address: string): boolean => { + if (PASTEL_BURN_ADDRESS) { + const pastelBurnAddresses = PASTEL_BURN_ADDRESS.split(','); + return pastelBurnAddresses.includes(address); + } + + return false; +}; diff --git a/src/utils/helpers/__test__/statisticsLib.test.ts b/src/utils/helpers/__test__/statisticsLib.test.ts index 2b13c764..cf81a016 100644 --- a/src/utils/helpers/__test__/statisticsLib.test.ts +++ b/src/utils/helpers/__test__/statisticsLib.test.ts @@ -847,7 +847,7 @@ describe('utils/helpers/statisticsLib', () => { test('generatePeriodToDropdownOptions should works correctly', () => { i18next.t = jest.fn().mockImplementation((...arg) => { - return `Last ${arg[1].period}`; + return arg[1] ? `Last ${arg[1]?.period}` : 'max'; }); const results: OptionsProps[] = [ @@ -868,7 +868,7 @@ describe('utils/helpers/statisticsLib', () => { value: '1y', }, { - name: 'Last max', + name: 'max', value: 'max', }, ]; @@ -956,7 +956,7 @@ describe('utils/helpers/statisticsLib', () => { expect(generateXAxisInterval('1d', '14d', dataX, 1500)).toEqual('auto'); expect(generateXAxisInterval('1d', '14d', dataX1, 1500)).toEqual(2); expect(generateXAxisInterval('1d', '30d', dataX2, 1500)).toEqual(1); - expect(generateXAxisInterval('1d', '30d', dataX3, 1500)).toEqual(3); + expect(generateXAxisInterval('1d', '30d', dataX3, 1500)).toEqual(4); expect(generateXAxisInterval('1d', '90d', dataX3, 1500)).toEqual(3); expect(generateXAxisInterval('1d', '180d', dataX3, 1500)).toEqual(3); expect(generateXAxisInterval('1d', '60d', dataX3, 1500)).toEqual(3); diff --git a/src/utils/helpers/chartOptions.ts b/src/utils/helpers/chartOptions.ts index 5a303935..019456db 100644 --- a/src/utils/helpers/chartOptions.ts +++ b/src/utils/helpers/chartOptions.ts @@ -3748,7 +3748,7 @@ export function getSummaryThemeUpdateOption(args: TThemeInitOption): EChartsOpti textStyle: { color: theme?.color, }, - color: ['#219653'], + color: [chartColor || '#219653'], grid: { top: 10, right: 0, @@ -3814,7 +3814,7 @@ export function getSummaryThemeUpdateOption(args: TThemeInitOption): EChartsOpti }, }, series: { - name: translate('pages.addressDetails.balanceHistory.receivedByMonth') || '', + name: translate(seriesName || 'pages.addressDetails.balanceHistory.receivedByMonth') || '', type: 'bar', data: dataY?.map((d, index) => { return { From 4fba8d867e47d2a0f1f7849eb2cc4c07174b37a1 Mon Sep 17 00:00:00 2001 From: Tuan Nguyen Date: Fri, 24 Mar 2023 16:15:53 +0700 Subject: [PATCH 07/12] Added burned page --- public/locales/en/translation.json | 26 +++ src/hooks/useBurned.ts | 30 +++ src/pages/Burned/Address.tsx | 42 ++++ src/pages/Burned/Burned.styles.ts | 76 +++++++ src/pages/Burned/Burned.tsx | 21 ++ src/pages/Burned/Overview.tsx | 204 ++++++++++++++++++ .../AddressDetails/AddressDetails.helpers.tsx | 12 +- .../AddressDetails/AddressDetails.styles.ts | 1 + src/routes/index.tsx | 11 + src/utils/appInfo.ts | 9 + src/utils/constants/routes.ts | 1 + src/utils/constants/urls.ts | 2 + src/utils/helpers/chartOptions.ts | 108 ++++++++++ 13 files changed, 539 insertions(+), 4 deletions(-) create mode 100644 src/hooks/useBurned.ts create mode 100644 src/pages/Burned/Address.tsx create mode 100644 src/pages/Burned/Burned.styles.ts create mode 100644 src/pages/Burned/Burned.tsx create mode 100644 src/pages/Burned/Overview.tsx diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index f6f73fb0..b75a5c00 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -2423,6 +2423,28 @@ "message": "Not Yet Activated", "description": "Used for the TicketsType page" } + }, + "burned": { + "title": { + "message": "Total {{currency}} Burned", + "description": "Used for the TicketsType page" + }, + "totalBurned": { + "message": "Total Burned ({{currency}})", + "description": "Used for the CascadeAndSenseStatistics page" + }, + "burnedByMonth": { + "message": "Burned by Month", + "description": "Used for the CascadeAndSenseStatistics page" + }, + "burnedAddresses": { + "message": "Burned Addresses", + "description": "Used for the CascadeAndSenseStatistics page" + }, + "address": { + "message": "Address", + "description": "Used for the CascadeAndSenseStatistics page" + } } }, "routes": { @@ -2601,6 +2623,10 @@ "ticketsType": { "message": "Tickets Type", "description": "Used for the routes" + }, + "burned": { + "message": "Burned", + "description": "Used for the routes" } }, "chartOptions": { diff --git a/src/hooks/useBurned.ts b/src/hooks/useBurned.ts new file mode 100644 index 00000000..63517372 --- /dev/null +++ b/src/hooks/useBurned.ts @@ -0,0 +1,30 @@ +import useSWRInfinite from 'swr/infinite'; + +import { TChartStatisticsResponse } from '@utils/types/IStatistics'; +import { SWR_OPTIONS } from '@utils/constants/statistics'; +import { axiosGet } from '@utils/helpers/useFetch/useFetch'; +import * as URLS from '@utils/constants/urls'; + +export function useBurnedByMonth(period: string) { + const { data, isLoading } = useSWRInfinite>( + () => `${URLS.GET_BURNED_BY_MONTH}?period=${period}`, + axiosGet, + SWR_OPTIONS, + ); + return { + data: data ? data[0] : [], + isLoading, + }; +} + +export function useSummary(period: string) { + const { data, isLoading } = useSWRInfinite<{ + data: Array; + totalBurned: number; + }>(() => `${URLS.GET_TOTAL_BURNED}?period=${period}`, axiosGet, SWR_OPTIONS); + return { + data: data ? data[0].data : [], + isLoading, + totalBurned: data ? data[0].totalBurned : 0, + }; +} diff --git a/src/pages/Burned/Address.tsx b/src/pages/Burned/Address.tsx new file mode 100644 index 00000000..3adf87ed --- /dev/null +++ b/src/pages/Burned/Address.tsx @@ -0,0 +1,42 @@ +import { AlertTitle } from '@material-ui/lab'; + +import RouterLink from '@components/RouterLink/RouterLink'; +import { getPastelBurnAddresses } from '@utils/appInfo'; +import { translate } from '@utils/helpers/i18n'; +import * as ROUTES from '@utils/constants/routes'; + +import * as TransactionDetailsStyles from '@pages/Details/TransactionDetails/TransactionDetails.styles'; + +import * as Styles from './Burned.styles'; + +const Address = () => { + const addresses = getPastelBurnAddresses(); + + const generateLatestTransactions = () => { + return addresses.map(address => ( +
  • + - + +
  • + )); + }; + + return ( + + + + {translate('pages.burned.burnedAddresses')}: +
      {generateLatestTransactions()}
    +
    +
    +
    + ); +}; + +export default Address; diff --git a/src/pages/Burned/Burned.styles.ts b/src/pages/Burned/Burned.styles.ts new file mode 100644 index 00000000..1be4603a --- /dev/null +++ b/src/pages/Burned/Burned.styles.ts @@ -0,0 +1,76 @@ +import styled from 'styled-components/macro'; + +export const BurnedWrapper = styled.div` + width: 100%; + + .table-title { + margin: 0; + } + + .summary-item { + cursor: default; + } +`; + +export const OverviewWrapper = styled.div` + width: 100%; + + .chart-box { + min-height: 330px; + + @media (max-width: 480px) { + min-height: 250px; + } + } + + .burned-by-month { + padding: 20px; + + .echarts-for-react { + height: 290px !important; + + @media (max-width: 480px) { + height: 210px !important; + } + } + } +`; + +export const AddressWrapper = styled.div` + width: 100%; + margin-bottom: 20px; + + .MuiAlert-standardInfo { + border-radius: 10px; + } + + .MuiAlert-message { + width: 100%; + } + + .address-list { + margin: 0; + padding-left: 10px; + list-style: none; + + a { + display: inline-block; + white-space: nowrap; + max-width: 74vw; + } + + span { + margin-right: 5px; + } + + .address-item { + display: flex; + align-items: center; + margin-bottom: 10px; + + &:last-child { + margin-bottom: 0; + } + } + } +`; diff --git a/src/pages/Burned/Burned.tsx b/src/pages/Burned/Burned.tsx new file mode 100644 index 00000000..ce0c4608 --- /dev/null +++ b/src/pages/Burned/Burned.tsx @@ -0,0 +1,21 @@ +import Grid from '@material-ui/core/Grid'; +// import CircularProgress from '@material-ui/core/CircularProgress'; + +import Overview from './Overview'; +import Address from './Address'; +import * as Styles from './Burned.styles'; + +const Burned = () => { + return ( + +
    + + + + + + + ); +}; + +export default Burned; diff --git a/src/pages/Burned/Overview.tsx b/src/pages/Burned/Overview.tsx new file mode 100644 index 00000000..fac30526 --- /dev/null +++ b/src/pages/Burned/Overview.tsx @@ -0,0 +1,204 @@ +import { useState } from 'react'; +import { Skeleton } from '@material-ui/lab'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import Grid from '@material-ui/core/Grid'; +import Box from '@material-ui/core/Box'; + +import { LineChart } from '@components/Summary/LineChart'; +import { RedReceived } from '@components/SvgIcon/Received'; +import { translate } from '@utils/helpers/i18n'; +import { formatNumber } from '@utils/helpers/formatNumbers/formatNumbers'; +import { getCurrencyName } from '@utils/appInfo'; +import { periods } from '@utils/constants/statistics'; +import { PeriodTypes } from '@utils/helpers/statisticsLib'; +import { useBurnedByMonth, useSummary } from '@hooks/useBurned'; +import { + transformChartData, + transformDirectionChartData, +} from '@pages/Details/AddressDetails/AddressDetails.helpers'; + +import * as TableStyles from '@components/Table/Table.styles'; +import * as AddressDetailsStyles from '@pages/Details/AddressDetails/AddressDetails.styles'; +import * as ChartStyles from '@pages/HistoricalStatistics/Chart/Chart.styles'; + +import * as Styles from './Burned.styles'; + +const BurnedByMonth = () => { + const [period, setPeriod] = useState(periods[10][0]); + const { isLoading, data } = useBurnedByMonth(period); + + const getActivePeriodButtonStyle = (_period: string): string => { + if (period === _period) { + return 'active'; + } + return ''; + }; + + const handlePeriodChange = (_period: string) => { + setPeriod(_period as PeriodTypes); + }; + const chartData = transformDirectionChartData(data); + + return ( + + + + + {translate('pages.burned.burnedByMonth')} + + + {translate('pages.historicalStatistics.period')}: + {periods[10].map(_period => ( + handlePeriodChange(_period)} + type="button" + key={`burned-filter-${_period}`} + > + {_period} + + ))} + + + + + {isLoading ? ( + + + + {translate('common.loadingData')} + + + ) : ( + + )} + + + ); +}; + +const Summary = () => { + const [selectedPeriod, setSelectedPeriod] = useState(periods[1][0]); + const { isLoading, data, totalBurned } = useSummary(selectedPeriod); + + const handlePeriodFilterChange = (value: PeriodTypes) => { + setSelectedPeriod(value); + }; + + const getActivePeriodButtonStyle = (period: string): string => { + if (selectedPeriod === period) { + return 'active'; + } + return ''; + }; + const chartData = transformChartData(data); + + return ( + <> + + + + + {translate('pages.burned.title', { currency: getCurrencyName() })} + + + + + + + + + + + + + + {translate(`pages.burned.totalBurned`, { + currency: getCurrencyName(), + })} + + + {isLoading ? ( + + ) : ( + formatNumber(totalBurned, { decimalsLength: 2 }) + )} + + + + + + + {translate('pages.historicalStatistics.period')}: +
    + {periods[1].map(period => ( + handlePeriodFilterChange(period)} + type="button" + key={`button-filter-${period}`} + > + {period} + + ))} +
    +
    +
    +
    + + {isLoading ? ( + + + + {translate('common.loadingData')} + + + ) : ( + + )} + +
    +
    +
    + + ); +}; + +const Overview = () => { + return ( + + + + + + + + + + + ); +}; + +export default Overview; diff --git a/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx b/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx index 08e82912..0c731f40 100644 --- a/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx +++ b/src/pages/Details/AddressDetails/AddressDetails.helpers.tsx @@ -100,8 +100,10 @@ export const transformChartData = (data: TChartStatisticsResponse[] | null) => { const dataY: number[] = []; if (data?.length) { data.forEach(item => { - dataX.push(format(item.time, 'MM/dd/yyyy')); - dataY.push(item.value); + if (item.time) { + dataX.push(format(item.time, 'MM/dd/yyyy')); + dataY.push(item.value); + } }); const nowHour = format(new Date(), 'MM/dd/yyyy'); @@ -123,8 +125,10 @@ export const transformDirectionChartData = (data: TChartStatisticsResponse[] | n const dataY: number[] = []; if (data?.length) { data.forEach(item => { - dataX.push(item.time.toString()); - dataY.push(item.value); + if (item.time) { + dataX.push(item.time.toString()); + dataY.push(item.value); + } }); } diff --git a/src/pages/Details/AddressDetails/AddressDetails.styles.ts b/src/pages/Details/AddressDetails/AddressDetails.styles.ts index 207ca48a..07629465 100644 --- a/src/pages/Details/AddressDetails/AddressDetails.styles.ts +++ b/src/pages/Details/AddressDetails/AddressDetails.styles.ts @@ -195,6 +195,7 @@ export const Wrapper = styled.div` export const AddressTitleBlock = styled.div` display: flex; width: 100%; + white-space: nowrap; span { margin-left: 5px; diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 3f7acf52..ed3555f5 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -62,6 +62,7 @@ const TotalTransactionFees = loadable( ); const Tickets = loadable(() => import('@pages/Tickets/Tickets')); const TicketsType = loadable(() => import('@pages/TicketsType/TicketsType')); +const Burned = loadable(() => import('@pages/Burned/Burned')); const explorerRoutes = { id: 'routes.explorer', @@ -423,6 +424,15 @@ const ticketsTypeRoutes = { children: null, }; +const burnedRoutes = { + id: 'routes.burned', + path: `${ROUTES.PASTEL_BURN_ADDRESS}`, + icon: , + component: Burned, + seoTitle: 'routes.burned', + children: null, +}; + export const pageRoutes = [ explorerRoutes, movementRoutes, @@ -460,6 +470,7 @@ export const pageRoutes = [ senseDetailsRoutes, pastelIdDetailsRoutes, ticketsTypeRoutes, + burnedRoutes, ]; export const sidebarRoutes = [ diff --git a/src/utils/appInfo.ts b/src/utils/appInfo.ts index de623fd8..0b02a8ac 100644 --- a/src/utils/appInfo.ts +++ b/src/utils/appInfo.ts @@ -25,3 +25,12 @@ export const isPastelBurnAddress = (address: string): boolean => { return false; }; + +export const getPastelBurnAddresses = (): string[] => { + if (PASTEL_BURN_ADDRESS) { + const pastelBurnAddresses = PASTEL_BURN_ADDRESS.split(','); + return pastelBurnAddresses; + } + + return []; +}; diff --git a/src/utils/constants/routes.ts b/src/utils/constants/routes.ts index 45f826e3..a9156d52 100644 --- a/src/utils/constants/routes.ts +++ b/src/utils/constants/routes.ts @@ -38,3 +38,4 @@ export const STATISTICS_ACCOUNTS = `${STATISTICS_OVERTIME}/accounts`; export const CASCADE_AND_SENSE_STATISTICS = '/cascade-and-sense-statistics'; export const SENSE_DETAILS = '/sense'; export const PASTEL_ID_DETAILS = '/pastelid'; +export const PASTEL_BURN_ADDRESS = '/burned'; diff --git a/src/utils/constants/urls.ts b/src/utils/constants/urls.ts index 6dc43faf..3944df9b 100644 --- a/src/utils/constants/urls.ts +++ b/src/utils/constants/urls.ts @@ -47,6 +47,8 @@ export const GET_STATISTICS_AVERAGE_SIZE_OF_NFT_STORED_ON_CASCADE = 'v1/stats/average-size-of-nft-stored-on-cascade'; export const GET_STATISTICS_CASCADE_REQUEST = 'v1/stats/cascade-requests'; export const GET_STATISTICS_TOTAL_DATA_STORED_ON_CASCADE = 'v1/stats/total-data-stored-on-cascade'; +export const GET_BURNED_BY_MONTH = 'v1/stats/burned-by-month'; +export const GET_TOTAL_BURNED = 'v1/stats/total-burned'; // External URLS export const TWITTER_URL = 'https://twitter.com/pastelnetwork'; export const TELEGRAM_URL = 'https://t.me/PastelNetwork'; diff --git a/src/utils/helpers/chartOptions.ts b/src/utils/helpers/chartOptions.ts index 019456db..19d506ee 100644 --- a/src/utils/helpers/chartOptions.ts +++ b/src/utils/helpers/chartOptions.ts @@ -3841,6 +3841,114 @@ export function getSummaryThemeUpdateOption(args: TThemeInitOption): EChartsOpti }, animation: false, }, + totalBurned: { + backgroundColor: theme?.backgroundColor, + textStyle: { + color: theme?.color, + }, + color: [chartColor || blueColor], + grid: { + top: 10, + right: 0, + bottom: 20, + left: 0, + show: false, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross', + label: { + formatter: (param: TAxisPointerProps) => { + if (param.axisDimension === 'x') { + return param.value; + } + + return `${formatNumber(param.value, { decimalsLength: 2 })} ${getCurrencyName()}`; + }, + }, + }, + formatter: (params: TToolTipParamsProps[]) => { + return `
    ${params[0].axisValue}
    ${params[0].marker} ${ + params[0].seriesName + }: ${ + params[0].data ? formatNumber(params[0].data, { decimalsLength: 2 }) : '0' + } ${getCurrencyName()}`; + }, + }, + xAxis: { + type: 'category', + data: dataX, + boundaryGap: false, + axisLabel: { + show: true, + formatter(value: string) { + return value ? generateXAxisLabel(new Date(value), period, false) : null; + }, + showMaxLabel: false, + showMinLabel: true, + interval: balanceHistoryXAxisInterval(dataX, width), + align: 'left', + }, + axisLine: { + show: false, + }, + splitLine: { + show: false, + }, + axisTick: { + show: false, + }, + name: dataX?.length + ? generateXAxisLabel(new Date(dataX[dataX.length - 1]), period, false) + : '', + nameGap: 0, + nameLocation: 'end', + nameTextStyle: { + align: 'right', + verticalAlign: 'top', + padding: [8, 2, 0, 0], + }, + }, + yAxis: { + type: 'value', + min: minY, + max: maxY, + interval: (maxY - minY) / 5, + splitLine: { + show: false, + }, + axisLine: { + show: false, + }, + axisTick: { + show: false, + }, + axisLabel: { + show: false, + }, + }, + series: { + name: translate('pages.burned.totalBurned', { currency: getCurrencyName() }) || '', + type: 'line', + sampling: 'lttb', + data: dataY, + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: chartColor || blueColor, + }, + { + offset: 1, + color: theme?.backgroundColor || '#fff', + }, + ]), + }, + showSymbol: false, + }, + animation: false, + }, }; return chartOptions[chartName]; } From f55c3f36975b9c554f153fba33e41408683f1237 Mon Sep 17 00:00:00 2001 From: Tuan Nguyen Date: Fri, 24 Mar 2023 18:05:19 +0700 Subject: [PATCH 08/12] redirect to the 404 page when the address is not found --- src/hooks/useAddressDetails.ts | 3 ++- src/pages/Details/AddressDetails/Summary.tsx | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/hooks/useAddressDetails.ts b/src/hooks/useAddressDetails.ts index e6d2c38a..aba287fa 100644 --- a/src/hooks/useAddressDetails.ts +++ b/src/hooks/useAddressDetails.ts @@ -18,7 +18,7 @@ interface IAddressDetails { } export default function useAddressDetails(id: string) { - const { data, isLoading } = useSWR( + const { data, isLoading, error } = useSWR( `${URLS.ADDRESS_URL}/${id}`, axiosGet, SWR_OPTIONS, @@ -28,6 +28,7 @@ export default function useAddressDetails(id: string) { isLoading, outgoingSum: data?.outgoingSum || 0, incomingSum: data?.incomingSum || 0, + error, }; } diff --git a/src/pages/Details/AddressDetails/Summary.tsx b/src/pages/Details/AddressDetails/Summary.tsx index 3c32d0ed..4b0a66b1 100644 --- a/src/pages/Details/AddressDetails/Summary.tsx +++ b/src/pages/Details/AddressDetails/Summary.tsx @@ -1,4 +1,5 @@ import { Skeleton } from '@material-ui/lab'; +import { useHistory } from 'react-router-dom'; import { formatNumber } from '@utils/helpers/formatNumbers/formatNumbers'; import { getCurrencyName, isPastelBurnAddress } from '@utils/appInfo'; @@ -7,6 +8,7 @@ import useAddressDetails from '@hooks/useAddressDetails'; import Balance, { BurnBalance } from '@components/SvgIcon/Balance'; import Sent from '@components/SvgIcon/Sent'; import Received, { RedReceived } from '@components/SvgIcon/Received'; +import * as ROUTES from '@utils/constants/routes'; import * as Styles from './AddressDetails.styles'; @@ -18,8 +20,13 @@ interface ISummaryProps { } const Summary: React.FC = ({ id, onChange, selectedChart, isBalanceLoading }) => { + const history = useHistory(); const isBurnAddress = isPastelBurnAddress(id); - const { outgoingSum, incomingSum, isLoading } = useAddressDetails(id); + const { outgoingSum, incomingSum, isLoading, error } = useAddressDetails(id); + + if (error) { + history.push(ROUTES.NOT_FOUND); + } return ( From a2392768c115afc50d895fbe94219ff4867cc5a1 Mon Sep 17 00:00:00 2001 From: Tuan Nguyen Date: Tue, 28 Mar 2023 14:20:57 +0700 Subject: [PATCH 09/12] removed Burned Addresses added jest test for the Burned page --- src/components/Navbar/Navbar.styles.ts | 1 + src/components/SearchBar/SearchBar.styles.ts | 33 +++++++++++++++ src/components/SearchBar/SearchBar.tsx | 18 +++++++- src/pages/Burned/Address.tsx | 42 ------------------- src/pages/Burned/Burned.tsx | 2 - src/pages/Burned/__test__/Burned.test.tsx | 42 +++++++++++++++++++ .../__snapshots__/Burned.test.tsx.snap | 3 ++ src/utils/helpers/date/__test__/date.test.ts | 4 +- 8 files changed, 98 insertions(+), 47 deletions(-) delete mode 100644 src/pages/Burned/Address.tsx create mode 100644 src/pages/Burned/__test__/Burned.test.tsx create mode 100644 src/pages/Burned/__test__/__snapshots__/Burned.test.tsx.snap diff --git a/src/components/Navbar/Navbar.styles.ts b/src/components/Navbar/Navbar.styles.ts index 168874fc..ed63c283 100644 --- a/src/components/Navbar/Navbar.styles.ts +++ b/src/components/Navbar/Navbar.styles.ts @@ -42,6 +42,7 @@ export const BrandLogo = styled.img` export const Menu = styled.div` display: flex; + z-index: 3; ${props => props.theme.breakpoints.down('xs')} { align-items: flex-start; diff --git a/src/components/SearchBar/SearchBar.styles.ts b/src/components/SearchBar/SearchBar.styles.ts index e74dc0c6..a1f4e703 100644 --- a/src/components/SearchBar/SearchBar.styles.ts +++ b/src/components/SearchBar/SearchBar.styles.ts @@ -60,6 +60,38 @@ export const GridStyle = styled(Grid)` .MuiInputBase-input { font-size: 13px; } + + @media (max-width: 1424px) and (min-width: 900px) { + &.autocomplete-focus { + position: absolute; + right: 76px; + width: 55vw; + max-width: 74vw; + background: ${props => props.theme.sidebar.menu.background}; + z-index: 100; + + .label-input { + max-width: 100%; + width: auto; + } + } + } + + @media (max-width: 900px) and (min-width: 600px) { + &.autocomplete-focus { + position: absolute; + right: 76px; + width: 74vw; + max-width: 74vw; + background: ${props => props.theme.sidebar.menu.background}; + z-index: 100; + + .label-input { + max-width: 100%; + width: auto; + } + } + } `; export const AppBar = styled(MuiAppBar)` @@ -68,6 +100,7 @@ export const AppBar = styled(MuiAppBar)` background: ${props => props.theme.sidebar.menu.background}; color: ${props => props.theme.header.color}; transition: min-height 0.5s ease-in; + z-index: 10; ${props => props.theme.breakpoints.up('md')} { max-width: 28%; diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx index 81a0890a..b438dd1b 100644 --- a/src/components/SearchBar/SearchBar.tsx +++ b/src/components/SearchBar/SearchBar.tsx @@ -88,6 +88,7 @@ const SearchBar: React.FC = ({ onDrawerToggle }) => { const [loading, setLoading] = React.useState(false); const [isShowSearchInput, setShowSearchInput] = React.useState(false); const [forceShowSearchInput, setForceShowSearchInput] = React.useState(false); + const [isInputFocus, setInputFocus] = React.useState(false); const handleShowSearchInput = () => { const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; @@ -184,6 +185,14 @@ const SearchBar: React.FC = ({ onDrawerToggle }) => { const handleClose = () => searchData.length && setSearchData([]); + const handleFocus = () => { + setInputFocus(true); + }; + + const handleBlur = () => { + setInputFocus(false); + }; + const dropdownOpen = Boolean(searchData.length) || loading; const renderSearchInput = () => ( @@ -203,6 +212,8 @@ const SearchBar: React.FC = ({ onDrawerToggle }) => { onInputChange={handleInputChange} onChange={handleChange} onClose={handleClose} + onFocus={handleFocus} + onBlur={handleBlur} forcePopupIcon={false} getOptionSelected={(option, value) => (option as TAutocompleteOptions).value === (value as TAutocompleteOptions).value @@ -297,7 +308,12 @@ const SearchBar: React.FC = ({ onDrawerToggle }) => { className={`${isShowSearchInput ? 'search-show' : ''} ${forceShowSearchInput ? 'force' : ''}`} > - + {renderSearchInput()} { - const addresses = getPastelBurnAddresses(); - - const generateLatestTransactions = () => { - return addresses.map(address => ( -
  • - - - -
  • - )); - }; - - return ( - - - - {translate('pages.burned.burnedAddresses')}: -
      {generateLatestTransactions()}
    -
    -
    -
    - ); -}; - -export default Address; diff --git a/src/pages/Burned/Burned.tsx b/src/pages/Burned/Burned.tsx index ce0c4608..08656d98 100644 --- a/src/pages/Burned/Burned.tsx +++ b/src/pages/Burned/Burned.tsx @@ -2,13 +2,11 @@ import Grid from '@material-ui/core/Grid'; // import CircularProgress from '@material-ui/core/CircularProgress'; import Overview from './Overview'; -import Address from './Address'; import * as Styles from './Burned.styles'; const Burned = () => { return ( -
    diff --git a/src/pages/Burned/__test__/Burned.test.tsx b/src/pages/Burned/__test__/Burned.test.tsx new file mode 100644 index 00000000..7665489b --- /dev/null +++ b/src/pages/Burned/__test__/Burned.test.tsx @@ -0,0 +1,42 @@ +import { shallow } from 'enzyme'; +import 'jest-styled-components'; + +import i18next from '../../../utils/helpers/i18n'; +import Burned from '../Burned'; +import Overview from '../Overview'; + +jest.mock('i18next-http-backend'); +jest.mock('react-i18next', () => ({ + useTranslation: () => { + return { + t: (str: string) => str, + i18n: { + changeLanguage: () => + new Promise(() => { + // noop + }), + }, + }; + }, + initReactI18next: { + type: '3rdParty', + init: () => { + // noop + }, + }, +})); +i18next.t = jest.fn().mockImplementation((...arg) => { + return arg[0]; +}); + +describe('pages/Burned', () => { + const wrapper = shallow(); + + test('renders correctly', () => { + expect(wrapper).toMatchSnapshot(); + }); + + test('should render ', () => { + expect(wrapper.find(Overview).length).toBe(1); + }); +}); diff --git a/src/pages/Burned/__test__/__snapshots__/Burned.test.tsx.snap b/src/pages/Burned/__test__/__snapshots__/Burned.test.tsx.snap new file mode 100644 index 00000000..a31d7473 --- /dev/null +++ b/src/pages/Burned/__test__/__snapshots__/Burned.test.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`pages/Burned renders correctly 1`] = `ShallowWrapper {}`; diff --git a/src/utils/helpers/date/__test__/date.test.ts b/src/utils/helpers/date/__test__/date.test.ts index a8b2a62f..8bb3f584 100644 --- a/src/utils/helpers/date/__test__/date.test.ts +++ b/src/utils/helpers/date/__test__/date.test.ts @@ -20,7 +20,7 @@ describe('utils/helpers/date', () => { }); test('formattedDate should works incorrectly', () => { - expect(formattedDate(123)).toEqual('Th, 1st Jan 1970 08:02:03'); + expect(formattedDate(123)).not.toEqual('Fr, 23rd Apr 55165 21:11:28'); }); test('formattedTimeElapsed should works correctly', () => { @@ -52,6 +52,6 @@ describe('utils/helpers/date', () => { }); test('formatFullDate should works incorrectly', () => { - expect(formatFullDate(123)).toEqual('Th, 1st Jan 1970 08:00:00'); + expect(formatFullDate(123)).not.toEqual('Mo, 13th Mar 2023 11:30:07'); }); }); From 981ddf07f2b1853322c2a4dbc855fc0c4b32eebf Mon Sep 17 00:00:00 2001 From: Tuan Nguyen Date: Thu, 30 Mar 2023 17:39:15 +0700 Subject: [PATCH 10/12] fix UI issues and update the API endpoint --- public/locales/en/translation.json | 4 ++++ src/hooks/useBlocks.ts | 9 ++++++++- src/pages/Blocks/Blocks.tsx | 8 ++++---- .../Details/PastelIdDetails/TicketList.tsx | 4 ++-- .../SenseDetails/SimilarRegisteredImages.tsx | 7 ++++++- src/pages/Richlist/BarChart.tsx | 2 +- src/pages/Richlist/Richlist.helpers.tsx | 2 +- .../BlockStatistics/BlockStatistics.tsx | 2 +- src/utils/constants/urls.ts | 19 +++++++++---------- src/utils/helpers/statisticsLib.ts | 2 +- 10 files changed, 37 insertions(+), 22 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index b75a5c00..8d7405ab 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -2168,6 +2168,10 @@ "filterLabel": { "message": "Last {{period}}", "description": "Used for the Statistics page" + }, + "filterLabelMax": { + "message": "Max", + "description": "Used for the Statistics page" } }, "supernodes": { diff --git a/src/hooks/useBlocks.ts b/src/hooks/useBlocks.ts index bca49c25..120ac6ad 100644 --- a/src/hooks/useBlocks.ts +++ b/src/hooks/useBlocks.ts @@ -32,11 +32,18 @@ export default function useBlocks( } else if (period && period !== 'custom') { dateParam = `&period=${period}`; } + let newSortBy = sortBy; + if (sortBy === 'blockHash') { + newSortBy = 'id'; + } + if (sortBy === 'blockId') { + newSortBy = 'height'; + } const { data, isLoading, size, setSize } = useSWRInfinite( index => `${URLS.BLOCK_URL}?offset=${ index * DATA_FETCH_LIMIT - }&limit=${limit}&sortBy=${sortBy}&sortDirection=${sortDirection}${dateParam}${typesParam}`, + }&limit=${limit}&sortBy=${newSortBy}&sortDirection=${sortDirection}${dateParam}${typesParam}`, axiosGet, SWR_OPTIONS, ); diff --git a/src/pages/Blocks/Blocks.tsx b/src/pages/Blocks/Blocks.tsx index 3813a59c..6f029422 100644 --- a/src/pages/Blocks/Blocks.tsx +++ b/src/pages/Blocks/Blocks.tsx @@ -11,7 +11,7 @@ import { formatNumber } from '@utils/helpers/formatNumbers/formatNumbers'; import useBlocks from '@hooks/useBlocks'; import { translate } from '@utils/helpers/i18n'; -import { columns, BLOCK_ID_KEY } from './Blocks.columns'; +import { columns } from './Blocks.columns'; import { transformTableData, DATA_DEFAULT_SORT, DATA_FETCH_LIMIT } from './Blocks.helpers'; import * as Styles from './Blocks.styles'; @@ -29,7 +29,7 @@ interface IBlocksDataRef { const Blocks = () => { const filter = useSelector(getFilterState); const [apiParams, setParams] = useState({ - sortBy: 'id', + sortBy: 'blockId', sortDirection: DATA_DEFAULT_SORT, period: filter.dateRange || 'all', types: filter.dropdownType || [], @@ -40,7 +40,7 @@ const Blocks = () => { }); const { swrData, total, swrSize, swrSetSize, isLoading } = useBlocks( DATA_FETCH_LIMIT, - apiParams.sortBy === BLOCK_ID_KEY ? 'id' : apiParams.sortBy, + apiParams.sortBy, apiParams.sortDirection, apiParams.period, apiParams.types, @@ -75,7 +75,7 @@ const Blocks = () => { swrSetSize(1); setParams({ ...apiParams, - sortBy: sortBy === 'blockHash' ? 'id' : sortBy, + sortBy, sortDirection, }); }; diff --git a/src/pages/Details/PastelIdDetails/TicketList.tsx b/src/pages/Details/PastelIdDetails/TicketList.tsx index 010ed3e6..fba1dec0 100644 --- a/src/pages/Details/PastelIdDetails/TicketList.tsx +++ b/src/pages/Details/PastelIdDetails/TicketList.tsx @@ -212,14 +212,14 @@ const TicketsList: React.FC = ({ const results = [ { value: TICKET_TYPE_OPTIONS[0].value, - name: `${TICKET_TYPE_OPTIONS[0].name}(${totalAllTickets})`, + name: `${translate(TICKET_TYPE_OPTIONS[0].name)}(${totalAllTickets})`, }, ]; for (let i = 0; i < ticketsTypeList.length; i += 1) { const item = TICKET_TYPE_OPTIONS.find(ticket => ticket.value === ticketsTypeList[i].type); results.push({ value: ticketsTypeList[i].type, - name: `${item?.name}(${ticketsTypeList[i].total})`, + name: `${translate(item?.name || '')}(${ticketsTypeList[i].total})`, }); } return results; diff --git a/src/pages/Details/SenseDetails/SimilarRegisteredImages.tsx b/src/pages/Details/SenseDetails/SimilarRegisteredImages.tsx index c671f4c1..03edad54 100644 --- a/src/pages/Details/SenseDetails/SimilarRegisteredImages.tsx +++ b/src/pages/Details/SenseDetails/SimilarRegisteredImages.tsx @@ -173,7 +173,12 @@ const SimilarRegisteredImages: React.FC = ({ rarenessS className="address-link" /> ) : ( - item.imageHash + )} diff --git a/src/pages/Richlist/BarChart.tsx b/src/pages/Richlist/BarChart.tsx index d8fc1138..ad3509cb 100644 --- a/src/pages/Richlist/BarChart.tsx +++ b/src/pages/Richlist/BarChart.tsx @@ -58,7 +58,7 @@ export const BarChart = ({ data }: BarChartProps): JSX.Element => { formatter: (params: TTooltipParams) => { return `
    ${params.name}
    -
    ${translate('pages.richlist.accounts', { +
    ${translate('pages.richlist.amount', { currency: getCurrencyName(), })}: ${formatNumber(params.value, { decimalsLength: 2, diff --git a/src/pages/Richlist/Richlist.helpers.tsx b/src/pages/Richlist/Richlist.helpers.tsx index 376f02bd..0a7b46ac 100644 --- a/src/pages/Richlist/Richlist.helpers.tsx +++ b/src/pages/Richlist/Richlist.helpers.tsx @@ -140,7 +140,7 @@ export const generateWealthDistributionData = (list: IRichlist[], coinSupply: nu const newList = list.sort((a, b) => a.rank - b.rank); const dividedLists = LIST_DIVIDERS.map(([firstDivider, lastDivider]) => { const currentWealthDistributionList = newList.slice(firstDivider, lastDivider); - const rowLabel = translate('pages.richlist.percentage', { + const rowLabel = translate('pages.richlist.top', { rank: `${firstDivider + 1}-${lastDivider}`, }); return generateWealthDistributionItem( diff --git a/src/pages/Statistics/BlockStatistics/BlockStatistics.tsx b/src/pages/Statistics/BlockStatistics/BlockStatistics.tsx index c1937048..27c17526 100644 --- a/src/pages/Statistics/BlockStatistics/BlockStatistics.tsx +++ b/src/pages/Statistics/BlockStatistics/BlockStatistics.tsx @@ -66,7 +66,7 @@ const StatisticsBlocks: React.FC = ({ blockElements, blocksUn ? translate('pages.statistics.transactions', { txsCount }) : translate('pages.statistics.transaction', { txsCount }) } - minutesAgo={translate('pages.statistics.transaction', { + minutesAgo={translate('pages.statistics.blocksUnconfirmedTime', { time: blocksUnconfirmed.length * 10, })} /> diff --git a/src/utils/constants/urls.ts b/src/utils/constants/urls.ts index 3944df9b..075e6b8b 100644 --- a/src/utils/constants/urls.ts +++ b/src/utils/constants/urls.ts @@ -7,22 +7,21 @@ export const DEFAULT_API_URL = process.env.REACT_APP_EXPLORER_DEFAULT_API_URL; // API endpoints export const SEARCH_URL = `/v1/search`; export const TRANSACTION_URL = 'v1/transactions'; -export const VOLUME_TRANSACTION_URL = 'v1/transactions/chart/volume'; -export const INCOMING_TRANSACTION_URL = 'v1/transactions/chart/latest'; +export const VOLUME_TRANSACTION_URL = 'v1/transactions/charts/volume-of-transactions'; +export const INCOMING_TRANSACTION_URL = 'v1/transactions/charts/incoming-transactions'; export const GET_UNCONFIRMED_TRANSACTIONS = 'v1/transactions/blocks-unconfirmed'; export const GET_TRANSACTIONS_CHARTS = 'v1/transactions/charts'; -export const GET_BLOCKS_CHARTS = 'v1/block/charts'; +export const GET_BLOCKS_CHARTS = 'v1/blocks/charts'; export const BLOCK_URL = 'v1/blocks'; -export const BLOCK_SIZE_URL = 'v1/blocks/size'; -export const STATISTICS_BLOCK_URL = 'v1/blocks/statistics'; -export const ADDRESS_URL = 'v1/addresses'; +export const BLOCK_SIZE_URL = 'v1/blocks/charts/size'; +export const STATISTICS_BLOCK_URL = 'v1/blocks/charts/statistics'; +export const ADDRESS_URL = 'v1/addresses/balance'; export const RICHLIST_URL = 'v1/addresses/rank/100'; export const BALANCE_HISTORY_URL = 'v1/addresses/balance-history'; export const LATEST_TRANSACTIONS_URL = 'v1/addresses/latest-transactions'; export const DIRECTION_URL = 'v1/addresses/direction'; export const NETWORK_URL = 'v1/network'; export const SUMMARY_URL = 'v1/stats'; -export const GET_STATISTICS = 'v1/stats/list'; export const GET_STATISTICS_HASHRATE = 'v1/stats/mining-list'; export const GET_STATISTICS_MEMPOOL_INFO = 'v1/stats/mempool-info-list'; export const GET_STATISTICS_NETTOTALS = 'v1/stats/nettotals-list'; @@ -35,10 +34,10 @@ export const GET_STATISTICS_CIRCULATING_SUPPLY = 'v1/stats/circulating-supply'; export const GET_STATISTICS_PERCENT_OF_PSL_STAKED = 'v1/stats/percent-of-psl-staked'; export const GET_STATISTICS_ACCOUNTS = 'v1/stats/accounts'; export const GET_HISTORICAL_STATISTICS = 'v1/stats/historical-statistics'; -export const SENSE_URL = 'v1/transactions/sense'; -export const PASTEL_ID_URL = 'v1/transactions/pastelid'; +export const SENSE_URL = 'v1/sense'; +export const PASTEL_ID_URL = 'v1/pastelid'; export const CURRENT_STATS = 'v1/stats/current-stats'; -export const GET_TICKETS = 'v1/transactions/tickets'; +export const GET_TICKETS = 'v1/tickets'; export const GET_STATISTICS_AVERAGE_RARENESS_SCORE_ON_SENSE = 'v1/stats/average-rareness-score-on-sense'; export const GET_STATISTICS_SENSE_REQUESTS = 'v1/stats/sense-requests'; diff --git a/src/utils/helpers/statisticsLib.ts b/src/utils/helpers/statisticsLib.ts index 493d81ba..77719dec 100644 --- a/src/utils/helpers/statisticsLib.ts +++ b/src/utils/helpers/statisticsLib.ts @@ -549,7 +549,7 @@ export const generatePeriodToDropdownOptions = (periods: PeriodTypes[]) => { }); } else { results.push({ - name: translate('pages.statistics.filterLabel'), + name: translate('pages.statistics.filterLabelMax'), value: periods[i], }); } From b7b84aeee170827896cbc6f4eae9556c387a9e46 Mon Sep 17 00:00:00 2001 From: Tuan Nguyen Date: Tue, 4 Apr 2023 09:24:55 +0700 Subject: [PATCH 11/12] fix sort issues for the Latest Blocks (Live) --- src/apis/blocks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apis/blocks.ts b/src/apis/blocks.ts index 5ec99501..d7f152a9 100644 --- a/src/apis/blocks.ts +++ b/src/apis/blocks.ts @@ -6,7 +6,7 @@ const getLatestBlock = async (limit = 8) => { const { data: { data, timestamp }, }: { data: { data: IBlock[]; timestamp: number } } = await axiosInstance.get(BLOCK_URL, { - params: { limit, sortBy: 'id', excludePaging: true }, + params: { limit, sortBy: 'height', excludePaging: true }, }); const blockTuple: [string, IBlock][] = data.map((block: IBlock) => [block.id, block]); From 33c0a4479b6139130811d71b1d1e493679314e36 Mon Sep 17 00:00:00 2001 From: Tuan Nguyen Date: Tue, 4 Apr 2023 10:14:09 +0700 Subject: [PATCH 12/12] update UI for the Address Details page --- src/pages/Details/AddressDetails/BalanceHistory.tsx | 2 +- src/pages/Details/AddressDetails/Summary.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Details/AddressDetails/BalanceHistory.tsx b/src/pages/Details/AddressDetails/BalanceHistory.tsx index 83b99075..e73f46bb 100644 --- a/src/pages/Details/AddressDetails/BalanceHistory.tsx +++ b/src/pages/Details/AddressDetails/BalanceHistory.tsx @@ -19,7 +19,7 @@ interface IBalanceHistoryProps { const BalanceHistory: React.FC = ({ id }) => { const isBurnAddress = isPastelBurnAddress(id); - const [selectedPeriod, setSelectedPeriod] = useState(periods[1][0]); + const [selectedPeriod, setSelectedPeriod] = useState(periods[1][4]); const [selectedChartType, setSelectedChartType] = useState('balance'); const { isLoading, balance, incoming, outgoing } = useBalanceHistory(id, selectedPeriod); diff --git a/src/pages/Details/AddressDetails/Summary.tsx b/src/pages/Details/AddressDetails/Summary.tsx index 4b0a66b1..acfd3f03 100644 --- a/src/pages/Details/AddressDetails/Summary.tsx +++ b/src/pages/Details/AddressDetails/Summary.tsx @@ -75,7 +75,7 @@ const Summary: React.FC = ({ id, onChange, selectedChart, isBalan {isLoading ? ( ) : ( - formatNumber(outgoingSum, { decimalsLength: 2 }) + formatNumber(outgoingSum * -1, { decimalsLength: 2 }) )}