diff --git a/src/assets/scss/components/_components.scss b/src/assets/scss/components/_components.scss index b8c464f60..2f3349383 100644 --- a/src/assets/scss/components/_components.scss +++ b/src/assets/scss/components/_components.scss @@ -15,10 +15,10 @@ @import '../../../components/Marquee/marquee.styles.scss'; @import '../../../components/MultilayerPercentage/multilayerPercentage.styles.scss'; @import '../../../components/NftPreview/nftPreview.styles.scss'; -@import '../../../components/NodesTable/nodesTable.styles.scss'; @import '../../../components/Nodes/AuctionListTable/auctionListTables.styles.scss'; @import '../../../components/Nodes/NodesOverview/nodesOverview.styles.scss'; @import '../../../components/Nodes/NodesStatusPreviewCards/nodesStatusPreviewCards.styles.scss'; +@import '../../../components/NodesTable/nodesTable.styles.scss'; @import '../../../components/NotificationsBar/notificationsBar.styles.scss'; @import '../../../components/Pager/pager.styles.scss'; @import '../../../components/PageState/components/IconState/iconState.styles.scss'; diff --git a/src/assets/scss/components/_pages.scss b/src/assets/scss/components/_pages.scss index f71135710..fe766c89f 100644 --- a/src/assets/scss/components/_pages.scss +++ b/src/assets/scss/components/_pages.scss @@ -2,7 +2,9 @@ @import '../../../pages/AccountDetails/AccountTokensTable/accountTokensTable.styles.scss'; @import '../../../pages/Analytics/analytics.styles.scss'; @import '../../../pages/BlockDetails/blockDetails.styles.scss'; +@import '../../../pages/Home/home.styles.scss'; @import '../../../pages/Identities/identities.styles.scss'; +@import '../../../pages/NativeToken/nativeToken.styles.scss'; @import '../../../pages/NftDetails/nftDetails.styles.scss'; @import '../../../pages/NodeDetails/components/NetworkMetrics/networkMetrics.styles.scss'; @import '../../../pages/NodeDetails/components/Rounds/rounds.styles.scss'; diff --git a/src/assets/scss/components/_widgets.scss b/src/assets/scss/components/_widgets.scss index c1855ed0f..e05afff04 100644 --- a/src/assets/scss/components/_widgets.scss +++ b/src/assets/scss/components/_widgets.scss @@ -1,9 +1,9 @@ +@import '../../../widgets/ChartCard/chartCard.styles.scss'; +@import '../../../widgets/ChartContractsTransactions/chartContractsTransactions.styles.scss'; +@import '../../../widgets/EconomicsCard/economicsCard.styles.scss'; @import '../../../widgets/HeroHome/heroHome.styles.scss'; @import '../../../widgets/HeroNodes/heroNodes.styles.scss'; @import '../../../widgets/HeroPills/heroPills.styles.scss'; @import '../../../widgets/MostUsed/mostUsed.styles.scss'; -@import '../../../widgets/ValidatorsStatusCard/validatorsStatusCard.styles.scss'; @import '../../../widgets/StatsCard/statsCard.styles.scss'; - -// temp -@import '../../../pages/Home/components/ChartContractsTransactions/chartContractsTransactions.styles.scss'; +@import '../../../widgets/ValidatorsStatusCard/validatorsStatusCard.styles.scss'; diff --git a/src/assets/scss/elements/_tabs.scss b/src/assets/scss/elements/_tabs.scss index 84ff2c8f6..3e32ffd0d 100644 --- a/src/assets/scss/elements/_tabs.scss +++ b/src/assets/scss/elements/_tabs.scss @@ -19,7 +19,7 @@ border-radius: 0.5rem; background-color: transparent; - @media (min-width: 768px) { + @include media-breakpoint-up(md) { font-size: 1rem; padding: 0.75rem 1rem; } @@ -45,7 +45,7 @@ &.active { color: var(--primary); } - @media (min-width: 768px) { + @include media-breakpoint-up(md) { font-size: 1.5rem; padding: 0.5rem 0.75rem; } diff --git a/src/assets/scss/plugins/_sdk-dapp-overrides.scss b/src/assets/scss/plugins/_sdk-dapp-overrides.scss index 77feaf32c..40b65ae99 100644 --- a/src/assets/scss/plugins/_sdk-dapp-overrides.scss +++ b/src/assets/scss/plugins/_sdk-dapp-overrides.scss @@ -547,7 +547,7 @@ a .dapp-core-component__trim-styles__ellipsis { } } -@media (min-width: 768px) { +@include media-breakpoint-up(md) { .dapp-core-component__signWithDeviceModalStyles__modal-layout-content.dapp-core-component__signWithDeviceModalStyles__spaced { padding: 2rem 0.75rem 1.5rem 0.75rem !important; } diff --git a/src/components/AccountsTable/AccountsTable.tsx b/src/components/AccountsTable/AccountsTable.tsx new file mode 100644 index 000000000..3bd432321 --- /dev/null +++ b/src/components/AccountsTable/AccountsTable.tsx @@ -0,0 +1,170 @@ +import BigNumber from 'bignumber.js'; +import classNames from 'classnames'; + +import { + FormatAmount, + AccountLink, + FormatNumber, + FormatUSD, + PercentageBar, + Pager, + PageSize, + Loader, + PageState +} from 'components'; +import { DECIMALS } from 'config'; +import { formatBigNumber, parseAmount } from 'helpers'; +import { faUser } from 'icons/regular'; +import { AccountType, TokenLockedAccountType, WithClassnameType } from 'types'; + +export interface AccountsTableUIType extends WithClassnameType { + accounts: AccountType[] | TokenLockedAccountType[]; + accountsCount: number; + title?: React.ReactNode; + message?: string; + decimals?: number; + showValue?: boolean; + supply?: string | number; + price?: number; + isDataReady?: boolean; + hasNameColumn?: boolean; + isNativeToken?: boolean; +} + +export const AccountsTable = ({ + accounts, + accountsCount, + title, + message = 'Accounts', + decimals = DECIMALS, + showValue, + price, + supply, + isNativeToken = false, + isDataReady, + hasNameColumn +}: AccountsTableUIType) => { + const hasSupply = new BigNumber(supply ?? 0).isGreaterThan(0); + const showAccounts = isDataReady === true && accounts.length > 0; + + return ( +
+
+
+ {title} + 0} + className='d-flex ms-auto me-auto me-sm-0' + /> +
+
+ {showAccounts ? ( + <> +
+
+ + + + + {hasNameColumn && } + + {hasSupply && ( + + )} + {showValue && } + + + + {accounts.map((account) => { + const holdingsPercentage = + hasSupply && supply + ? new BigNumber(account.balance) + .times(100) + .dividedBy(parseAmount(supply, decimals)) + : new BigNumber(0); + const hasAccountName = + hasNameColumn && (account as TokenLockedAccountType).name; + + return ( + + + {hasAccountName && ( + + )} + + {hasSupply && ( + + )} + {showValue && ( + + )} + + ); + })} + +
AddressNameBalancePercentageValue
+
+ +
+
{(account as TokenLockedAccountType).name} + + +
+ +
+ +
+ +
+
+
+
+ + 0} /> +
+ + ) : ( + <> + {isDataReady === undefined && } + {isDataReady === false && ( + + )} + {isDataReady === true && accounts.length === 0 && ( + + )} + + )} +
+ ); +}; diff --git a/src/components/AccountsTable/index.ts b/src/components/AccountsTable/index.ts new file mode 100644 index 000000000..20e7f94d1 --- /dev/null +++ b/src/components/AccountsTable/index.ts @@ -0,0 +1 @@ +export * from './AccountsTable'; diff --git a/src/components/HeroDetailsCard/HeroDetailsCard.tsx b/src/components/HeroDetailsCard/HeroDetailsCard.tsx index 190f259a1..1b28f7f51 100644 --- a/src/components/HeroDetailsCard/HeroDetailsCard.tsx +++ b/src/components/HeroDetailsCard/HeroDetailsCard.tsx @@ -24,6 +24,7 @@ export interface HeroDetailsCardUIType extends WithClassnameType { title?: React.ReactNode; titleContent?: React.ReactNode; icon?: string; + iconComponent?: React.ReactNode; iconPlaceholder?: React.ReactNode; isVerified?: boolean; verifiedComponent?: React.ReactNode; @@ -40,6 +41,7 @@ export const HeroDetailsCard = ({ title, titleContent, icon, + iconComponent, iconPlaceholder, isVerified, verifiedComponent, @@ -55,7 +57,7 @@ export const HeroDetailsCard = ({ const dispatch = useDispatch(); const isMainnet = useIsMainnet(); const hasStatCards = statsCards.length > 0 || smallStatsCards.length > 0; - const hasIcon = Boolean(icon || iconPlaceholder); + const hasIcon = Boolean(icon || iconPlaceholder || iconComponent); useEffect(() => { if (seoDetails?.completeDetails && isMainnet) { @@ -70,6 +72,33 @@ export const HeroDetailsCard = ({ } }, [seoDetails, isMainnet]); + const Icon = ({ className }: WithClassnameType) => ( + + {hasIcon && + (iconComponent ? ( + <>{iconComponent} + ) : ( + <> + {icon ? ( + + ) : ( + iconPlaceholder + )} + + ))} + + ); + return (
- - {hasIcon && ( - <> - {icon ? ( - - ) : ( - iconPlaceholder - )} - - )} - + +
{title && (
- - {hasIcon && ( - <> - {icon ? ( - - ) : ( - iconPlaceholder - )} - - )} - +

`/tokens/${tokenId}/locked-accounts`, tokenDetailsRoles: (tokenId: string) => `/tokens/${tokenId}/roles`, + nativeTokenDetails: (egldLabel: string) => `/${egldLabel.toLowerCase()}`, + nativeTokenDetailsAccounts: (egldLabel: string) => + `/${egldLabel.toLowerCase()}/accounts`, collections: (params?: GetCollectionsType) => { const urlSearch = params ? new URLSearchParams(params as Record).toString() diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 09668885c..ca3a4c9ad 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -9,6 +9,7 @@ export * from './useCustomNetwork'; export * from './useDebounce'; export * from './useGetExplorerTitle'; export * from './useGetHash'; +export * from './useGetNativeTokenDetails'; export * from './useGetNetworkChangeLink'; export * from './useGetNodesCategoryCount'; export * from './useGetShardText'; diff --git a/src/hooks/useGetNativeTokenDetails.ts b/src/hooks/useGetNativeTokenDetails.ts new file mode 100644 index 000000000..e2642822f --- /dev/null +++ b/src/hooks/useGetNativeTokenDetails.ts @@ -0,0 +1,83 @@ +import BigNumber from 'bignumber.js'; +import { useSelector } from 'react-redux'; + +import { BRAND_NAME } from 'appConstants'; +import { DECIMALS } from 'config'; +import { useIsSovereign } from 'hooks'; +import { + economicsSelector, + statsSelector, + activeNetworkSelector +} from 'redux/selectors'; +import { TokenAssetType } from 'types'; + +export interface NativeTokenType { + identifier: string; + ticker?: string; + name: string; + balance?: string; + decimals?: number; + supply: string | number; + circulatingSupply: string | number; + transactions: number; + accounts: number; + price?: number; + marketCap?: number; + valueUsd?: number; + assets?: TokenAssetType; + transfersCount?: number; +} + +export const useGetNativeTokenDetails = () => { + const { egldLabel, name, decimals } = useSelector(activeNetworkSelector); + const { + unprocessed: { price, marketCap, totalSupply, circulatingSupply } + } = useSelector(economicsSelector); + const { + unprocessed: { accounts, transactions, scResults } + } = useSelector(statsSelector); + + const isSovereign = useIsSovereign(); + + const displayName = `${isSovereign ? name : BRAND_NAME} ${egldLabel}`; + const description = isSovereign + ? `${egldLabel} Token is native to ${name ?? BRAND_NAME}` + : `The ${BRAND_NAME} eGold (${egldLabel}) Token is native to the ${BRAND_NAME} Network and will be used for everything from staking, governance, transactions, smart contracts and validator rewards.`; + + const nativeTokenTransactions = new BigNumber(transactions || 0).minus( + scResults || 0 + ); + const assets = { + name: displayName, + description, + ...(!isSovereign + ? { + website: 'https://multiversx.com/', + social: { + blog: 'https://multiversx.com/blog', + x: 'https://x.com/MultiversX', + telegram: 'https://t.me/MultiversX', + discord: 'https://discord.com/invite/multiversxbuilders', + facebook: 'https://www.facebook.com/MultiversX/', + linkedin: 'https://www.linkedin.com/company/multiversx' + } + } + : {}) + }; + + return { + identifier: egldLabel, + ticker: egldLabel, + name: egldLabel, + decimals: Number(decimals ?? DECIMALS), + supply: totalSupply, + circulatingSupply, + transactions: nativeTokenTransactions.isGreaterThanOrEqualTo(0) + ? nativeTokenTransactions.toNumber() + : 0, + accounts, + price, + marketCap, + assets + }; +}; diff --git a/src/layouts/Layout/Layout.tsx b/src/layouts/Layout/Layout.tsx index 01b001d94..61e828323 100644 --- a/src/layouts/Layout/Layout.tsx +++ b/src/layouts/Layout/Layout.tsx @@ -79,7 +79,7 @@ export const Layout = () => { ) : ( <> -
+
diff --git a/src/layouts/Layout/components/Hero/Hero.tsx b/src/layouts/Layout/components/Hero/Hero.tsx index c2ff64721..8c97ade8f 100644 --- a/src/layouts/Layout/components/Hero/Hero.tsx +++ b/src/layouts/Layout/components/Hero/Hero.tsx @@ -10,12 +10,12 @@ import { useHasGrowthWidgets, usePageStats } from 'hooks'; -import { ChartContractsTransactions } from 'pages/Home/components/ChartContractsTransactions'; import { activeNetworkSelector, defaultNetworkSelector } from 'redux/selectors'; import { analyticsRoutes } from 'routes'; import { AccountsStatsCard, BlockHeightStatsCard, + ChartContractsTransactions, TransactionsStatsCard, ValidatorsStatusCard, HeroPills, diff --git a/src/layouts/NativeTokenLayout/NativeTokenDetailsCard.tsx b/src/layouts/NativeTokenLayout/NativeTokenDetailsCard.tsx new file mode 100644 index 000000000..eddf55374 --- /dev/null +++ b/src/layouts/NativeTokenLayout/NativeTokenDetailsCard.tsx @@ -0,0 +1,152 @@ +import { useMemo } from 'react'; +import BigNumber from 'bignumber.js'; +import { useSelector } from 'react-redux'; + +import { + SocialIcons, + SocialWebsite, + HeroDetailsCard, + FormatUSD, + NativeTokenLogo +} from 'components'; +import { useGetNativeTokenDetails, useHasGrowthWidgets } from 'hooks'; +import { growthPriceSelector } from 'redux/selectors'; + +export const NativeTokenDetailsCard = () => { + const hasGrowthWidgets = useHasGrowthWidgets(); + const { + name, + ticker, + decimals, + assets, + price: economicsPrice, + marketCap: economicsMarketCap, + accounts, + transactions, + supply, + circulatingSupply + } = useGetNativeTokenDetails(); + + const { unprocessed: unprocessedGrowth, isFetched: isGrowthFetched } = + useSelector(growthPriceSelector); + + const title = assets?.name ?? name; + + // avoid differences between cached api calls + const price = + hasGrowthWidgets && isGrowthFetched + ? unprocessedGrowth.currentPrice + : economicsPrice; + + const marketCap = + hasGrowthWidgets && isGrowthFetched + ? unprocessedGrowth.marketCap + : economicsMarketCap; + + const detailItems = useMemo( + () => [ + assets?.description + ? { + title: 'Description', + value: ( +
+ {assets.description} +
+ ) + } + : {}, + assets?.website + ? { + title: 'Website', + value: + } + : {}, + assets?.social && Object.keys(assets.social).length > 0 + ? { + title: 'Other Links', + value: + } + : {}, + !assets && ticker !== name ? { title: 'Name', value: name } : {}, + { title: 'Decimals', value: decimals } + ], + [assets, ticker, name] + ); + + const statsCards = useMemo( + () => [ + ...(price && marketCap + ? [ + { + title: 'Price', + value: ( + + ) + }, + { + title: 'Market Cap', + value: ( + + ) + } + ] + : []), + { title: 'Holders', value: new BigNumber(accounts).toFormat() }, + { + title: 'Transactions', + value: new BigNumber(transactions || 0).toFormat() + } + ], + [price, marketCap, transactions, accounts] + ); + + const smallStatsCards = useMemo( + () => [ + supply && new BigNumber(supply).isGreaterThanOrEqualTo(0) + ? { + title: 'Supply', + value: new BigNumber(supply).toFormat(0) + } + : {}, + circulatingSupply && + new BigNumber(circulatingSupply).isGreaterThanOrEqualTo(0) + ? { + title: 'Circulating', + value: new BigNumber(circulatingSupply).toFormat(0) + } + : {} + ], + [supply, circulatingSupply] + ); + + return ( + + } + seoDetails={{ + title, + description: assets?.description, + completeDetails: Boolean(assets) + }} + className='native-token-details' + detailItems={detailItems} + statsCards={statsCards} + smallStatsCards={smallStatsCards} + /> + ); +}; diff --git a/src/layouts/NativeTokenLayout/NativeTokenLayout.tsx b/src/layouts/NativeTokenLayout/NativeTokenLayout.tsx new file mode 100644 index 000000000..273a5e684 --- /dev/null +++ b/src/layouts/NativeTokenLayout/NativeTokenLayout.tsx @@ -0,0 +1,58 @@ +import { useEffect } from 'react'; +import { useSelector } from 'react-redux'; +import { Outlet } from 'react-router-dom'; + +import { Loader, PageState } from 'components'; +import { useHasGrowthWidgets } from 'hooks'; +import { faCoins } from 'icons/regular'; +import { activeNetworkSelector, economicsSelector } from 'redux/selectors'; +import { ChartPrice, ChartStake } from 'widgets'; + +import { NativeTokenDetailsCard } from './NativeTokenDetailsCard'; + +export const NativeTokenLayout = () => { + const hasGrowthWidgets = useHasGrowthWidgets(); + const { egldLabel } = useSelector(activeNetworkSelector); + const { isFetched } = useSelector(economicsSelector); + + const loading = isFetched === undefined; + const failed = isFetched === false; + + useEffect(() => { + setTimeout(() => { + window.scrollTo(0, 0); + }); + }, []); + + if (loading) { + return ; + } + + if (failed) { + return ( + + {egldLabel} +
+ } + isError + /> + ); + } + + return ( +
+ + {hasGrowthWidgets && ( +
+ + +
+ )} + +
+ ); +}; diff --git a/src/layouts/NativeTokenLayout/NativeTokenTabs.tsx b/src/layouts/NativeTokenLayout/NativeTokenTabs.tsx new file mode 100644 index 000000000..ae87a0c37 --- /dev/null +++ b/src/layouts/NativeTokenLayout/NativeTokenTabs.tsx @@ -0,0 +1,24 @@ +import { useSelector } from 'react-redux'; + +import { Tabs } from 'components/Tabs'; +import { urlBuilder } from 'helpers'; +import { activeNetworkSelector } from 'redux/selectors'; + +export const NativeTokenTabs = () => { + const { egldLabel = 'egld' } = useSelector(activeNetworkSelector); + + const tabs = [ + { + tabLabel: 'Transactions', + tabTo: urlBuilder.nativeTokenDetails(egldLabel), + activationRoutes: [urlBuilder.nativeTokenDetails(egldLabel)] + }, + { + tabLabel: 'Holders', + tabTo: urlBuilder.nativeTokenDetailsAccounts(egldLabel), + activationRoutes: [urlBuilder.nativeTokenDetailsAccounts(egldLabel)] + } + ]; + + return ; +}; diff --git a/src/layouts/NativeTokenLayout/index.ts b/src/layouts/NativeTokenLayout/index.ts new file mode 100644 index 000000000..c14abb02b --- /dev/null +++ b/src/layouts/NativeTokenLayout/index.ts @@ -0,0 +1 @@ +export * from './NativeTokenLayout'; diff --git a/src/layouts/TokenLayout/TokenLayout.tsx b/src/layouts/TokenLayout/TokenLayout.tsx index fba9225cd..953f9dfa8 100644 --- a/src/layouts/TokenLayout/TokenLayout.tsx +++ b/src/layouts/TokenLayout/TokenLayout.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { useParams, Outlet } from 'react-router-dom'; +import { Navigate, Outlet, useParams } from 'react-router-dom'; import { Loader } from 'components'; import { useAdapter, useGetPage } from 'hooks'; @@ -15,9 +15,12 @@ export const TokenLayout = () => { const { getToken } = useAdapter(); const { hash: tokenId } = useParams(); const { firstPageRefreshTrigger } = useGetPage(); - const { id: activeNetworkId } = useSelector(activeNetworkSelector); + const { id: activeNetworkId, egldLabel } = useSelector(activeNetworkSelector); const { token } = useSelector(tokenSelector); + const isNativeToken = + tokenId && tokenId?.toLowerCase() === egldLabel?.toLowerCase(); + const [isDataReady, setIsDataReady] = useState(); const fetchTokenDetails = () => { @@ -33,13 +36,19 @@ export const TokenLayout = () => { }; useEffect(() => { - fetchTokenDetails(); - }, [firstPageRefreshTrigger, activeNetworkId, tokenId]); + if (!isNativeToken) { + fetchTokenDetails(); + } + }, [firstPageRefreshTrigger, activeNetworkId, tokenId, isNativeToken]); const loading = isDataReady === undefined || (tokenId && tokenId !== token.identifier); const failed = isDataReady === false; + if (isNativeToken) { + return ; + } + if (failed) { return ; } diff --git a/src/pages/Analytics/Analytics.tsx b/src/pages/Analytics/Analytics.tsx index d2db6dbd5..f8cb67160 100644 --- a/src/pages/Analytics/Analytics.tsx +++ b/src/pages/Analytics/Analytics.tsx @@ -9,10 +9,9 @@ import { useAdapter, useHasGrowthWidgets, useNetworkRoute } from 'hooks'; import { AnalyticsChart } from 'pages/AnalyticsCompare/AnalyticsChart'; import { FailedAnalytics } from 'pages/AnalyticsCompare/components/FailedAnalytics'; import { NoAnalytics } from 'pages/AnalyticsCompare/components/NoAnalytics'; -import { ChartContractsTransactions } from 'pages/Home/components/ChartContractsTransactions'; import { activeNetworkSelector } from 'redux/selectors'; import { analyticsRoutes } from 'routes'; -import { MostUsed } from 'widgets'; +import { ChartContractsTransactions, MostUsed } from 'widgets'; import { ChartWrapper } from './components/ChartWrapper'; diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index bc9ee634f..b9ccefc2a 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -1,10 +1,12 @@ import { useHasGrowthWidgets } from 'hooks'; -import { MostUsed } from 'widgets'; +import { + MostUsed, + ChartContractsTransactions, + ChartPrice, + ChartStake, + EconomicsCard +} from 'widgets'; -import { ChartContractsTransactions } from './components/ChartContractsTransactions'; -import { ChartPrice } from './components/ChartPrice'; -import { ChartStake } from './components/ChartStake'; -import { EconomicsCard } from './components/EconomicsCard'; import { LatestBlocks } from './components/LatestBlocks'; import { LatestTransactions } from './components/LatestTransactions'; @@ -15,9 +17,9 @@ export const Home = () => {
{hasGrowthWidgets && ( <> -
+
- +
diff --git a/src/pages/Home/components/ChartArea/styles.module.scss b/src/pages/Home/components/ChartArea/styles.module.scss deleted file mode 100644 index d0794f80e..000000000 --- a/src/pages/Home/components/ChartArea/styles.module.scss +++ /dev/null @@ -1,33 +0,0 @@ -.tooltip { - border-radius: 0.5rem; - border: 1px solid var(--black); - overflow: hidden; - font-size: 0.75rem; - background-color: var(--neutral-900); - text-align: left; - opacity: 0.8; - - .date { - line-height: 1; - color: var(--neutral-400); - background-color: var(--black); - padding: 0.25rem 0.5rem; - } - - .items { - padding: 0 0.5rem; - - .item { - margin-bottom: 0.25rem; - padding: 0.25rem 0; - - &:last-child { - margin-bottom: 0; - } - - .label { - display: block; - } - } - } -} diff --git a/src/pages/Home/components/ChartStake/styles.module.scss b/src/pages/Home/components/ChartStake/styles.module.scss deleted file mode 100644 index ee7f03d84..000000000 --- a/src/pages/Home/components/ChartStake/styles.module.scss +++ /dev/null @@ -1,161 +0,0 @@ -.chart { - background-color: black; - border-radius: 1rem; - margin: 1rem 0; - padding: 1rem 0; - display: flex; - min-height: 18.75rem; - position: relative; - flex-direction: column; - - @media (min-width: 768px) { - padding: 1.5rem 0; - margin: 2rem 0; - } - - @media (min-width: 1200px) { - width: 41.85%; - margin: 0; - margin-left: 1rem; - margin-bottom: 1rem; - } - - .root { - position: absolute; - left: 0; - top: 0; - bottom: 0; - width: 100%; - height: 100%; - display: flex; - align-items: center; - } - - .wrapper { - display: flex; - align-items: flex-start; - justify-content: space-between; - color: var(--primary); - padding: 0 1rem 1rem; - pointer-events: none; - position: relative; - z-index: 2; - pointer-events: none; - - @media (min-width: 768px) { - padding: 0 2rem 2rem; - } - - .right { - pointer-events: all; - } - } - - .label { - color: var(--neutral-500); - font-family: var(--headings-font-family); - } - - .price { - font-weight: 500; - font-family: var(--headings-font-family); - font-size: 1.5rem; - line-height: 1.25; - margin-bottom: 0.5rem; - - @media (min-width: 992px) { - font-size: 2rem; - } - - span { - color: var(--primary-100); - font-size: 1.5rem; - } - } - - .change { - display: flex; - align-items: center; - - .percentage { - font-size: 0.75rem; - color: var(--primary-200); - margin-left: 0.33rem; - } - } - - .statistics { - display: flex; - align-items: flex-start; - justify-content: flex-start; - flex-wrap: wrap; - padding: 3rem 1rem 0; - margin-top: auto; - - @media (min-width: 768px) { - padding: 2rem 2rem 0; - } - - .statistic { - margin-top: 1rem; - margin-right: 2rem; - - &:last-child { - margin-right: 0; - } - - .value { - font-size: 1.25rem; - font-family: var(--headings-font-family); - font-weight: 500; - color: var(--neutral-300); - line-height: 1; - - @media (min-width: 768px) { - font-size: 1.5rem; - } - } - } - } -} - -.tooltip { - border: 2px solid #24f7dd33; - border-radius: 1rem; - padding: 0.625rem; - padding-left: 1.75rem; - line-height: 1; - color: var(--primary); - font-family: 'Roobert SemiBold'; - font-weight: 600; - position: relative; - overflow: hidden; - - &:before { - content: ''; - left: 0.625rem; - width: 0.625rem; - top: 50%; - -webkit-transform: translateY(-50%); - transform: translateY(-50%); - position: absolute; - border-radius: 50%; - background-color: var(--primary); - height: 0.625rem; - } - - &:after { - content: ''; - left: 0; - right: 0; - top: 0; - bottom: 0; - position: absolute; - background: linear-gradient( - to bottom, - black 0%, - rgba(var(--primary-rgb), 0.15) 100% - ); - z-index: -1; - } -} diff --git a/src/pages/Home/home.styles.scss b/src/pages/Home/home.styles.scss new file mode 100644 index 000000000..328669f88 --- /dev/null +++ b/src/pages/Home/home.styles.scss @@ -0,0 +1,10 @@ +.home { + @include media-breakpoint-up(xl) { + .chart-price { + width: 37.5%; + } + .chart-stake { + width: 41.85%; + } + } +} diff --git a/src/pages/NativeToken/NativeTokenAccounts.tsx b/src/pages/NativeToken/NativeTokenAccounts.tsx new file mode 100644 index 000000000..2f2276b6e --- /dev/null +++ b/src/pages/NativeToken/NativeTokenAccounts.tsx @@ -0,0 +1,56 @@ +import { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { useSearchParams } from 'react-router-dom'; + +import { AccountsTable } from 'components'; +import { useAdapter, useGetNativeTokenDetails, useGetPage } from 'hooks'; +import { NativeTokenTabs } from 'layouts/NativeTokenLayout/NativeTokenTabs'; +import { activeNetworkSelector } from 'redux/selectors'; +import { AccountType } from 'types'; + +export const NativeTokenAccounts = () => { + const [searchParams] = useSearchParams(); + const { id: activeNetworkId } = useSelector(activeNetworkSelector); + + const { price, marketCap, supply, decimals } = useGetNativeTokenDetails(); + const { page, size } = useGetPage(); + const { getAccounts, getAccountsCount } = useAdapter(); + + const [accounts, setAccounts] = useState([]); + const [accountsCount, setAccountsCount] = useState(0); + const [isDataReady, setIsDataReady] = useState(); + + const fetchAccounts = () => { + Promise.all([getAccounts({ page, size }), getAccountsCount({})]).then( + ([tokenAccountsData, tokenAccountsCountData]) => { + if (tokenAccountsData.success && tokenAccountsCountData.success) { + setAccounts(tokenAccountsData.data); + setAccountsCount(tokenAccountsCountData.data); + } + setIsDataReady( + tokenAccountsData.success && tokenAccountsCountData.success + ); + } + ); + }; + + useEffect(() => { + fetchAccounts(); + }, [activeNetworkId, searchParams]); + + const showValue = Boolean(price && marketCap); + + return ( + } + accounts={accounts} + accountsCount={accountsCount} + price={price} + supply={supply} + decimals={decimals} + showValue={showValue} + isNativeToken={true} + isDataReady={isDataReady} + /> + ); +}; diff --git a/src/pages/NativeToken/NativeTokenTransactions.tsx b/src/pages/NativeToken/NativeTokenTransactions.tsx new file mode 100644 index 000000000..b0714e9df --- /dev/null +++ b/src/pages/NativeToken/NativeTokenTransactions.tsx @@ -0,0 +1,56 @@ +import { useEffect, useRef } from 'react'; +import { useSelector } from 'react-redux'; +import { useSearchParams } from 'react-router-dom'; + +import { TransactionsTable } from 'components'; +import { isEgldToken } from 'helpers'; +import { useAdapter, useFetchTransactions } from 'hooks'; +import { NativeTokenTabs } from 'layouts/NativeTokenLayout/NativeTokenTabs'; +import { activeNetworkSelector } from 'redux/selectors'; +import { TransactionFiltersEnum } from 'types'; + +export const NativeTokenTransactions = () => { + const ref = useRef(null); + const [searchParams] = useSearchParams(); + const { id: activeNetworkId, egldLabel } = useSelector(activeNetworkSelector); + const { getTransfers, getTransfersCount } = useAdapter(); + + const { + fetchTransactions, + transactions, + totalTransactions, + isDataReady, + dataChanged + } = useFetchTransactions(getTransfers, getTransfersCount, { + token: isEgldToken(egldLabel) ? 'EGLD' : egldLabel + }); + + useEffect(() => { + if (ref.current !== null) { + fetchTransactions(); + } + }, [activeNetworkId]); + + useEffect(() => { + fetchTransactions(Boolean(searchParams.toString())); + }, [searchParams]); + + return ( +
+
+
+ } + dataChanged={dataChanged} + isDataReady={isDataReady} + inactiveFilters={[TransactionFiltersEnum.token]} + showLockedAccounts + /> +
+
+
+ ); +}; diff --git a/src/pages/NativeToken/nativeToken.styles.scss b/src/pages/NativeToken/nativeToken.styles.scss new file mode 100644 index 000000000..06bef5b3f --- /dev/null +++ b/src/pages/NativeToken/nativeToken.styles.scss @@ -0,0 +1,10 @@ +.native-token { + @include media-breakpoint-up(xl) { + .chart-price { + width: 50%; + } + .chart-stake { + width: 50%; + } + } +} diff --git a/src/pages/TokenDetails/TokenAccounts.tsx b/src/pages/TokenDetails/TokenAccounts.tsx index 7a7706f75..6724e6600 100644 --- a/src/pages/TokenDetails/TokenAccounts.tsx +++ b/src/pages/TokenDetails/TokenAccounts.tsx @@ -1,30 +1,16 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useState } from 'react'; import BigNumber from 'bignumber.js'; -import classNames from 'classnames'; import { useSelector } from 'react-redux'; import { useSearchParams } from 'react-router-dom'; import { LOW_LIQUIDITY_MARKET_CAP_DISPLAY_TRESHOLD } from 'appConstants'; -import { - Loader, - Pager, - PageSize, - FormatAmount, - PageState, - AccountLink, - FormatNumber, - FormatUSD, - PercentageBar -} from 'components'; -import { formatBigNumber, parseAmount } from 'helpers'; +import { AccountsTable } from 'components'; import { useAdapter, useGetPage } from 'hooks'; -import { faUser } from 'icons/regular'; import { TokenTabs } from 'layouts/TokenLayout/TokenTabs'; import { activeNetworkSelector, tokenSelector } from 'redux/selectors'; import { AccountType } from 'types'; export const TokenDetailsAccounts = () => { - const ref = useRef(null); const [searchParams] = useSearchParams(); const { token } = useSelector(tokenSelector); const { id: activeNetworkId } = useSelector(activeNetworkSelector); @@ -44,20 +30,20 @@ export const TokenDetailsAccounts = () => { const [accounts, setAccounts] = useState([]); const [accountsCount, setAccountsCount] = useState(0); - const [dataReady, setDataReady] = useState(); + const [isDataReady, setIsDataReady] = useState(); const fetchAccounts = () => { Promise.all([ getTokenAccounts({ tokenId: identifier, page, size }), getTokenAccountsCount({ tokenId: identifier }) ]).then(([tokenAccountsData, tokenAccountsCountData]) => { - if (ref.current !== null) { - if (tokenAccountsData.success && tokenAccountsCountData.success) { - setAccounts(tokenAccountsData.data); - setAccountsCount(tokenAccountsCountData.data); - setDataReady(true); - } + if (tokenAccountsData.success && tokenAccountsCountData.success) { + setAccounts(tokenAccountsData.data); + setAccountsCount(tokenAccountsCountData.data); } + setIsDataReady( + tokenAccountsData.success && tokenAccountsCountData.success + ); }); }; @@ -65,131 +51,26 @@ export const TokenDetailsAccounts = () => { fetchAccounts(); }, [activeNetworkId, totalAccounts, searchParams, identifier]); - const hasSupply = new BigNumber(supply ?? 0).isGreaterThan(0); - const showAccounts = dataReady === true && accounts.length > 0; - const showValue = + const showValue = Boolean( price && - marketCap && - (!isLowLiquidity || - new BigNumber(marketCap).isLessThan( - LOW_LIQUIDITY_MARKET_CAP_DISPLAY_TRESHOLD - )); + marketCap && + (!isLowLiquidity || + new BigNumber(marketCap).isLessThan( + LOW_LIQUIDITY_MARKET_CAP_DISPLAY_TRESHOLD + )) + ); return ( -
-
-
-
- - 0} - className='d-flex ms-auto me-auto me-sm-0' - /> -
-
- {showAccounts ? ( - <> -
-
- - - - - - {hasSupply && ( - - )} - {showValue && } - - - - {accounts.map((account) => { - const holdingsPercentage = new BigNumber(account.balance) - .times(100) - .dividedBy(parseAmount(supply, decimals)); - - return ( - - - - {hasSupply && ( - - )} - {showValue && ( - - )} - - ); - })} - -
- Address - BalancePercentageValue
-
- -
-
- - -
- -
- -
- -
-
-
-
- - 0} /> -
- - ) : ( - <> - {dataReady === undefined && ( - - )} - {dataReady === false && ( - - )} - {dataReady === true && accounts.length === 0 && ( - - )} - - )} -
-
+ } + accounts={accounts} + accountsCount={accountsCount} + price={price} + supply={supply} + decimals={decimals} + showValue={showValue} + isDataReady={isDataReady} + message='Token Holders' + /> ); }; diff --git a/src/pages/TokenDetails/TokenLockedAccounts.tsx b/src/pages/TokenDetails/TokenLockedAccounts.tsx index abbe041d8..8a9d6ac26 100644 --- a/src/pages/TokenDetails/TokenLockedAccounts.tsx +++ b/src/pages/TokenDetails/TokenLockedAccounts.tsx @@ -1,27 +1,15 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useState } from 'react'; import BigNumber from 'bignumber.js'; import { useSelector } from 'react-redux'; import { LOW_LIQUIDITY_MARKET_CAP_DISPLAY_TRESHOLD } from 'appConstants'; -import { - Loader, - AccountLink, - PageState, - FormatAmount, - FormatNumber, - FormatUSD, - PercentageBar -} from 'components'; -import { formatBigNumber, parseAmount } from 'helpers'; +import { AccountsTable } from 'components'; import { useAdapter } from 'hooks'; -import { faUser } from 'icons/regular'; import { TokenTabs } from 'layouts/TokenLayout/TokenTabs'; import { activeNetworkSelector, tokenSelector } from 'redux/selectors'; import { TokenLockedAccountType } from 'types'; export const TokenDetailsLockedAccounts = () => { - const ref = useRef(null); - const { id: activeNetworkId } = useSelector(activeNetworkSelector); const { token } = useSelector(tokenSelector); const { identifier, price, marketCap, supply, isLowLiquidity, decimals } = @@ -31,16 +19,14 @@ export const TokenDetailsLockedAccounts = () => { const [tokenLockedAccounts, setTokenLockedAccounts] = useState< TokenLockedAccountType[] >([]); - const [dataReady, setDataReady] = useState(); + const [isDataReady, setIsDataReady] = useState(); const fetchTokenLockedAccounts = () => { getTokenSupply({ tokenId: identifier }).then(({ data, success }) => { - if (ref.current !== null) { - if (success && data?.lockedAccounts) { - setTokenLockedAccounts(data.lockedAccounts); - } - setDataReady(success); + if (success && data?.lockedAccounts) { + setTokenLockedAccounts(data.lockedAccounts); } + setIsDataReady(Boolean(success && data?.lockedAccounts)); }); }; @@ -48,127 +34,27 @@ export const TokenDetailsLockedAccounts = () => { fetchTokenLockedAccounts(); }, [activeNetworkId, identifier]); - const showLockedAccounts = - dataReady === true && tokenLockedAccounts.length > 0; - const hasSupply = new BigNumber(supply ?? 0).isGreaterThan(0); - const showValue = + const showValue = Boolean( price && - marketCap && - (!isLowLiquidity || - new BigNumber(marketCap).isLessThan( - LOW_LIQUIDITY_MARKET_CAP_DISPLAY_TRESHOLD - )); + marketCap && + (!isLowLiquidity || + new BigNumber(marketCap).isLessThan( + LOW_LIQUIDITY_MARKET_CAP_DISPLAY_TRESHOLD + )) + ); return ( -
-
-
-
- -
-
- {showLockedAccounts ? ( - <> -
-
- - - - - - - {hasSupply && ( - - )} - {showValue && } - - - - {tokenLockedAccounts.map((lockedAccount) => { - const holdingsPercentage = new BigNumber( - lockedAccount.balance - ) - .times(100) - .dividedBy(parseAmount(supply, decimals)); - - return ( - - - - - {hasSupply && ( - - )} - {showValue && ( - - )} - - ); - })} - -
AddressNameBalancePercentageValue
- - {lockedAccount.name} - - -
- -
- -
- -
-
-
-
- - ) : ( - <> - {dataReady === undefined && ( - - )} - {dataReady === false && ( - - )} - {dataReady === true && tokenLockedAccounts.length === 0 && ( - - )} - - )} -
-
+ } + accounts={tokenLockedAccounts} + accountsCount={tokenLockedAccounts.length} + price={price} + supply={supply} + decimals={decimals} + showValue={showValue} + isDataReady={isDataReady} + hasNameColumn={true} + message='Token Locked Accounts' + /> ); }; diff --git a/src/pages/Tokens/components/TokensTable/NativeTokenRow.tsx b/src/pages/Tokens/components/TokensTable/NativeTokenRow.tsx index 97cb37f03..511cb3318 100644 --- a/src/pages/Tokens/components/TokensTable/NativeTokenRow.tsx +++ b/src/pages/Tokens/components/TokensTable/NativeTokenRow.tsx @@ -1,15 +1,15 @@ import BigNumber from 'bignumber.js'; import { useSelector } from 'react-redux'; -import { BRAND_NAME } from 'appConstants'; -import { NativeTokenLogo } from 'components'; +import { NativeTokenLogo, NetworkLink } from 'components'; import { pagerHelper } from 'components/Pager/helpers/pagerHelper'; +import { formatBigNumber } from 'helpers'; import { useGetPage, useGetSearch, useGetSort, - useIsSovereign, - useIsNativeTokenSearched + useIsNativeTokenSearched, + useGetNativeTokenDetails } from 'hooks'; import { economicsSelector, @@ -35,7 +35,7 @@ export const NativeTokenRow = ({ index: number; totalTokens: number; }) => { - const { egldLabel, name } = useSelector(activeNetworkSelector); + const { egldLabel } = useSelector(activeNetworkSelector); const { isFetched: isEconomicsFetched, price, @@ -43,25 +43,16 @@ export const NativeTokenRow = ({ circulatingSupply, unprocessed: unProcessedEconomics } = useSelector(economicsSelector); - const { - isFetched: isStatsFetched, - accounts, - transactions, - unprocessed: unProcessedStats - } = useSelector(statsSelector); - + const { isFetched: isStatsFetched } = useSelector(statsSelector); + const { assets, accounts, transactions } = useGetNativeTokenDetails(); const { page, size } = useGetPage(); const { search } = useGetSearch(); const { sort, order } = useGetSort(); - const isSovereign = useIsSovereign(); + const showOnSearch = useIsNativeTokenSearched(); - const description = isSovereign - ? `${egldLabel} Token is native to ${name ?? BRAND_NAME}` - : `The ${BRAND_NAME} eGold (${egldLabel}) Token is native to the ${BRAND_NAME} Network and will be used for everything from staking, governance, transactions, smart contracts and validator rewards.`; + const nativeTokenLink = `/${egldLabel?.toLowerCase()}`; - const showOnSearch = useIsNativeTokenSearched(); let showOnFilter = (!page || page === 1) && index === 0; - const previousToken = tokens[index > 0 ? index - 1 : 0]; const currentToken = tokens[index]; const nextToken = tokens[index < tokens.length - 1 ? index + 1 : index]; @@ -85,8 +76,8 @@ export const NativeTokenRow = ({ price: unProcessedEconomics.price, marketCap: unProcessedEconomics.marketCap, circulatingSupply: unProcessedEconomics.circulatingSupply, - accounts: unProcessedStats.accounts, - transactions: unProcessedStats.transactions + accounts, + transactions }; const tokenValue = currentToken[sort as keyof TokenType]; @@ -151,31 +142,33 @@ export const NativeTokenRow = ({
- +
-
+
- {egldLabel} -
- {description} -
+ + {egldLabel} + + {assets?.description && ( +
+ {assets.description} +
+ )}
- - {isSovereign ? name : BRAND_NAME} {egldLabel} - + {assets?.name ?? egldLabel} {price} {circulatingSupply} {marketCap} - {accounts} - {transactions} + {formatBigNumber({ value: accounts })} + {formatBigNumber({ value: transactions })} ); }; diff --git a/src/redux/slices/economics.ts b/src/redux/slices/economics.ts index e888ef7e9..2218263d8 100644 --- a/src/redux/slices/economics.ts +++ b/src/redux/slices/economics.ts @@ -28,7 +28,7 @@ export const getInitialEconomicsState = (): EconomicsSliceType => { baseApr: 0, tokenMarketCap: 0 }, - isFetched: false + isFetched: undefined }; }; diff --git a/src/redux/slices/stats.ts b/src/redux/slices/stats.ts index 60f32f86a..342f66459 100644 --- a/src/redux/slices/stats.ts +++ b/src/redux/slices/stats.ts @@ -10,6 +10,7 @@ export const getInitialStatsState = (): StatsSliceType => { blocks: 0, accounts: 0, transactions: 0, + scResults: 0, refreshRate: 0, epoch: 0, roundsPassed: 0, @@ -25,6 +26,7 @@ export const getInitialStatsState = (): StatsSliceType => { blocks: ELLIPSIS, accounts: ELLIPSIS, transactions: ELLIPSIS, + scResults: ELLIPSIS, refreshRate: 0, epoch: 0, roundsPassed: 0, @@ -52,6 +54,7 @@ export const statsSlice = createSlice({ state.blocks = action.payload.blocks; state.accounts = action.payload.accounts; state.transactions = action.payload.transactions; + state.scResults = action.payload.scResults; state.refreshRate = action.payload.refreshRate; state.epoch = action.payload.epoch; state.roundsPassed = action.payload.roundsPassed; diff --git a/src/routes/layouts/index.ts b/src/routes/layouts/index.ts index df1e5020f..94dc4c9b5 100644 --- a/src/routes/layouts/index.ts +++ b/src/routes/layouts/index.ts @@ -1,6 +1,7 @@ export * from './accountLayout'; export * from './blockLayout'; export * from './collectionLayout'; +export * from './nativeTokenLayout'; export * from './nftLayout'; export * from './tokenLayout'; export * from './transactionsLayout'; diff --git a/src/routes/layouts/nativeTokenLayout.ts b/src/routes/layouts/nativeTokenLayout.ts new file mode 100644 index 000000000..c63db58a9 --- /dev/null +++ b/src/routes/layouts/nativeTokenLayout.ts @@ -0,0 +1,45 @@ +import { networks } from 'config'; + +import { NativeTokenLayout } from 'layouts/NativeTokenLayout'; +import { NativeTokenAccounts } from 'pages/NativeToken/NativeTokenAccounts'; +import { NativeTokenTransactions } from 'pages/NativeToken/NativeTokenTransactions'; + +import { TitledRouteObject } from '../routes'; + +export const nativeTokenLayout: TitledRouteObject[] = []; + +networks.forEach((network) => { + if (!network.egldLabel) { + return; + } + + const networkPath = `/${network.egldLabel?.toLowerCase()}`; + + const routeExists = nativeTokenLayout.find( + (route) => route.path === networkPath + ); + + if (routeExists) { + return; + } + + nativeTokenLayout.push({ + path: networkPath, + preventScroll: true, + Component: NativeTokenLayout, + children: [ + { + path: networkPath, + title: network.egldLabel, + preventScroll: true, + Component: NativeTokenTransactions + }, + { + path: `${networkPath}/accounts`, + title: 'Holders', + preventScroll: true, + Component: NativeTokenAccounts + } + ] + }); +}); diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx index d4e1149eb..6216761ec 100644 --- a/src/routes/routes.tsx +++ b/src/routes/routes.tsx @@ -20,6 +20,7 @@ import { blocksRoutes, collectionLayout, collectionRoutes, + nativeTokenLayout, nftLayout, nftRoutes, tokenLayout, @@ -107,7 +108,8 @@ const mainRoutes: TitledRouteObject[] = [ ...nftLayout, ...tokenLayout, ...transactionsLayout, - ...validatorLayout + ...validatorLayout, + ...nativeTokenLayout ] } ]; diff --git a/src/types/stats.types.ts b/src/types/stats.types.ts index 40b557911..cdf1e2698 100644 --- a/src/types/stats.types.ts +++ b/src/types/stats.types.ts @@ -5,6 +5,7 @@ export interface StatsType { blocks: number; accounts: number; transactions: number; + scResults: number; refreshRate: number; epoch: number; roundsPassed: number; @@ -25,6 +26,7 @@ export interface StatsSliceType extends SliceType { blocks: string; accounts: string; transactions: string; + scResults: string; refreshRate: number; epoch: number; roundsPassed: number; diff --git a/src/widgets/ChartCard/ChartCard.tsx b/src/widgets/ChartCard/ChartCard.tsx new file mode 100644 index 000000000..8d9974e85 --- /dev/null +++ b/src/widgets/ChartCard/ChartCard.tsx @@ -0,0 +1,59 @@ +import { ReactNode } from 'react'; +import classNames from 'classnames'; +import { SingleValue } from 'react-select'; + +import { Select, SelectOptionType } from 'components'; +import { StatisticType, WithClassnameType } from 'types'; + +export interface ChartCardUIType extends WithClassnameType { + title?: ReactNode; + value?: ReactNode; + subtitle?: ReactNode; + children?: ReactNode; + filters?: SelectOptionType[]; + defaultFilterValue?: SelectOptionType; + statistics?: StatisticType[]; + onChange?: (option: SingleValue) => void; +} + +export const ChartCard = ({ + title, + value, + subtitle, + children, + filters = [], + defaultFilterValue, + statistics = [], + onChange, + className +}: ChartCardUIType) => { + return ( +
+
+
+ {title &&
{title}
} + {value &&
{value}
} + {subtitle &&
{subtitle}
} +
+ {onChange && ( +
+ + -
-
- -
- - new Intl.NumberFormat('en-US', { - style: 'currency', - currency: 'USD', - maximumFractionDigits: 2, - minimumFractionDigits: 2 - }).format(option.value) - } - /> -
-
- {statistics.map((statistic) => ( -
-
{statistic.label}
-
{statistic.value}
-
- ))} -
-
+ {priceChange24h} today + + } + > + + new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + maximumFractionDigits: 2, + minimumFractionDigits: 2 + }).format(option.value) + } + /> + ); }; diff --git a/src/pages/Home/components/ChartPrice/index.ts b/src/widgets/ChartPrice/index.ts similarity index 100% rename from src/pages/Home/components/ChartPrice/index.ts rename to src/widgets/ChartPrice/index.ts diff --git a/src/pages/Home/components/ChartStake/ChartStake.tsx b/src/widgets/ChartStake/ChartStake.tsx similarity index 63% rename from src/pages/Home/components/ChartStake/ChartStake.tsx rename to src/widgets/ChartStake/ChartStake.tsx index 0b88ef59c..e4370f5a1 100644 --- a/src/pages/Home/components/ChartStake/ChartStake.tsx +++ b/src/widgets/ChartStake/ChartStake.tsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import { useSelector } from 'react-redux'; import { SingleValue } from 'react-select'; -import { Select, SelectOptionType } from 'components'; +import { SelectOptionType } from 'components'; import { getPrimaryColor } from 'helpers'; import { useFetchGrowthStaking } from 'hooks'; import { growthStakingSelector, activeNetworkSelector } from 'redux/selectors'; @@ -14,8 +14,7 @@ import { WithClassnameType } from 'types'; -import styles from './styles.module.scss'; -import { ChartRoot } from '../ChartRoot'; +import { ChartCard, ChartRoot } from '../ChartCard'; export const ChartStake = ({ className }: WithClassnameType) => { const { @@ -83,7 +82,7 @@ export const ChartStake = ({ className }: WithClassnameType) => { const defaultValue = filters.find((filter) => filter.value === initialFilter); const [data, setData] = useState(dataMap.get(initialFilter)); - const onChange = useCallback( + const handleChange = useCallback( (option: SingleValue) => { if (option && option.value && isFetched) { setData(dataMap.get(String(option.value))); @@ -102,45 +101,28 @@ export const ChartStake = ({ className }: WithClassnameType) => { useEffect(onInitialLoad, [onInitialLoad]); return ( -
-
-
-
Total Staked
-
- {totalStaked} EGLD ({stakingPercentage}) -
-
- -
-