diff --git a/explorer/src/components/account/TokenAccountSection.tsx b/explorer/src/components/account/TokenAccountSection.tsx index 9f4d28ece9..3c6a2fee52 100644 --- a/explorer/src/components/account/TokenAccountSection.tsx +++ b/explorer/src/components/account/TokenAccountSection.tsx @@ -11,7 +11,7 @@ import { TableCardBody } from "components/common/TableCardBody"; import { Address } from "components/common/Address"; import { UnknownAccountCard } from "./UnknownAccountCard"; import { Cluster, useCluster } from "providers/cluster"; -import { normalizeTokenAmount } from "utils"; +import { abbreviatedNumber, normalizeTokenAmount } from "utils"; import { addressLabel } from "utils/tx"; import { reportError } from "utils/sentry"; import { useTokenRegistry } from "providers/mints/token-registry"; @@ -19,6 +19,7 @@ import { BigNumber } from "bignumber.js"; import { Copyable } from "components/common/Copyable"; import { CoingeckoStatus, useCoinGecko } from "utils/coingecko"; import { displayTimestampWithoutDate } from "utils/date"; +import { LoadingCard } from "components/common/LoadingCard"; const getEthAddress = (link?: string) => { let address = ""; @@ -95,129 +96,177 @@ function MintAccountCard({ } return ( -
-
-

- {tokenInfo ? "Overview" : "Token Mint"} -

- -
- - - - Address - -
- - - - - {info.mintAuthority === null ? "Fixed Supply" : "Current Supply"} - - - {normalizeTokenAmount(info.supply, info.decimals).toLocaleString( - "en-US", - { - minimumFractionDigits: info.decimals, - } - )} - - - {tokenPriceInfo?.price && ( - - Current Price - - $ - {tokenPriceInfo.price.toLocaleString("en-US", { - minimumFractionDigits: 2, - })} - - + <> + {tokenInfo?.extensions?.coingeckoId && + coinInfo?.status === CoingeckoStatus.Loading && ( + )} - {tokenInfo?.extensions?.website && ( - - Website - - - {tokenInfo.extensions.website} - - - - - )} - {info.mintAuthority && ( - - Mint Authority - -
- - - )} - {info.freezeAuthority && ( - - Freeze Authority - -
- - - )} - - Decimals - {info.decimals} - - {!info.isInitialized && ( - - Status - Uninitialized - - )} - {tokenInfo?.extensions?.bridgeContract && bridgeContractAddress && ( - - Bridge Contract - - - - {bridgeContractAddress} - - - - - )} - {tokenInfo?.extensions?.assetContract && assetContractAddress && ( - - Bridged Asset Contract - - - - {assetContractAddress} - - - - - )} - {tokenPriceInfo && ( -

- Price updated at{" "} - {displayTimestampWithoutDate(tokenPriceInfo.last_updated.getTime())} -

+
+
+
+
+

+ Price{" "} + + Rank #{tokenPriceInfo.market_cap_rank} + +

+

+ ${tokenPriceInfo.price.toFixed(2)}{" "} + {tokenPriceInfo.price_change_percentage_24h > 0 && ( + + ↑{" "} + {tokenPriceInfo.price_change_percentage_24h.toFixed(2)}% + + )} + {tokenPriceInfo.price_change_percentage_24h < 0 && ( + + ↓{" "} + {tokenPriceInfo.price_change_percentage_24h.toFixed(2)}% + + )} + {tokenPriceInfo.price_change_percentage_24h === 0 && ( + 0% + )} +

+
+
+
+
+
+
+

24 Hour Volume

+

+ ${abbreviatedNumber(tokenPriceInfo.volume_24)} +

+
+
+
+
+
+
+

Market Cap

+

+ ${abbreviatedNumber(tokenPriceInfo.market_cap)} +

+

+ Updated at{" "} + {displayTimestampWithoutDate( + tokenPriceInfo.last_updated.getTime() + )} +

+
+
+
+
)} -
+
+
+

+ {tokenInfo ? "Overview" : "Token Mint"} +

+ +
+ + + Address + +
+ + + + + {info.mintAuthority === null ? "Fixed Supply" : "Current Supply"} + + + {normalizeTokenAmount(info.supply, info.decimals).toLocaleString( + "en-US", + { + minimumFractionDigits: info.decimals, + } + )} + + + {tokenInfo?.extensions?.website && ( + + Website + + + {tokenInfo.extensions.website} + + + + + )} + {info.mintAuthority && ( + + Mint Authority + +
+ + + )} + {info.freezeAuthority && ( + + Freeze Authority + +
+ + + )} + + Decimals + {info.decimals} + + {!info.isInitialized && ( + + Status + Uninitialized + + )} + {tokenInfo?.extensions?.bridgeContract && bridgeContractAddress && ( + + Bridge Contract + + + + {bridgeContractAddress} + + + + + )} + {tokenInfo?.extensions?.assetContract && assetContractAddress && ( + + Bridged Asset Contract + + + + {assetContractAddress} + + + + + )} + +
+ ); } diff --git a/explorer/src/pages/ClusterStatsPage.tsx b/explorer/src/pages/ClusterStatsPage.tsx index 807fb6ecb5..dbf6c53faf 100644 --- a/explorer/src/pages/ClusterStatsPage.tsx +++ b/explorer/src/pages/ClusterStatsPage.tsx @@ -7,7 +7,7 @@ import { usePerformanceInfo, useStatsProvider, } from "providers/stats/solanaClusterStats"; -import { lamportsToSol, slotsToHumanString } from "utils"; +import { abbreviatedNumber, lamportsToSol, slotsToHumanString } from "utils"; import { ClusterStatus, useCluster } from "providers/cluster"; import { TpsCard } from "components/TpsCard"; import { displayTimestampWithoutDate, displayTimestampUtc } from "utils/date"; @@ -82,7 +82,7 @@ function StakingComponent() { } if (supply === Status.Idle || supply === Status.Connecting || !coinInfo) { - return ; + return ; } else if (typeof supply === "string") { return ; } @@ -105,10 +105,10 @@ function StakingComponent() { } return ( -
-
-
-
+
+
+
+

Circulating Supply

{displayLamports(supply.circulating)} /{" "} @@ -118,8 +118,11 @@ function StakingComponent() { {circulatingPercentage}% is circulating

-
-
+
+
+
+
+

Active Stake

{activeStake && (

@@ -133,66 +136,65 @@ function StakingComponent() {

)}
-
- {solanaInfo && ( -
-

- Price{" "} - - Rank #{solanaInfo.market_cap_rank} - -

-

- ${solanaInfo.price.toFixed(2)}{" "} - {solanaInfo.price_change_percentage_24h > 0 && ( - - ↑ {solanaInfo.price_change_percentage_24h.toFixed(2)}% - - )} - {solanaInfo.price_change_percentage_24h < 0 && ( - - ↓ {solanaInfo.price_change_percentage_24h.toFixed(2)}% - - )} - {solanaInfo.price_change_percentage_24h === 0 && ( - 0% - )} -

-
- 24h Vol: ${abbreviatedNumber(solanaInfo.volume_24)}{" "} - MCap: ${abbreviatedNumber(solanaInfo.market_cap)} -
-
- )} - {coinInfo.status === CoingeckoStatus.FetchFailed && ( -
-

Price

-

- $--.-- -

-
Error fetching the latest price information
-
- )}
- {solanaInfo && ( -

- Updated at{" "} - {displayTimestampWithoutDate(solanaInfo.last_updated.getTime())} -

- )} +
+
+
+
+ {solanaInfo && ( + <> +

+ Price{" "} + + Rank #{solanaInfo.market_cap_rank} + +

+

+ ${solanaInfo.price.toFixed(2)}{" "} + {solanaInfo.price_change_percentage_24h > 0 && ( + + ↑ {solanaInfo.price_change_percentage_24h.toFixed(2)} + % + + )} + {solanaInfo.price_change_percentage_24h < 0 && ( + + ↓ {solanaInfo.price_change_percentage_24h.toFixed(2)} + % + + )} + {solanaInfo.price_change_percentage_24h === 0 && ( + 0% + )} +

+
+ 24h Vol: ${abbreviatedNumber(solanaInfo.volume_24)}{" "} + MCap: ${abbreviatedNumber(solanaInfo.market_cap)} +
+ + )} + {coinInfo.status === CoingeckoStatus.FetchFailed && ( + <> +

Price

+

+ $--.-- +

+
Error fetching the latest price information
+ + )} + {solanaInfo && ( +

+ Updated at{" "} + {displayTimestampWithoutDate(solanaInfo.last_updated.getTime())} +

+ )} +
+
); } -const abbreviatedNumber = (value: number, fixed = 1) => { - if (value < 1e3) return value; - if (value >= 1e3 && value < 1e6) return +(value / 1e3).toFixed(fixed) + "K"; - if (value >= 1e6 && value < 1e9) return +(value / 1e6).toFixed(fixed) + "M"; - if (value >= 1e9 && value < 1e12) return +(value / 1e9).toFixed(fixed) + "B"; - if (value >= 1e12) return +(value / 1e12).toFixed(fixed) + "T"; -}; - function displayLamports(value: number) { return abbreviatedNumber(lamportsToSol(value)); } diff --git a/explorer/src/scss/_solana.scss b/explorer/src/scss/_solana.scss index 53e020564a..7426b1828c 100644 --- a/explorer/src/scss/_solana.scss +++ b/explorer/src/scss/_solana.scss @@ -379,8 +379,11 @@ pre.json-wrap { } p.updated-time { + position: absolute; font-size: 0.66rem; - text-align: right; + margin: .375rem; + right: 0; + bottom: 0; } .change-positive { diff --git a/explorer/src/utils/coingecko.tsx b/explorer/src/utils/coingecko.tsx index cf86fbc5f9..df7d9626c4 100644 --- a/explorer/src/utils/coingecko.tsx +++ b/explorer/src/utils/coingecko.tsx @@ -9,6 +9,7 @@ const CoinGeckoClient = new CoinGecko(); export enum CoingeckoStatus { Success, FetchFailed, + Loading, } export interface CoinInfo { @@ -49,7 +50,12 @@ export function useCoinGecko(coinId?: string): CoinGeckoResult | undefined { React.useEffect(() => { let interval: NodeJS.Timeout | undefined; if (coinId) { - const getCoinInfo = () => { + const getCoinInfo = (refresh = false) => { + if (!refresh) { + setCoinInfo({ + status: CoingeckoStatus.Loading, + }); + } CoinGeckoClient.coins .fetch(coinId) .then((info: CoinInfoResult) => { @@ -75,7 +81,7 @@ export function useCoinGecko(coinId?: string): CoinGeckoResult | undefined { getCoinInfo(); interval = setInterval(() => { - getCoinInfo(); + getCoinInfo(true); }, PRICE_REFRESH); } return () => { diff --git a/explorer/src/utils/index.tsx b/explorer/src/utils/index.tsx index 9f81b093ad..95fa3943c0 100644 --- a/explorer/src/utils/index.tsx +++ b/explorer/src/utils/index.tsx @@ -125,3 +125,11 @@ export function camelToTitleCase(str: string): string { const result = str.replace(/([A-Z])/g, " $1"); return result.charAt(0).toUpperCase() + result.slice(1); } + +export function abbreviatedNumber(value: number, fixed = 1) { + if (value < 1e3) return value; + if (value >= 1e3 && value < 1e6) return +(value / 1e3).toFixed(fixed) + "K"; + if (value >= 1e6 && value < 1e9) return +(value / 1e6).toFixed(fixed) + "M"; + if (value >= 1e9 && value < 1e12) return +(value / 1e9).toFixed(fixed) + "B"; + if (value >= 1e12) return +(value / 1e12).toFixed(fixed) + "T"; +}